Объединить разработки CAN и STM32 в master

This commit is contained in:
2026-09-15 15:57:48 +03:00
68 changed files with 3585 additions and 122 deletions

View File

@@ -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 \

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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}"

View File

@@ -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" }
}
}

View File

@@ -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))
}
}

View File

@@ -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])
}
}

View File

@@ -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)

View File

@@ -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]) }
}
}

View File

@@ -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,
&timestamp, 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;

View File

@@ -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)
}
}

View File

@@ -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 })
}
}

View File

@@ -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))
}
}

View File

@@ -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)
}
}

View 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.

View 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);
}

View 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 */