Расширить общий протокол CAN_Bal_2812
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* @file balsam_can.h
|
||||
* @brief Legacy Balsam 167 extended-CAN register frames.
|
||||
* @brief CAN_Bal_2812 extended-CAN register space.
|
||||
*
|
||||
* The wire layout is taken from Balsam_167_periph/Source/Internal/ecan.c:
|
||||
* The wire layout is used by BALZAM Src/balzam_7/CanSetupBalzam7.c:
|
||||
* one big-endian address/mask word followed by three big-endian register words.
|
||||
*/
|
||||
#ifndef BALSAM_CAN_H
|
||||
@@ -34,7 +34,7 @@ typedef struct {
|
||||
uint16_t values[BALSAM_CAN_REGISTER_COUNT];
|
||||
} balsam_can_frame_t;
|
||||
|
||||
/** Return non-zero for the command/data/terminal IDs used by Balsam 167. */
|
||||
/** Return non-zero for the command/data/terminal IDs used by CAN_Bal_2812. */
|
||||
int balsam_can_is_id(uint32_t can_id);
|
||||
|
||||
/**
|
||||
@@ -44,11 +44,11 @@ int balsam_can_is_id(uint32_t can_id);
|
||||
int balsam_can_decode(uint32_t can_id, const uint8_t *data, size_t size,
|
||||
balsam_can_frame_t *output);
|
||||
|
||||
/** Human-readable Russian name of a Balsam device. */
|
||||
/** Human-readable Russian name of a CAN_Bal_2812 device. */
|
||||
const char *balsam_can_device_name(uint8_t device);
|
||||
|
||||
/**
|
||||
* Format the register name from the Balsam 167 data table into output.
|
||||
* Format the register name from the CAN_Bal_2812 data table into output.
|
||||
* Returns the required length (excluding NUL); an empty string means unknown.
|
||||
*/
|
||||
size_t balsam_can_register_name(uint8_t device, uint16_t address,
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user