Объединить разработки CAN и STM32 в master
This commit is contained in:
@@ -13,6 +13,9 @@ LOCAL_SRC_FILES := \
|
||||
../../src/set_trends.c \
|
||||
../../src/set_spectrum.c \
|
||||
../../src/balsam_can.c \
|
||||
../../src/set_crc.c \
|
||||
../../src/periph28335.c \
|
||||
../../src/tms2812.c \
|
||||
../../src/gui_catalog.c \
|
||||
../../src/gui_frame.c \
|
||||
../../src/pcan_abi.c \
|
||||
|
||||
@@ -175,7 +175,9 @@ object CanBridgeProtocol {
|
||||
stats = Stats(
|
||||
frames = values[0],
|
||||
crcErrors = values[1],
|
||||
resyncBytes = values[2] + values[3],
|
||||
// The former Kotlin parser counted one resync step
|
||||
// for a rejected CRC frame in addition to stray bytes.
|
||||
resyncBytes = values[1] + values[2] + values[3],
|
||||
sequenceLost = values[4],
|
||||
)
|
||||
}
|
||||
@@ -263,31 +265,14 @@ data class ProtoCanId(
|
||||
val messageType: Int,
|
||||
val body: Int,
|
||||
) {
|
||||
val messageTypeName: String get() = MESSAGE_TYPES[messageType] ?: "Reserved 0x${messageType.toString(16).uppercase()}"
|
||||
val messageTypeName: String get() = ProtoCanMessageType.fromCode(messageType)?.title
|
||||
?: "Reserved 0x${messageType.toString(16).uppercase()}"
|
||||
val deviceName: String? get() = DEVICE_NAMES[device]
|
||||
|
||||
fun summary(): String = "P$priority · PM$pm · Type $deviceType · Dev $device" +
|
||||
(deviceName?.let { " ($it)" } ?: "") + " · $messageTypeName · Body 0x%04X".format(body)
|
||||
|
||||
companion object {
|
||||
private val MESSAGE_TYPES = mapOf(
|
||||
0x0 to "Broadcast",
|
||||
0x1 to "Discrete",
|
||||
0x2 to "Analog",
|
||||
0x3 to "General Address Space",
|
||||
0x4 to "Modbus Coil",
|
||||
0x5 to "Modbus Discrete",
|
||||
0x6 to "Modbus Holding",
|
||||
0x7 to "Modbus Input",
|
||||
0x8 to "Error",
|
||||
0x9 to "Boot Control",
|
||||
0xA to "Boot Data A",
|
||||
0xB to "Boot Data B",
|
||||
0xC to "Boot Status",
|
||||
0xD to "Boot Discovery",
|
||||
0xE to "Settings",
|
||||
0xF to "Pulse",
|
||||
)
|
||||
private val DEVICE_NAMES = mapOf(
|
||||
0xD to "DS_CONTROL",
|
||||
0xE to "Android GUI",
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package ru.setcorp.setflash.core
|
||||
|
||||
/** Canonical ProtoCAN message-type registry shared by Android protocol clients. */
|
||||
enum class ProtoCanMessageType(val code: Int, val title: String) {
|
||||
BROADCAST(0x0, "Broadcast"),
|
||||
DISCRETE(0x1, "Discrete"),
|
||||
ANALOG(0x2, "Analog"),
|
||||
GENERAL_ADDRESS_SPACE(0x3, "General Address Space"),
|
||||
MODBUS_COIL(0x4, "Modbus Coil"),
|
||||
MODBUS_DISCRETE(0x5, "Modbus Discrete"),
|
||||
MODBUS_HOLDING(0x6, "Modbus Holding"),
|
||||
MODBUS_INPUT(0x7, "Modbus Input"),
|
||||
ERROR(0x8, "Error"),
|
||||
BOOT_CONTROL(0x9, "Boot Control"),
|
||||
BOOT_DATA_A(0xA, "Boot Data A"),
|
||||
BOOT_DATA_B(0xB, "Boot Data B"),
|
||||
BOOT_STATUS(0xC, "Boot Status"),
|
||||
BOOT_DISCOVERY(0xD, "Boot Discovery"),
|
||||
SETTINGS(0xE, "Settings"),
|
||||
PULSE(0xF, "Pulse");
|
||||
|
||||
companion object {
|
||||
fun fromCode(code: Int): ProtoCanMessageType? = entries.firstOrNull { it.code == code }
|
||||
}
|
||||
}
|
||||
|
||||
enum class ProtoCanBroadcastType(val code: Int, val title: String) {
|
||||
STATUS(0x0, "STATUS · запрос статуса"),
|
||||
ONOFF(0x1, "ONOFF · вкл/выкл пульса"),
|
||||
RESTART_DEVICE(0x2, "RESTARTDEVICE · перезапуск"),
|
||||
RTC_SETUP(0x3, "RTCSETUP · установка RTC"),
|
||||
END(0xFFF, "END · конец диапазона"),
|
||||
}
|
||||
|
||||
enum class ProtoCanDiscreteType(val code: Int, val title: String) {
|
||||
ACCIDENT(0x0, "ACCIDENT · авария"),
|
||||
WARNING(0x1, "WARNING · предупреждение"),
|
||||
CONTROL_SIGNALS(0x2, "CONTROL_SIGNALS · управление"),
|
||||
FLAGS(0x3, "FLAGS · флаги"),
|
||||
RESET(0x4, "RESET · сброс"),
|
||||
CHANGE_MODE(0x5, "CHANGE_MODE · смена режима"),
|
||||
REQUEST_PARAMETERS(0x6, "REQUEST_LIST_OF_PARAMETERS"),
|
||||
END(0xF, "END · конец диапазона"),
|
||||
}
|
||||
|
||||
enum class ProtoCanAnalogType(val code: Int, val title: String) {
|
||||
UNIVERSAL(0x0, "UNIVERSAL · универсальный"),
|
||||
SETTINGS(0x1, "SETTINGS · уставки"),
|
||||
U(0x2, "U · напряжение"),
|
||||
I(0x3, "I · ток"),
|
||||
T(0x4, "T · температура"),
|
||||
END(0xF, "END · конец диапазона"),
|
||||
}
|
||||
|
||||
/** Portable MsgBody layouts matching `pcan_id.h` and SETGUI's frame builder. */
|
||||
object ProtoCanBody {
|
||||
fun broadcast(type: Int, body: Int): Int = ((type and 0xFFF) shl 4) or (body and 0xF)
|
||||
|
||||
fun typed(type: Int, body: Int): Int = ((type and 0xF) shl 12) or (body and 0xFFF)
|
||||
|
||||
fun modbus(address: Int, registerCount: Int): Int =
|
||||
((address and 0xFFF) shl 4) or (registerCount and 0xF)
|
||||
|
||||
fun error(info: Int, code: Int): Int = ((info and 0xFF) shl 8) or (code and 0xFF)
|
||||
|
||||
fun settings(z: Int, y: Int): Int = ((z and 0xFF) shl 8) or (y and 0xFF)
|
||||
}
|
||||
@@ -4,7 +4,10 @@ package ru.setcorp.setprotocol
|
||||
object NativeSetProtocol {
|
||||
val available: Boolean by lazy {
|
||||
runCatching {
|
||||
System.loadLibrary("setprotocol")
|
||||
val hostLibrary = System.getProperty("setprotocol.library")
|
||||
?: System.getProperty("setplot.library")
|
||||
if (hostLibrary != null) System.load(hostLibrary)
|
||||
else System.loadLibrary("setprotocol")
|
||||
nativeAbiVersion() == 1
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
@@ -13,12 +16,18 @@ object NativeSetProtocol {
|
||||
/** {status, N, Fs, jitter, amplitudes...}; status != 0 has no amplitudes. */
|
||||
external fun nativeSpectrum(times: DoubleArray, values: DoubleArray, maxSize: Int,
|
||||
window: Int, filter: Int, lowHz: Double, highHz: Double, removeMean: Boolean): DoubleArray?
|
||||
/** {frequencyHz, amplitude}, empty when no narrow-band peak is present. */
|
||||
external fun nativeSpectrumPeak(amplitudes: DoubleArray, binHz: Double,
|
||||
relativeThreshold: Double, absoluteFloor: Double): DoubleArray?
|
||||
external fun nativeTrendCanValue(
|
||||
source: Int, address: Long, deviceType: Int, device: Int, byteOffset: Int,
|
||||
extended: Boolean, signed: Boolean, canId: Long, flags: Int, input: ByteArray,
|
||||
): Int
|
||||
external fun nativeTrendWatchRequest(period: Int, addresses: IntArray): ByteArray?
|
||||
external fun nativeTrendWatchAck(input: ByteArray, period: Int, count: Int): Boolean
|
||||
external fun nativeTrendWatchValues(input: ByteArray): IntArray?
|
||||
/** {unsigned timestampMs, word0, ...}. */
|
||||
external fun nativeTrendWatchDecode(input: ByteArray): LongArray?
|
||||
external fun nativePackId(
|
||||
priority: Int,
|
||||
route: Int,
|
||||
@@ -32,6 +41,22 @@ object NativeSetProtocol {
|
||||
external fun nativeBalsamDecode(canId: Long, input: ByteArray): IntArray?
|
||||
external fun nativeBalsamDeviceName(device: Int): String
|
||||
external fun nativeBalsamRegisterName(device: Int, address: Int): String
|
||||
external fun nativePeriph28335Crc16(input: ByteArray): Int
|
||||
external fun nativePeriph28335AppendCrc(input: ByteArray): ByteArray?
|
||||
external fun nativePeriph28335BuildRead(controller: Int, start: Int, count: Int): ByteArray?
|
||||
external fun nativePeriph28335BuildWrite(controller: Int, address: Int, value: Int): ByteArray?
|
||||
external fun nativePeriph28335BuildCommand(controller: Int, commandIndex: Int): ByteArray?
|
||||
external fun nativePeriph28335ExpectedReadSize(count: Int): Int
|
||||
external fun nativePeriph28335DecodeRead(input: ByteArray, controller: Int, count: Int): IntArray?
|
||||
external fun nativePeriph28335ValidateWrite(response: ByteArray, request: ByteArray): Int
|
||||
external fun nativePeriph28335ProjectCount(): Int
|
||||
external fun nativePeriph28335ProjectName(projectIndex: Int): String?
|
||||
external fun nativePeriph28335CommandName(projectIndex: Int, commandIndex: Int): String?
|
||||
external fun nativeTms2812Crc16(input: ByteArray): Int
|
||||
external fun nativeTms2812BuildUpload(controller: Int, wordAddress: Long, byteCount: Long): ByteArray?
|
||||
external fun nativeTms2812ExpectedUploadSize(byteCount: Long): Int
|
||||
external fun nativeTms2812ValidateUpload(input: ByteArray, controller: Int, byteCount: Int): Boolean
|
||||
external fun nativeTms2812DecodeUpload(input: ByteArray, controller: Int, byteCount: Int): ByteArray?
|
||||
external fun nativeEncodeFrame(
|
||||
sequence: Int,
|
||||
flags: Int,
|
||||
|
||||
@@ -7,6 +7,15 @@ data class BalsamRegister(val address: Int, val value: Int, val name: String) {
|
||||
val signedValue: Int get() = if (value < 0x8000) value else value - 0x10000
|
||||
}
|
||||
|
||||
data class BalsamCanNode(
|
||||
val device: Int,
|
||||
val name: String,
|
||||
val commandId: Long,
|
||||
val dataId: Long,
|
||||
)
|
||||
|
||||
data class BalsamCanWireFrame(val canId: Long, val data: ByteArray)
|
||||
|
||||
data class BalsamFrame(
|
||||
val canId: Long,
|
||||
val device: Int,
|
||||
@@ -20,18 +29,31 @@ data class BalsamFrame(
|
||||
val direction = if (fromDevice) "данные" else "команда"
|
||||
val values = registers.joinToString { "${it.displayName}=0x%04X (%d)".format(it.value, it.signedValue) }
|
||||
.ifEmpty { "нет отмеченных регистров" }
|
||||
return "BALZAM · $deviceName · $direction · $values"
|
||||
return "CAN_Bal_2812 · $deviceName · $direction · $values"
|
||||
}
|
||||
}
|
||||
|
||||
/** Shared parser for Balsam_167_periph eCAN frames. */
|
||||
/** Shared CAN_Bal_2812 register protocol used by the BALZAM F2812 firmware. */
|
||||
object BalsamCanProtocol {
|
||||
const val BASE_ID = 0x00BA_0000L
|
||||
const val NODE_COUNT = 13
|
||||
const val REGISTER_BANK_SIZE = 128
|
||||
const val TERMINAL_REQUEST_ID = 0x00BA_001CL
|
||||
const val TERMINAL_RESPONSE_ID = 0x00BA_000CL
|
||||
const val PULT_REQUEST_ID = 0x0074_5019L
|
||||
const val PULT_RESPONSE_ID = 0x0074_5009L
|
||||
|
||||
val nodes: List<BalsamCanNode> by lazy {
|
||||
(1..NODE_COUNT).map { device ->
|
||||
BalsamCanNode(
|
||||
device = device,
|
||||
name = deviceName(device),
|
||||
commandId = BASE_ID + device - 1L,
|
||||
dataId = BASE_ID + 0x0FL + device,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun isLegacyId(canId: Long): Boolean {
|
||||
val relative = (canId and 0x1FFF_FFFFL) - BASE_ID
|
||||
return relative in 0L..12L || relative in 0x10L..0x1CL ||
|
||||
@@ -62,17 +84,44 @@ object BalsamCanProtocol {
|
||||
)
|
||||
}
|
||||
|
||||
/** Encode one CAN_Bal_2812 register write/data frame with one to three words. */
|
||||
fun encodeWrite(
|
||||
device: Int,
|
||||
fromDevice: Boolean,
|
||||
startAddress: Int,
|
||||
values: List<Int>,
|
||||
): BalsamCanWireFrame {
|
||||
require(device in 1..NODE_COUNT) { "Номер узла должен быть в диапазоне 1..$NODE_COUNT" }
|
||||
require(startAddress in 0 until REGISTER_BANK_SIZE) { "Адрес регистра должен быть в диапазоне 0..127" }
|
||||
require(values.size in 1..3) { "Нужно от 1 до 3 слов данных" }
|
||||
require(startAddress + values.size <= REGISTER_BANK_SIZE) { "Запись выходит за границу регистрового пространства" }
|
||||
values.forEach { require(it in 0..0xFFFF) { "Значение должно быть в диапазоне 0..65535" } }
|
||||
|
||||
val mask = when (values.size) {
|
||||
1 -> 4
|
||||
2 -> 6
|
||||
else -> 7
|
||||
}
|
||||
val padded = values + List(3 - values.size) { 0 }
|
||||
val header = (mask shl 13) or startAddress
|
||||
val data = listOf(header, padded[0], padded[1], padded[2])
|
||||
.flatMap { listOf((it ushr 8).toByte(), it.toByte()) }
|
||||
.toByteArray()
|
||||
val node = nodes[device - 1]
|
||||
return BalsamCanWireFrame(if (fromDevice) node.dataId else node.commandId, data)
|
||||
}
|
||||
|
||||
fun summary(canId: Long, data: ByteArray? = null): String =
|
||||
data?.let { decode(canId, it)?.summary() } ?: when (canId) {
|
||||
TERMINAL_REQUEST_ID -> "BALZAM legacy · запрос терминала"
|
||||
TERMINAL_RESPONSE_ID -> "BALZAM legacy · ответ терминалу"
|
||||
PULT_REQUEST_ID -> "BALZAM legacy · данные пульта"
|
||||
PULT_RESPONSE_ID -> "BALZAM legacy · команда пульту"
|
||||
TERMINAL_REQUEST_ID -> "CAN_Bal_2812 · запрос терминала"
|
||||
TERMINAL_RESPONSE_ID -> "CAN_Bal_2812 · ответ терминалу"
|
||||
PULT_REQUEST_ID -> "CAN_Bal_2812 · данные пульта"
|
||||
PULT_RESPONSE_ID -> "CAN_Bal_2812 · команда пульту"
|
||||
in (BASE_ID + 0x10L)..(BASE_ID + 0x1BL) ->
|
||||
"BALZAM legacy · данные · ${deviceName((canId - BASE_ID - 0x0FL).toInt())}"
|
||||
"CAN_Bal_2812 · данные · ${deviceName((canId - BASE_ID - 0x0FL).toInt())}"
|
||||
in BASE_ID..(BASE_ID + 0x0BL) ->
|
||||
"BALZAM legacy · команда · ${deviceName((canId - BASE_ID + 1L).toInt())}"
|
||||
else -> "BALZAM legacy · неизвестный ID"
|
||||
"CAN_Bal_2812 · команда · ${deviceName((canId - BASE_ID + 1L).toInt())}"
|
||||
else -> "CAN_Bal_2812 · неизвестный ID"
|
||||
}
|
||||
|
||||
private fun isRegisterId(canId: Long): Boolean {
|
||||
@@ -95,7 +144,7 @@ object BalsamCanProtocol {
|
||||
private fun u16be(data: ByteArray, offset: Int): Int =
|
||||
((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
|
||||
|
||||
private fun deviceName(device: Int): String = if (NativeSetProtocol.available) {
|
||||
fun deviceName(device: Int): String = if (NativeSetProtocol.available) {
|
||||
NativeSetProtocol.nativeBalsamDeviceName(device)
|
||||
} else listOf(
|
||||
"Трансформатор 1", "Трансформатор 2", "Силовой блок 1", "Силовой блок 2",
|
||||
@@ -103,7 +152,7 @@ object BalsamCanProtocol {
|
||||
"Узел 12", "Терминал",
|
||||
).getOrElse(device - 1) { "Неизвестный узел" }
|
||||
|
||||
private fun registerName(device: Int, address: Int): String =
|
||||
fun registerName(device: Int, address: Int): String =
|
||||
if (NativeSetProtocol.available) NativeSetProtocol.nativeBalsamRegisterName(device, address)
|
||||
else when {
|
||||
device in 1..2 && address in 0x18..0x2B -> "Показания T° ${address - 0x17}"
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package ru.setcorp.setprotocol.periph28335
|
||||
|
||||
import ru.setcorp.setprotocol.NativeSetProtocol
|
||||
|
||||
/** Kotlin UI adapter; all PM35 wire logic and the command catalog live in C99. */
|
||||
object Periph28335Protocol {
|
||||
const val REGISTER_COUNT = 128
|
||||
const val DEFAULT_CONTROLLER = 16
|
||||
const val DEFAULT_BAUD_RATE = 115_200
|
||||
|
||||
val projectCommands: Map<String, List<String>> by lazy {
|
||||
requireNative()
|
||||
buildMap {
|
||||
repeat(NativeSetProtocol.nativePeriph28335ProjectCount()) { project ->
|
||||
val name = requireNotNull(
|
||||
NativeSetProtocol.nativePeriph28335ProjectName(project),
|
||||
) { "Повреждён каталог проектов ПМ35" }
|
||||
put(name, List(17) { command ->
|
||||
requireNotNull(
|
||||
NativeSetProtocol.nativePeriph28335CommandName(project, command),
|
||||
) { "Повреждён каталог команд ПМ35" }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun crc16Modbus(data: ByteArray, initial: Int = 0xFFFF): Int {
|
||||
require(initial == 0xFFFF) {
|
||||
"Произвольное начальное значение CRC не входит в протокол ПМ35"
|
||||
}
|
||||
requireNative()
|
||||
return NativeSetProtocol.nativePeriph28335Crc16(data)
|
||||
}
|
||||
|
||||
fun withCrc(payload: ByteArray): ByteArray {
|
||||
requireNative()
|
||||
return requireNotNull(NativeSetProtocol.nativePeriph28335AppendCrc(payload)) {
|
||||
"SETProtocol отклонил данные ПМ35"
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
requireNative()
|
||||
return requireNotNull(
|
||||
NativeSetProtocol.nativePeriph28335BuildRead(controller, start, count),
|
||||
) { "SETProtocol отклонил запрос чтения ПМ35" }
|
||||
}
|
||||
|
||||
fun buildWriteRegister(controller: Int, address: Int, value: Int): ByteArray {
|
||||
requireRange("Адрес контроллера", controller, 0xFF)
|
||||
requireRange("Адрес регистра", address, REGISTER_COUNT - 1)
|
||||
requireRange("Значение", value, 0xFFFF)
|
||||
requireNative()
|
||||
return requireNotNull(
|
||||
NativeSetProtocol.nativePeriph28335BuildWrite(controller, address, value),
|
||||
) { "SETProtocol отклонил запрос записи ПМ35" }
|
||||
}
|
||||
|
||||
fun buildCommand(controller: Int, commandIndex: Int): ByteArray {
|
||||
require(commandIndex in 0..16) { "Номер команды должен быть в диапазоне 0..16" }
|
||||
requireNative()
|
||||
return requireNotNull(
|
||||
NativeSetProtocol.nativePeriph28335BuildCommand(controller, commandIndex),
|
||||
) { "SETProtocol отклонил команду ПМ35" }
|
||||
}
|
||||
|
||||
fun expectedReadResponseSize(count: Int): Int {
|
||||
require(count in 1..REGISTER_COUNT)
|
||||
requireNative()
|
||||
return NativeSetProtocol.nativePeriph28335ExpectedReadSize(count)
|
||||
}
|
||||
|
||||
fun decodeReadResponse(data: ByteArray, controller: Int, count: Int): List<Int> {
|
||||
val expected = expectedReadResponseSize(count)
|
||||
require(data.size == expected) { "Ожидалось $expected байт, получено ${data.size}" }
|
||||
requireNative()
|
||||
return requireNotNull(
|
||||
NativeSetProtocol.nativePeriph28335DecodeRead(data, controller, count),
|
||||
) { "Повреждён ответ ПМ35: заголовок, длина или CRC" }.toList()
|
||||
}
|
||||
|
||||
fun validateWriteResponse(data: ByteArray, request: ByteArray): Boolean {
|
||||
requireNative()
|
||||
return NativeSetProtocol.nativePeriph28335ValidateWrite(data, request) == 0
|
||||
}
|
||||
|
||||
// Presentation-only conversions stay in the GUI port; they do not define wire bytes.
|
||||
fun bitsLsbFirst(value: Int): List<Boolean> {
|
||||
requireRange("Значение", value, 0xFFFF)
|
||||
return (0 until 16).map { bit -> value and (1 shl bit) != 0 }
|
||||
}
|
||||
|
||||
fun wordFromBits(bits: List<Boolean>): 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 requireNative() {
|
||||
check(NativeSetProtocol.available) { "Нативное ядро SETProtocol недоступно" }
|
||||
}
|
||||
|
||||
private fun requireRange(name: String, value: Int, maximum: Int) {
|
||||
require(value in 0..maximum) { "$name вне диапазона 0..$maximum" }
|
||||
}
|
||||
}
|
||||
@@ -10,37 +10,28 @@ object GuiGasWatch {
|
||||
|
||||
fun request(periodMs: Int, addresses: List<Int>): ByteArray {
|
||||
require(periodMs in 0..65535 && addresses.size <= 64 && addresses.all { it in 0..65535 })
|
||||
if (NativeSetProtocol.available) return requireNotNull(NativeSetProtocol.nativeTrendWatchRequest(periodMs, addresses.toIntArray()))
|
||||
return ByteArray(4 + addresses.size * 2).also { output ->
|
||||
put16(output, 0, periodMs)
|
||||
put16(output, 2, addresses.size)
|
||||
addresses.forEachIndexed { index, address -> put16(output, 4 + index * 2, address) }
|
||||
}
|
||||
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
|
||||
return requireNotNull(NativeSetProtocol.nativeTrendWatchRequest(periodMs, addresses.toIntArray()))
|
||||
}
|
||||
|
||||
fun validateAck(payload: ByteArray, periodMs: Int, count: Int) {
|
||||
require(payload.size == 4 && read16(payload, 0) == periodMs && read16(payload, 2) == count) {
|
||||
require(NativeSetProtocol.available && NativeSetProtocol.nativeTrendWatchAck(payload, periodMs, count)) {
|
||||
"Прибор принял не все адреса GAS. Проверьте карту регистров; отображение по неполной подписке невозможно"
|
||||
}
|
||||
}
|
||||
|
||||
fun values(payload: ByteArray, expectedCount: Int): List<Int> {
|
||||
val values = if (NativeSetProtocol.available) {
|
||||
requireNotNull(NativeSetProtocol.nativeTrendWatchValues(payload)) { "Повреждён GAS_WATCH_DATA" }.toList()
|
||||
} else {
|
||||
require(payload.size >= 6) { "GAS_WATCH_DATA короче заголовка" }
|
||||
val count = read16(payload, 4)
|
||||
require(count <= 64 && payload.size == 6 + count * 2) { "Неверная длина GAS_WATCH_DATA" }
|
||||
List(count) { read16(payload, 6 + it * 2) }
|
||||
}
|
||||
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
|
||||
val values = requireNotNull(NativeSetProtocol.nativeTrendWatchValues(payload)) { "Повреждён GAS_WATCH_DATA" }.toList()
|
||||
require(values.size == expectedCount) { "Число значений GAS не соответствует подписке" }
|
||||
return values
|
||||
}
|
||||
|
||||
private fun read16(data: ByteArray, offset: Int): Int =
|
||||
(data[offset].toInt() and 0xFF) or ((data[offset + 1].toInt() and 0xFF) shl 8)
|
||||
private fun put16(data: ByteArray, offset: Int, value: Int) {
|
||||
data[offset] = value.toByte()
|
||||
data[offset + 1] = (value ushr 8).toByte()
|
||||
data class Sample(val timestampMs: Long, val values: List<Int>)
|
||||
fun decode(payload: ByteArray, expectedCount: Int): Sample {
|
||||
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
|
||||
val decoded = requireNotNull(NativeSetProtocol.nativeTrendWatchDecode(payload)) { "Повреждён GAS_WATCH_DATA" }
|
||||
require(decoded.size == expectedCount + 1) { "Число значений GAS не соответствует подписке" }
|
||||
return Sample(decoded[0], decoded.drop(1).map(Long::toInt))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,3 +32,10 @@ fun plotTickStep(range: Double, pixels: Double): Double = NativePlot.call(5, ran
|
||||
fun plotDelta(a: Double, b: Double, multiplier: Double = 1.0): Double = NativePlot.call(6, a, b, multiplier)[0]
|
||||
fun plotDbDelta(a: Double, b: Double): Double? =
|
||||
runCatching { NativePlot.call(7, a, b)[0] }.getOrNull()
|
||||
|
||||
data class PlotLimits(val xMin: Double, val xMax: Double, val yMin: Double, val yMax: Double) {
|
||||
fun validated(): PlotLimits {
|
||||
val values = NativePlot.call(8, xMin, xMax, yMin, yMax)
|
||||
return PlotLimits(values[0], values[1], values[2], values[3])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TrendSource> = 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)
|
||||
|
||||
@@ -34,6 +34,8 @@ data class TrendSpectrum(
|
||||
val binHz: Double get() = if (size > 0) sampleRate / size else 0.0
|
||||
}
|
||||
|
||||
data class SpectrumPeak(val frequencyHz: Double, val amplitude: Double)
|
||||
|
||||
/** Math is implemented once in C and used unchanged by JNI and ctypes. */
|
||||
object SpectrumAnalyzer {
|
||||
fun analyze(points: List<TrendPoint>, options: SpectrumOptions): TrendSpectrum {
|
||||
@@ -64,4 +66,12 @@ object SpectrumAnalyzer {
|
||||
return TrendSpectrum(output[1].toInt(), output[2], output[3],
|
||||
if (error == null) output.drop(4) else emptyList(), error)
|
||||
}
|
||||
|
||||
fun dominantPeak(spectrum: TrendSpectrum, relativeThreshold: Double = 3.0,
|
||||
absoluteFloor: Double = 1e-6): SpectrumPeak? {
|
||||
if (spectrum.error != null || spectrum.size <= 0 || spectrum.amplitudes.size < 3) return null
|
||||
val result = NativeSetProtocol.nativeSpectrumPeak(spectrum.amplitudes.toDoubleArray(),
|
||||
spectrum.binHz, relativeThreshold, absoluteFloor) ?: return null
|
||||
return result.takeIf { it.size == 2 }?.let { SpectrumPeak(it[0], it[1]) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,273 @@
|
||||
#include "set_trends.h"
|
||||
#include "set_spectrum.h"
|
||||
#include "balsam_can.h"
|
||||
#include "periph28335.h"
|
||||
#include "tms2812.h"
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812Crc16(
|
||||
JNIEnv *env, jobject self, jbyteArray input)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL) return 0;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if ((data == NULL) && (size != 0)) return 0;
|
||||
uint16_t crc = tms2812_crc16((const uint8_t *)data, (size_t)size);
|
||||
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
return (jint)crc;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildUpload(
|
||||
JNIEnv *env, jobject self, jint controller, jlong word_address,
|
||||
jlong byte_count)
|
||||
{
|
||||
(void)self;
|
||||
uint8_t output[TMS2812_UPLOAD_REQUEST_SIZE];
|
||||
if (controller < 0 || controller > 255 || word_address < 0 ||
|
||||
(uint64_t)word_address > UINT32_MAX || byte_count < 1 ||
|
||||
(uint64_t)byte_count > UINT32_MAX) return NULL;
|
||||
size_t written = tms2812_build_upload_request(
|
||||
(uint8_t)controller, (uint32_t)word_address, (uint32_t)byte_count,
|
||||
output, sizeof output);
|
||||
if (written == 0U) return NULL;
|
||||
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
|
||||
if (result != NULL) (*env)->SetByteArrayRegion(
|
||||
env, result, 0, (jsize)written, (const jbyte *)output);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ExpectedUploadSize(
|
||||
JNIEnv *env, jobject self, jlong byte_count)
|
||||
{
|
||||
(void)env; (void)self;
|
||||
if (byte_count < 1 || (uint64_t)byte_count > UINT32_MAX) return 0;
|
||||
size_t size = tms2812_expected_upload_response_size((uint32_t)byte_count);
|
||||
return size <= INT32_MAX ? (jint)size : 0;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ValidateUpload(
|
||||
JNIEnv *env, jobject self, jbyteArray input, jint controller,
|
||||
jint byte_count)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL || controller < 0 || controller > 255 || byte_count < 1)
|
||||
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_upload_response(
|
||||
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
|
||||
(uint32_t)byte_count);
|
||||
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
return status == TMS2812_OK ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812DecodeUpload(
|
||||
JNIEnv *env, jobject self, jbyteArray input, jint controller,
|
||||
jint byte_count)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL || controller < 0 || controller > 255 || byte_count < 1)
|
||||
return NULL;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if (data == NULL) return NULL;
|
||||
uint8_t *output = (uint8_t *)malloc((size_t)byte_count);
|
||||
if (output == NULL) {
|
||||
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
return NULL;
|
||||
}
|
||||
int status = tms2812_decode_upload_response(
|
||||
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
|
||||
(uint32_t)byte_count, output, (size_t)byte_count);
|
||||
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
if (status != TMS2812_OK) { free(output); return NULL; }
|
||||
jbyteArray result = (*env)->NewByteArray(env, byte_count);
|
||||
if (result != NULL) (*env)->SetByteArrayRegion(
|
||||
env, result, 0, byte_count, (const jbyte *)output);
|
||||
free(output);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335Crc16(
|
||||
JNIEnv *env, jobject self, jbyteArray input)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL) return 0;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if ((data == NULL) && (size != 0)) return 0;
|
||||
uint16_t crc = periph28335_crc16_modbus((const uint8_t *)data, (size_t)size);
|
||||
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
return (jint)crc;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335AppendCrc(
|
||||
JNIEnv *env, jobject self, jbyteArray input)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL) return NULL;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if ((data == NULL) && (size != 0)) return NULL;
|
||||
uint8_t *output = (uint8_t *)malloc((size_t)size + 2U);
|
||||
if (output == NULL) {
|
||||
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
return NULL;
|
||||
}
|
||||
size_t written = periph28335_append_crc(
|
||||
(const uint8_t *)data, (size_t)size, output, (size_t)size + 2U);
|
||||
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
if (written == 0U) { free(output); return NULL; }
|
||||
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
|
||||
if (result != NULL) (*env)->SetByteArrayRegion(
|
||||
env, result, 0, (jsize)written, (const jbyte *)output);
|
||||
free(output);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildRead(
|
||||
JNIEnv *env, jobject self, jint controller, jint start, jint count)
|
||||
{
|
||||
(void)self;
|
||||
uint8_t output[PERIPH28335_REQUEST_SIZE];
|
||||
if (controller < 0 || controller > 255 || start < 0 || start > 65535 ||
|
||||
count < 0 || count > 65535) return NULL;
|
||||
size_t written = periph28335_build_read_registers(
|
||||
(uint8_t)controller, (uint16_t)start, (uint16_t)count,
|
||||
output, sizeof output);
|
||||
if (written == 0U) return NULL;
|
||||
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
|
||||
if (result != NULL) (*env)->SetByteArrayRegion(
|
||||
env, result, 0, (jsize)written, (const jbyte *)output);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildWrite(
|
||||
JNIEnv *env, jobject self, jint controller, jint address, jint value)
|
||||
{
|
||||
(void)self;
|
||||
uint8_t output[PERIPH28335_REQUEST_SIZE];
|
||||
if (controller < 0 || controller > 255 || address < 0 || address > 65535 ||
|
||||
value < 0 || value > 65535) return NULL;
|
||||
size_t written = periph28335_build_write_register(
|
||||
(uint8_t)controller, (uint16_t)address, (uint16_t)value,
|
||||
output, sizeof output);
|
||||
if (written == 0U) return NULL;
|
||||
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
|
||||
if (result != NULL) (*env)->SetByteArrayRegion(
|
||||
env, result, 0, (jsize)written, (const jbyte *)output);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildCommand(
|
||||
JNIEnv *env, jobject self, jint controller, jint command_index)
|
||||
{
|
||||
(void)self;
|
||||
uint8_t output[PERIPH28335_REQUEST_SIZE];
|
||||
if (controller < 0 || controller > 255 || command_index < 0 || command_index > 255) return NULL;
|
||||
size_t written = periph28335_build_command(
|
||||
(uint8_t)controller, (uint8_t)command_index, output, sizeof output);
|
||||
if (written == 0U) return NULL;
|
||||
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
|
||||
if (result != NULL) (*env)->SetByteArrayRegion(
|
||||
env, result, 0, (jsize)written, (const jbyte *)output);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ExpectedReadSize(
|
||||
JNIEnv *env, jobject self, jint count)
|
||||
{
|
||||
(void)env; (void)self;
|
||||
if (count < 0 || count > 65535) return 0;
|
||||
return (jint)periph28335_expected_read_response_size((uint16_t)count);
|
||||
}
|
||||
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335DecodeRead(
|
||||
JNIEnv *env, jobject self, jbyteArray input, jint controller, jint count)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL || controller < 0 || controller > 255 ||
|
||||
count < 1 || count > (jint)PERIPH28335_REGISTER_COUNT) return NULL;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if ((data == NULL) && (size != 0)) return NULL;
|
||||
uint16_t words[PERIPH28335_REGISTER_COUNT];
|
||||
int status = periph28335_decode_read_response(
|
||||
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
|
||||
(uint16_t)count, words, PERIPH28335_REGISTER_COUNT);
|
||||
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
if (status != PERIPH28335_OK) return NULL;
|
||||
jint values[PERIPH28335_REGISTER_COUNT];
|
||||
for (jint index = 0; index < count; ++index) values[index] = words[index];
|
||||
jintArray result = (*env)->NewIntArray(env, count);
|
||||
if (result != NULL) (*env)->SetIntArrayRegion(env, result, 0, count, values);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ValidateWrite(
|
||||
JNIEnv *env, jobject self, jbyteArray response, jbyteArray request)
|
||||
{
|
||||
(void)self;
|
||||
if (response == NULL || request == NULL) return PERIPH28335_ERROR_ARGUMENT;
|
||||
jsize response_size = (*env)->GetArrayLength(env, response);
|
||||
jsize request_size = (*env)->GetArrayLength(env, request);
|
||||
jbyte *response_data = (*env)->GetByteArrayElements(env, response, NULL);
|
||||
jbyte *request_data = (*env)->GetByteArrayElements(env, request, NULL);
|
||||
if (response_data == NULL || request_data == NULL) {
|
||||
if (response_data != NULL) (*env)->ReleaseByteArrayElements(env, response, response_data, JNI_ABORT);
|
||||
if (request_data != NULL) (*env)->ReleaseByteArrayElements(env, request, request_data, JNI_ABORT);
|
||||
return PERIPH28335_ERROR_ARGUMENT;
|
||||
}
|
||||
int status = periph28335_validate_write_response(
|
||||
(const uint8_t *)response_data, (size_t)response_size,
|
||||
(const uint8_t *)request_data, (size_t)request_size);
|
||||
(*env)->ReleaseByteArrayElements(env, response, response_data, JNI_ABORT);
|
||||
(*env)->ReleaseByteArrayElements(env, request, request_data, JNI_ABORT);
|
||||
return status;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ProjectCount(
|
||||
JNIEnv *env, jobject self)
|
||||
{
|
||||
(void)env; (void)self;
|
||||
return (jint)periph28335_project_count();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ProjectName(
|
||||
JNIEnv *env, jobject self, jint project_index)
|
||||
{
|
||||
(void)self;
|
||||
const char *name = project_index >= 0
|
||||
? periph28335_project_name((size_t)project_index) : NULL;
|
||||
return name != NULL ? (*env)->NewStringUTF(env, name) : NULL;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335CommandName(
|
||||
JNIEnv *env, jobject self, jint project_index, jint command_index)
|
||||
{
|
||||
(void)self;
|
||||
const char *name = project_index >= 0 && command_index >= 0
|
||||
? periph28335_command_name((size_t)project_index, (size_t)command_index)
|
||||
: NULL;
|
||||
return name != NULL ? (*env)->NewStringUTF(env, name) : NULL;
|
||||
}
|
||||
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeBalsamDecode(
|
||||
@@ -100,6 +367,30 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendCanValue(
|
||||
(uint8_t)flags, (const uint8_t *)data, (size_t)size);
|
||||
}
|
||||
|
||||
JNIEXPORT jdoubleArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeSpectrumPeak(
|
||||
JNIEnv *env, jobject self, jdoubleArray input, jdouble bin_hz,
|
||||
jdouble relative_threshold, jdouble absolute_floor)
|
||||
{
|
||||
(void)self;
|
||||
jsize count = input == NULL ? 0 : (*env)->GetArrayLength(env, input);
|
||||
if (count < 3 || count > (jsize)(SET_SPECTRUM_MAX / 2U + 1U)) return NULL;
|
||||
double *buffer = (double *)malloc(sizeof(double) * (size_t)(count * 2 - 1));
|
||||
if (buffer == NULL) return NULL;
|
||||
double *amplitudes = buffer, *scratch = buffer + count, peak[2];
|
||||
(*env)->GetDoubleArrayRegion(env, input, 0, count, amplitudes);
|
||||
int status = (*env)->ExceptionCheck(env) ? -1 : set_spectrum_dominant_peak(
|
||||
amplitudes, (size_t)count, bin_hz, relative_threshold, absolute_floor,
|
||||
scratch, (size_t)count - 1U, peak, 2U);
|
||||
jdoubleArray result = NULL;
|
||||
if (status >= 0) {
|
||||
result = (*env)->NewDoubleArray(env, status == 1 ? 2 : 0);
|
||||
if (result != NULL && status == 1) (*env)->SetDoubleArrayRegion(env, result, 0, 2, peak);
|
||||
}
|
||||
free(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchRequest(
|
||||
JNIEnv *env, jobject self, jint period, jintArray input)
|
||||
@@ -122,6 +413,20 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchRequest(
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchAck(
|
||||
JNIEnv *env, jobject self, jbyteArray input, jint period, jint count)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL || period < 0 || period > 65535 || count < 0) return JNI_FALSE;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
if (size != 4) return JNI_FALSE;
|
||||
jbyte payload[4];
|
||||
(*env)->GetByteArrayRegion(env, input, 0, size, payload);
|
||||
return set_trend_watch_ack((const uint8_t *)payload, (size_t)size,
|
||||
(uint16_t)period, (size_t)count) ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchValues(
|
||||
JNIEnv *env, jobject self, jbyteArray input)
|
||||
@@ -141,6 +446,28 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchValues(
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jlongArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchDecode(
|
||||
JNIEnv *env, jobject self, jbyteArray input)
|
||||
{
|
||||
(void)self;
|
||||
jsize size = input == NULL ? 0 : (*env)->GetArrayLength(env, input);
|
||||
if (size < 6 || size > (jsize)(6U + 2U * SET_TREND_WATCH_MAX)) return NULL;
|
||||
jbyte payload[6U + 2U * SET_TREND_WATCH_MAX];
|
||||
uint16_t words[SET_TREND_WATCH_MAX];
|
||||
uint32_t timestamp = 0U;
|
||||
jlong values[1U + SET_TREND_WATCH_MAX];
|
||||
(*env)->GetByteArrayRegion(env, input, 0, size, payload);
|
||||
int count = set_trend_watch_decode((const uint8_t *)payload, (size_t)size,
|
||||
×tamp, words, SET_TREND_WATCH_MAX);
|
||||
if (count < 0) return NULL;
|
||||
values[0] = (jlong)timestamp;
|
||||
for (int i = 0; i < count; ++i) values[i + 1] = (jlong)words[i];
|
||||
jlongArray result = (*env)->NewLongArray(env, count + 1);
|
||||
if (result != NULL) (*env)->SetLongArrayRegion(env, result, 0, count + 1, values);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t *storage;
|
||||
size_t storage_size;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package ru.setcorp.setflash.core
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ProtoCanCatalogTest {
|
||||
@Test
|
||||
fun bodyLayoutsMatchSetGuiBuilder() {
|
||||
assertEquals(0x1234, ProtoCanBody.broadcast(0x123, 0x4))
|
||||
assertEquals(0x2001, ProtoCanBody.typed(ProtoCanAnalogType.U.code, 1))
|
||||
assertEquals(0x1234, ProtoCanBody.modbus(0x123, 4))
|
||||
assertEquals(0xABCD, ProtoCanBody.error(0xAB, 0xCD))
|
||||
assertEquals(0xFF01, ProtoCanBody.settings(0xFF, 0x01))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun messageRegistryCoversAllFourBitValues() {
|
||||
assertEquals((0..0xF).toList(), ProtoCanMessageType.entries.map { it.code })
|
||||
assertEquals("Settings", ProtoCanId.parse(0x17FE1234).messageTypeName)
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,22 @@ class BalsamCanProtocolTest {
|
||||
assertEquals("Показания T° 1", frame.registers.first().displayName)
|
||||
assertTrue(frame.summary().contains("Трансформатор 1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encodesControllerWriteForBal2812RegisterSpace() {
|
||||
val wire = BalsamCanProtocol.encodeWrite(
|
||||
device = 3,
|
||||
fromDevice = false,
|
||||
startAddress = 0x28,
|
||||
values = listOf(0x1234, 0xFEDC),
|
||||
)
|
||||
assertEquals(0x00BA_0002L, wire.canId)
|
||||
assertEquals(
|
||||
listOf(0xC0, 0x28, 0x12, 0x34, 0xFE, 0xDC, 0x00, 0x00),
|
||||
wire.data.map { it.toInt() and 0xFF },
|
||||
)
|
||||
val decoded = requireNotNull(BalsamCanProtocol.decode(wire.canId, wire.data))
|
||||
assertEquals(false, decoded.fromDevice)
|
||||
assertEquals(listOf(0x1234, 0xFEDC), decoded.registers.map { it.value })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -36,4 +36,12 @@ class PlotViewportTest {
|
||||
}
|
||||
SpectrumOptions().validate()
|
||||
}
|
||||
@Test fun absoluteLimitsAndDominantPeakUseNativeCore() {
|
||||
assertEquals(PlotLimits(0.0, 500.0, -1.0, 1.0),
|
||||
PlotLimits(0.0, 500.0, -1.0, 1.0).validated())
|
||||
assertTrue(runCatching { PlotLimits(1.0, 1.0, -1.0, 1.0).validated() }.isFailure)
|
||||
val spectrum = TrendSpectrum(12, 60.0, 0.0,
|
||||
listOf(10.0, .01, .02, .8, .03, .4, .02))
|
||||
assertEquals(15.0, SpectrumAnalyzer.dominantPeak(spectrum)!!.frequencyHz, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
23
c/set-protocol/ports/tms320f2812/README.md
Normal file
23
c/set-protocol/ports/tms320f2812/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# SETProtocol v2 firmware port for TMS320F2812
|
||||
|
||||
`setp_tms2812_boot.c` implements the shared SETP v2 CAN firmware service for
|
||||
F2812 projects. It owns CAN reassembly, request/response framing, firmware state,
|
||||
idempotent blocks, CRC32 and SHA-256 verification. It does not include TI or
|
||||
board headers.
|
||||
|
||||
The target application provides callbacks for classic-CAN transmission, Flash
|
||||
erase/program/read, optional signature authorization, and reset. Receive ISR
|
||||
code must only copy frames into a queue; call `setp_tms2812_boot_process()` from
|
||||
task context.
|
||||
|
||||
Add these sources to a CCS project:
|
||||
|
||||
- `c/set-protocol/src/set_protocol.c`
|
||||
- `c/set-protocol/src/set_can.c`
|
||||
- `c/set-protocol/src/set_firmware.c`
|
||||
- `c/set-protocol/ports/tms320f2812/setp_tms2812_boot.c`
|
||||
|
||||
Add `c/set-protocol/include` and this directory to include paths. The port uses
|
||||
extended 29-bit SETP CAN identifiers and supports one firmware slot. A single
|
||||
slot has no power-loss rollback; production hardware should provide staging or
|
||||
A/B storage.
|
||||
407
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.c
Normal file
407
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.c
Normal file
@@ -0,0 +1,407 @@
|
||||
#include "setp_tms2812_boot.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define SHA256_BLOCK_SIZE 64U
|
||||
|
||||
typedef struct {
|
||||
uint32_t state[8];
|
||||
uint64_t bit_count;
|
||||
uint8_t block[SHA256_BLOCK_SIZE];
|
||||
uint16_t block_length;
|
||||
} sha256_ctx_t;
|
||||
|
||||
static uint32_t rotr32(uint32_t value, uint8_t bits)
|
||||
{
|
||||
return (value >> bits) | (value << (32U - bits));
|
||||
}
|
||||
|
||||
static void sha256_transform(sha256_ctx_t *ctx, const uint8_t *block)
|
||||
{
|
||||
static const uint32_t k[64] = {
|
||||
0x428A2F98UL, 0x71374491UL, 0xB5C0FBCFUL, 0xE9B5DBA5UL,
|
||||
0x3956C25BUL, 0x59F111F1UL, 0x923F82A4UL, 0xAB1C5ED5UL,
|
||||
0xD807AA98UL, 0x12835B01UL, 0x243185BEUL, 0x550C7DC3UL,
|
||||
0x72BE5D74UL, 0x80DEB1FEUL, 0x9BDC06A7UL, 0xC19BF174UL,
|
||||
0xE49B69C1UL, 0xEFBE4786UL, 0x0FC19DC6UL, 0x240CA1CCUL,
|
||||
0x2DE92C6FUL, 0x4A7484AAUL, 0x5CB0A9DCUL, 0x76F988DAUL,
|
||||
0x983E5152UL, 0xA831C66DUL, 0xB00327C8UL, 0xBF597FC7UL,
|
||||
0xC6E00BF3UL, 0xD5A79147UL, 0x06CA6351UL, 0x14292967UL,
|
||||
0x27B70A85UL, 0x2E1B2138UL, 0x4D2C6DFCUL, 0x53380D13UL,
|
||||
0x650A7354UL, 0x766A0ABBUL, 0x81C2C92EUL, 0x92722C85UL,
|
||||
0xA2BFE8A1UL, 0xA81A664BUL, 0xC24B8B70UL, 0xC76C51A3UL,
|
||||
0xD192E819UL, 0xD6990624UL, 0xF40E3585UL, 0x106AA070UL,
|
||||
0x19A4C116UL, 0x1E376C08UL, 0x2748774CUL, 0x34B0BCB5UL,
|
||||
0x391C0CB3UL, 0x4ED8AA4AUL, 0x5B9CCA4FUL, 0x682E6FF3UL,
|
||||
0x748F82EEUL, 0x78A5636FUL, 0x84C87814UL, 0x8CC70208UL,
|
||||
0x90BEFFFAUL, 0xA4506CEBUL, 0xBEF9A3F7UL, 0xC67178F2UL
|
||||
};
|
||||
uint32_t w[64];
|
||||
uint32_t a, b, c, d, e, f, g, h, s0, s1, ch, maj, temp1, temp2;
|
||||
uint16_t i;
|
||||
for (i = 0U; i < 16U; i++) {
|
||||
uint16_t p = (uint16_t)(i * 4U);
|
||||
w[i] = ((uint32_t)block[p] << 24U)
|
||||
| ((uint32_t)block[p + 1U] << 16U)
|
||||
| ((uint32_t)block[p + 2U] << 8U)
|
||||
| (uint32_t)block[p + 3U];
|
||||
}
|
||||
for (i = 16U; i < 64U; i++) {
|
||||
s0 = rotr32(w[i - 15U], 7U) ^ rotr32(w[i - 15U], 18U)
|
||||
^ (w[i - 15U] >> 3U);
|
||||
s1 = rotr32(w[i - 2U], 17U) ^ rotr32(w[i - 2U], 19U)
|
||||
^ (w[i - 2U] >> 10U);
|
||||
w[i] = w[i - 16U] + s0 + w[i - 7U] + s1;
|
||||
}
|
||||
a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3];
|
||||
e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7];
|
||||
for (i = 0U; i < 64U; i++) {
|
||||
s1 = rotr32(e, 6U) ^ rotr32(e, 11U) ^ rotr32(e, 25U);
|
||||
ch = (e & f) ^ ((~e) & g);
|
||||
temp1 = h + s1 + ch + k[i] + w[i];
|
||||
s0 = rotr32(a, 2U) ^ rotr32(a, 13U) ^ rotr32(a, 22U);
|
||||
maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
temp2 = s0 + maj;
|
||||
h = g; g = f; f = e; e = d + temp1;
|
||||
d = c; c = b; b = a; a = temp1 + temp2;
|
||||
}
|
||||
ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
|
||||
ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
|
||||
}
|
||||
|
||||
static void sha256_init(sha256_ctx_t *ctx)
|
||||
{
|
||||
static const uint32_t initial[8] = {
|
||||
0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
|
||||
0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
|
||||
};
|
||||
(void)memcpy(ctx->state, initial, sizeof(initial));
|
||||
ctx->bit_count = 0U;
|
||||
ctx->block_length = 0U;
|
||||
}
|
||||
|
||||
static void sha256_update(sha256_ctx_t *ctx, const uint8_t *data, uint16_t length)
|
||||
{
|
||||
uint16_t i;
|
||||
for (i = 0U; i < length; i++) {
|
||||
ctx->block[ctx->block_length++] = data[i];
|
||||
ctx->bit_count += 8U;
|
||||
if (ctx->block_length == SHA256_BLOCK_SIZE) {
|
||||
sha256_transform(ctx, ctx->block);
|
||||
ctx->block_length = 0U;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sha256_finish(sha256_ctx_t *ctx, uint8_t digest[SETP_SHA256_SIZE])
|
||||
{
|
||||
uint16_t i;
|
||||
uint64_t bits = ctx->bit_count;
|
||||
ctx->block[ctx->block_length++] = 0x80U;
|
||||
if (ctx->block_length > 56U) {
|
||||
while (ctx->block_length < SHA256_BLOCK_SIZE) ctx->block[ctx->block_length++] = 0U;
|
||||
sha256_transform(ctx, ctx->block);
|
||||
ctx->block_length = 0U;
|
||||
}
|
||||
while (ctx->block_length < 56U) ctx->block[ctx->block_length++] = 0U;
|
||||
for (i = 0U; i < 8U; i++) {
|
||||
ctx->block[63U - i] = (uint8_t)(bits & 0xFFU);
|
||||
bits >>= 8U;
|
||||
}
|
||||
sha256_transform(ctx, ctx->block);
|
||||
for (i = 0U; i < 8U; i++) {
|
||||
digest[i * 4U] = (uint8_t)(ctx->state[i] >> 24U);
|
||||
digest[i * 4U + 1U] = (uint8_t)(ctx->state[i] >> 16U);
|
||||
digest[i * 4U + 2U] = (uint8_t)(ctx->state[i] >> 8U);
|
||||
digest[i * 4U + 3U] = (uint8_t)ctx->state[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void boot_status(const setp_tms2812_boot_t *boot, setp_fw_status_t *status)
|
||||
{
|
||||
status->state = boot->state;
|
||||
status->active_slot = boot->config.active_slot;
|
||||
status->max_block_size = boot->config.max_block_size;
|
||||
status->next_offset = boot->next_offset;
|
||||
status->image_size = boot->manifest.image_size;
|
||||
status->last_error = boot->last_error;
|
||||
status->flags = 0U;
|
||||
}
|
||||
|
||||
static bool boot_send_response(setp_tms2812_boot_t *boot,
|
||||
const setp_frame_t *request,
|
||||
const setp_can_id_t *request_id,
|
||||
uint16_t status,
|
||||
const uint8_t *body, uint16_t body_length)
|
||||
{
|
||||
setp_frame_t response;
|
||||
setp_can_id_t response_id;
|
||||
size_t packet_length;
|
||||
if ((uint32_t)body_length + 2U > sizeof(boot->response_payload)) return false;
|
||||
setp_put_u16(boot->response_payload, status);
|
||||
if (body_length != 0U) (void)memcpy(&boot->response_payload[2], body, body_length);
|
||||
response.flags = SETP_FLAG_RESPONSE;
|
||||
if (status != SETP_STATUS_OK) response.flags |= SETP_FLAG_ERROR;
|
||||
if ((request->flags & SETP_FLAG_PRIORITY) != 0U) response.flags |= SETP_FLAG_PRIORITY;
|
||||
response.message_type = request->message_type;
|
||||
response.source = boot->config.node_id;
|
||||
response.destination = request->source;
|
||||
response.sequence = request->sequence;
|
||||
response.payload_length = (uint16_t)(body_length + 2U);
|
||||
response.payload = boot->response_payload;
|
||||
packet_length = setp_frame_encode(&response, boot->response_packet,
|
||||
sizeof(boot->response_packet));
|
||||
if (packet_length == 0U) return false;
|
||||
response_id.destination = request_id->source;
|
||||
response_id.source = boot->config.node_id;
|
||||
response_id.priority = (response.flags & SETP_FLAG_PRIORITY) != 0U ? 1U : 0U;
|
||||
response_id.channel = request_id->channel;
|
||||
return setp_can_segment(boot->response_packet, (uint16_t)packet_length,
|
||||
setp_can_id_pack(&response_id), boot->port.send_can,
|
||||
boot->port_user);
|
||||
}
|
||||
|
||||
static uint16_t boot_verify_image(setp_tms2812_boot_t *boot)
|
||||
{
|
||||
uint8_t data[SETP_TMS2812_MAX_BLOCK_SIZE];
|
||||
uint8_t digest[SETP_SHA256_SIZE];
|
||||
sha256_ctx_t sha;
|
||||
uint32_t crc = 0xFFFFFFFFUL;
|
||||
uint32_t offset = 0U;
|
||||
uint16_t i;
|
||||
sha256_init(&sha);
|
||||
while (offset < boot->manifest.image_size) {
|
||||
uint32_t remaining = boot->manifest.image_size - offset;
|
||||
uint16_t length = remaining > sizeof(data) ? (uint16_t)sizeof(data) : (uint16_t)remaining;
|
||||
if (!boot->port.read_image(boot->port_user, offset, data, length)) {
|
||||
return SETP_STATUS_INTERNAL;
|
||||
}
|
||||
sha256_update(&sha, data, length);
|
||||
for (i = 0U; i < length; i++) {
|
||||
uint8_t bit;
|
||||
crc ^= data[i];
|
||||
for (bit = 0U; bit < 8U; bit++)
|
||||
crc = (crc >> 1U) ^ (((crc & 1U) != 0U) ? 0xEDB88320UL : 0U);
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
sha256_finish(&sha, digest);
|
||||
crc ^= 0xFFFFFFFFUL;
|
||||
if ((crc != boot->manifest.image_crc32)
|
||||
|| (memcmp(digest, boot->manifest.sha256, SETP_SHA256_SIZE) != 0)) {
|
||||
return SETP_STATUS_VERIFY_FAILED;
|
||||
}
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
|
||||
static uint16_t boot_fw_begin(setp_tms2812_boot_t *boot, const setp_frame_t *request)
|
||||
{
|
||||
setp_fw_begin_t value;
|
||||
bool same_manifest;
|
||||
if (!setp_fw_begin_decode(request->payload, request->payload_length, &value))
|
||||
return SETP_STATUS_INVALID_LENGTH;
|
||||
if ((value.image_size == 0U) || (value.image_size > boot->config.max_image_size)
|
||||
|| (value.slot != boot->config.active_slot)
|
||||
|| ((value.base_address != 0U)
|
||||
&& (value.base_address != boot->config.app_base_address))
|
||||
|| (value.block_size == 0U)
|
||||
|| (value.block_size > boot->config.max_block_size))
|
||||
return SETP_STATUS_INVALID_ARGUMENT;
|
||||
if (((value.flags & SETP_FW_FLAG_SIGNED) != 0U) || boot->config.require_signature) {
|
||||
if ((boot->port.authorize == NULL)
|
||||
|| !boot->port.authorize(boot->port_user, &value))
|
||||
return SETP_STATUS_AUTH_FAILED;
|
||||
}
|
||||
same_manifest = boot->state == SETP_FW_RECEIVING
|
||||
&& boot->manifest.image_size == value.image_size
|
||||
&& boot->manifest.image_crc32 == value.image_crc32
|
||||
&& boot->manifest.image_version == value.image_version
|
||||
&& memcmp(boot->manifest.sha256, value.sha256, SETP_SHA256_SIZE) == 0;
|
||||
if (same_manifest && ((value.flags & SETP_FW_FLAG_RESUME) != 0U)) return SETP_STATUS_OK;
|
||||
if (!boot->port.erase_image(boot->port_user, value.image_size))
|
||||
return SETP_STATUS_INTERNAL;
|
||||
boot->manifest = value;
|
||||
boot->manifest.signature = NULL;
|
||||
boot->manifest.signature_length = 0U;
|
||||
boot->next_offset = 0U;
|
||||
boot->state = SETP_FW_RECEIVING;
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
|
||||
static uint16_t boot_fw_data(setp_tms2812_boot_t *boot, const setp_frame_t *request)
|
||||
{
|
||||
setp_fw_data_t value;
|
||||
uint8_t current[SETP_TMS2812_MAX_BLOCK_SIZE];
|
||||
if (boot->state != SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
|
||||
if (!setp_fw_data_decode(request->payload, request->payload_length, &value))
|
||||
return SETP_STATUS_CRC;
|
||||
if ((value.data_length > boot->manifest.block_size)
|
||||
|| (value.data_length > boot->config.max_block_size)
|
||||
|| (value.offset > boot->manifest.image_size)
|
||||
|| ((uint32_t)value.data_length > boot->manifest.image_size - value.offset))
|
||||
return SETP_STATUS_INVALID_ARGUMENT;
|
||||
if (value.offset < boot->next_offset) {
|
||||
if ((value.offset + value.data_length > boot->next_offset)
|
||||
|| !boot->port.read_image(boot->port_user, value.offset,
|
||||
current, value.data_length)
|
||||
|| memcmp(current, value.data, value.data_length) != 0)
|
||||
return SETP_STATUS_SEQUENCE;
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
if (value.offset != boot->next_offset) return SETP_STATUS_SEQUENCE;
|
||||
if (!boot->port.write_image(boot->port_user, value.offset,
|
||||
value.data, value.data_length))
|
||||
return SETP_STATUS_INTERNAL;
|
||||
boot->next_offset += value.data_length;
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
|
||||
static uint16_t boot_fw_end(setp_tms2812_boot_t *boot, const setp_frame_t *request)
|
||||
{
|
||||
setp_fw_end_t value;
|
||||
uint16_t status;
|
||||
if (boot->state != SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
|
||||
if (!setp_fw_end_decode(request->payload, request->payload_length, &value))
|
||||
return SETP_STATUS_INVALID_LENGTH;
|
||||
if ((boot->next_offset != boot->manifest.image_size)
|
||||
|| (value.image_size != boot->manifest.image_size)
|
||||
|| (value.image_crc32 != boot->manifest.image_crc32)
|
||||
|| (memcmp(value.sha256, boot->manifest.sha256, SETP_SHA256_SIZE) != 0))
|
||||
return SETP_STATUS_VERIFY_FAILED;
|
||||
boot->state = SETP_FW_VERIFYING;
|
||||
status = boot_verify_image(boot);
|
||||
boot->state = status == SETP_STATUS_OK ? SETP_FW_READY : SETP_FW_FAILED;
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool boot_dispatch(setp_tms2812_boot_t *boot, const setp_frame_t *request,
|
||||
const setp_can_id_t *request_id, uint32_t now_ms)
|
||||
{
|
||||
uint8_t body[SETP_TMS2812_RESPONSE_PAYLOAD_SIZE - 2U];
|
||||
uint16_t body_length = 0U;
|
||||
uint16_t status = SETP_STATUS_OK;
|
||||
uint8_t reboot = 0U;
|
||||
setp_fw_status_t fw_status;
|
||||
if ((request->flags & (SETP_FLAG_RESPONSE | SETP_FLAG_EVENT)) != 0U) return false;
|
||||
if ((request->source > 0xFFU) || (request->destination != boot->config.node_id)) return false;
|
||||
switch (request->message_type) {
|
||||
case SETP_MSG_PING:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else { setp_put_u32(body, now_ms); body_length = 4U; }
|
||||
break;
|
||||
case SETP_MSG_DEVICE_INFO:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else {
|
||||
setp_device_info_t info;
|
||||
info.schema_version = SETP_DEVICE_INFO_SCHEMA_VERSION;
|
||||
info.device_class = boot->config.device_class;
|
||||
info.hardware_version = boot->config.hardware_version;
|
||||
info.firmware_version = boot->config.firmware_version;
|
||||
info.dictionary_version = boot->config.dictionary_version;
|
||||
info.serial_number = boot->config.serial_number;
|
||||
info.model_length = boot->config.model_length;
|
||||
info.model = boot->config.model;
|
||||
body_length = (uint16_t)setp_device_info_encode(&info, body, sizeof(body));
|
||||
if (body_length == 0U) status = SETP_STATUS_INTERNAL;
|
||||
}
|
||||
break;
|
||||
case SETP_MSG_CAPABILITIES:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else {
|
||||
setp_capabilities_t caps;
|
||||
caps.schema_version = SETP_CAPABILITIES_SCHEMA_VERSION;
|
||||
caps.max_payload = (uint16_t)(SETP_FW_DATA_HEADER_SIZE + boot->config.max_block_size);
|
||||
caps.interface_mask = SETP_IFACE_MASK(SETP_IFACE_CAN);
|
||||
caps.feature_flags = SETP_FEATURE_FIRMWARE;
|
||||
caps.max_read_items = 0U; caps.max_write_items = 0U;
|
||||
caps.max_subscriptions = 0U; caps.max_publish_items = 0U;
|
||||
body_length = (uint16_t)setp_capabilities_encode(&caps, body, sizeof(body));
|
||||
if (body_length == 0U) status = SETP_STATUS_INTERNAL;
|
||||
}
|
||||
break;
|
||||
case SETP_MSG_FW_BEGIN:
|
||||
status = boot_fw_begin(boot, request);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_DATA:
|
||||
status = boot_fw_data(boot, request);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_END:
|
||||
status = boot_fw_end(boot, request);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_ABORT:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else setp_tms2812_boot_abort(boot);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_STATUS:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else {
|
||||
boot_status(boot, &fw_status);
|
||||
body_length = (uint16_t)setp_fw_status_encode(&fw_status, body, sizeof(body));
|
||||
}
|
||||
break;
|
||||
case SETP_MSG_FW_ACTIVATE:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else if (boot->state != SETP_FW_READY) status = SETP_STATUS_WRONG_STATE;
|
||||
else { boot->state = SETP_FW_ACTIVE; reboot = 1U; }
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
default:
|
||||
status = SETP_STATUS_UNSUPPORTED;
|
||||
break;
|
||||
}
|
||||
boot->last_error = status == SETP_STATUS_OK ? 0U : status;
|
||||
if (!boot_send_response(boot, request, request_id, status, body, body_length)) return false;
|
||||
if (reboot != 0U) boot->port.reboot(boot->port_user);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setp_tms2812_boot_init(setp_tms2812_boot_t *boot,
|
||||
const setp_tms2812_boot_config_t *config,
|
||||
const setp_tms2812_boot_port_t *port,
|
||||
void *port_user)
|
||||
{
|
||||
if ((boot == NULL) || (config == NULL) || (port == NULL)
|
||||
|| (config->model == NULL) || (config->model_length > SETP_DEVICE_MODEL_MAX)
|
||||
|| (config->max_image_size == 0U) || (config->max_block_size == 0U)
|
||||
|| (config->max_block_size > SETP_TMS2812_MAX_BLOCK_SIZE)
|
||||
|| (port->send_can == NULL) || (port->erase_image == NULL)
|
||||
|| (port->write_image == NULL) || (port->read_image == NULL)
|
||||
|| (port->reboot == NULL)) return false;
|
||||
(void)memset(boot, 0, sizeof(*boot));
|
||||
boot->config = *config;
|
||||
boot->port = *port;
|
||||
boot->port_user = port_user;
|
||||
boot->state = SETP_FW_IDLE;
|
||||
setp_can_rx_init(&boot->rx);
|
||||
return true;
|
||||
}
|
||||
|
||||
void setp_tms2812_boot_abort(setp_tms2812_boot_t *boot)
|
||||
{
|
||||
if (boot == NULL) return;
|
||||
boot->state = SETP_FW_IDLE;
|
||||
boot->next_offset = 0U;
|
||||
boot->last_error = 0U;
|
||||
(void)memset(&boot->manifest, 0, sizeof(boot->manifest));
|
||||
}
|
||||
|
||||
bool setp_tms2812_boot_process(setp_tms2812_boot_t *boot,
|
||||
const setp_can_frame_t *frame,
|
||||
uint32_t now_ms)
|
||||
{
|
||||
setp_can_packet_t packet;
|
||||
setp_can_rx_result_t result;
|
||||
setp_can_id_t can_id;
|
||||
setp_frame_t request;
|
||||
if ((boot == NULL) || (frame == NULL)) return false;
|
||||
result = setp_can_rx_feed(&boot->rx, frame, now_ms, &packet);
|
||||
if (result != SETP_CAN_RX_COMPLETE) return result == SETP_CAN_RX_NONE;
|
||||
if (!setp_can_id_unpack(packet.can_id, &can_id)
|
||||
|| (can_id.destination != boot->config.node_id)
|
||||
|| !setp_frame_decode_datagram(packet.data, packet.length, &request)) return false;
|
||||
return boot_dispatch(boot, &request, &can_id, now_ms);
|
||||
}
|
||||
74
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.h
Normal file
74
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef SETP_TMS2812_BOOT_H
|
||||
#define SETP_TMS2812_BOOT_H
|
||||
|
||||
#include "set_can.h"
|
||||
#include "set_firmware.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef SETP_TMS2812_MAX_BLOCK_SIZE
|
||||
#define SETP_TMS2812_MAX_BLOCK_SIZE 64U
|
||||
#endif
|
||||
|
||||
#define SETP_TMS2812_RESPONSE_PAYLOAD_SIZE \
|
||||
(2U + SETP_DEVICE_INFO_FIXED_SIZE + SETP_DEVICE_MODEL_MAX)
|
||||
|
||||
typedef struct {
|
||||
uint8_t node_id;
|
||||
uint16_t device_class;
|
||||
uint32_t hardware_version;
|
||||
uint32_t firmware_version;
|
||||
uint32_t dictionary_version;
|
||||
uint64_t serial_number;
|
||||
const uint8_t *model;
|
||||
uint8_t model_length;
|
||||
uint32_t app_base_address;
|
||||
uint32_t max_image_size;
|
||||
uint16_t max_block_size;
|
||||
uint8_t active_slot;
|
||||
uint8_t require_signature;
|
||||
} setp_tms2812_boot_config_t;
|
||||
|
||||
typedef struct {
|
||||
setp_can_send_fn send_can;
|
||||
bool (*erase_image)(void *user, uint32_t image_size);
|
||||
bool (*write_image)(void *user, uint32_t offset,
|
||||
const uint8_t *data, uint16_t length);
|
||||
bool (*read_image)(void *user, uint32_t offset,
|
||||
uint8_t *data, uint16_t length);
|
||||
bool (*authorize)(void *user, const setp_fw_begin_t *manifest);
|
||||
void (*reboot)(void *user);
|
||||
} setp_tms2812_boot_port_t;
|
||||
|
||||
typedef struct {
|
||||
setp_tms2812_boot_config_t config;
|
||||
setp_tms2812_boot_port_t port;
|
||||
void *port_user;
|
||||
setp_can_rx_t rx;
|
||||
setp_fw_begin_t manifest;
|
||||
uint32_t next_offset;
|
||||
uint16_t last_error;
|
||||
uint8_t state;
|
||||
uint8_t response_payload[SETP_TMS2812_RESPONSE_PAYLOAD_SIZE];
|
||||
uint8_t response_packet[SETP_FRAME_MAX];
|
||||
} setp_tms2812_boot_t;
|
||||
|
||||
bool setp_tms2812_boot_init(setp_tms2812_boot_t *boot,
|
||||
const setp_tms2812_boot_config_t *config,
|
||||
const setp_tms2812_boot_port_t *port,
|
||||
void *port_user);
|
||||
|
||||
/** Process one classic-CAN frame in task context, never from an ISR. */
|
||||
bool setp_tms2812_boot_process(setp_tms2812_boot_t *boot,
|
||||
const setp_can_frame_t *frame,
|
||||
uint32_t now_ms);
|
||||
|
||||
void setp_tms2812_boot_abort(setp_tms2812_boot_t *boot);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SETP_TMS2812_BOOT_H */
|
||||
Reference in New Issue
Block a user