Исправить порядок слов BALZAM CAN и добавить поддержку пульта МПУ
This commit is contained in:
@@ -3,7 +3,10 @@
|
|||||||
* @brief CAN_Bal_2812 extended-CAN register space.
|
* @brief CAN_Bal_2812 extended-CAN register space.
|
||||||
*
|
*
|
||||||
* The wire layout is used by BALZAM Src/balzam_7/CanSetupBalzam7.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.
|
* eCAN MDL precedes MDH on the wire: value2, value3, address/mask, value1.
|
||||||
|
* Each word is big-endian; address/mask is at bytes 4..5, not 0..1.
|
||||||
|
* BA000x routes PM -> node; BA001x routes node -> PM.
|
||||||
|
* The MPU panel uses 745009 / 745019 with the same register layout.
|
||||||
*/
|
*/
|
||||||
#ifndef BALSAM_CAN_H
|
#ifndef BALSAM_CAN_H
|
||||||
#define BALSAM_CAN_H
|
#define BALSAM_CAN_H
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ object BalsamCanProtocol {
|
|||||||
}
|
}
|
||||||
val padded = values + List(3 - values.size) { 0 }
|
val padded = values + List(3 - values.size) { 0 }
|
||||||
val header = (mask shl 13) or startAddress
|
val header = (mask shl 13) or startAddress
|
||||||
val data = listOf(header, padded[0], padded[1], padded[2])
|
val data = listOf(padded[1], padded[2], header, padded[0])
|
||||||
.flatMap { listOf((it ushr 8).toByte(), it.toByte()) }
|
.flatMap { listOf((it ushr 8).toByte(), it.toByte()) }
|
||||||
.toByteArray()
|
.toByteArray()
|
||||||
val node = nodes[device - 1]
|
val node = nodes[device - 1]
|
||||||
@@ -131,13 +131,13 @@ object BalsamCanProtocol {
|
|||||||
|
|
||||||
private fun fallbackDecode(canId: Long, data: ByteArray): IntArray {
|
private fun fallbackDecode(canId: Long, data: ByteArray): IntArray {
|
||||||
val relative = (canId and 0x1FFF_FFFFL) - BASE_ID
|
val relative = (canId and 0x1FFF_FFFFL) - BASE_ID
|
||||||
val header = u16be(data, 0)
|
val header = u16be(data, 4)
|
||||||
return intArrayOf(
|
return intArrayOf(
|
||||||
((relative and 0x0F) + 1).toInt(),
|
((relative and 0x0F) + 1).toInt(),
|
||||||
if (relative >= 0x10) 1 else 0,
|
if (relative >= 0x10) 1 else 0,
|
||||||
(header ushr 13) and 7,
|
(header ushr 13) and 7,
|
||||||
header and 0x1FFF,
|
header and 0x1FFF,
|
||||||
u16be(data, 2), u16be(data, 4), u16be(data, 6),
|
u16be(data, 6), u16be(data, 0), u16be(data, 2),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class BalsamCanProtocolTest {
|
|||||||
fun decodesThreeNamedSensorRegisters() {
|
fun decodesThreeNamedSensorRegisters() {
|
||||||
val frame = requireNotNull(BalsamCanProtocol.decode(
|
val frame = requireNotNull(BalsamCanProtocol.decode(
|
||||||
0x00BA_0010L,
|
0x00BA_0010L,
|
||||||
byteArrayOf(0xE0.toByte(), 0x18, 0x00, 0x29, 0xFF.toByte(), 0xFE.toByte(), 0x12, 0x34),
|
byteArrayOf(0xFF.toByte(), 0xFE.toByte(), 0x12, 0x34, 0xE0.toByte(), 0x18, 0x00, 0x29),
|
||||||
))
|
))
|
||||||
assertEquals(1, frame.device)
|
assertEquals(1, frame.device)
|
||||||
assertEquals(0x18, frame.startAddress)
|
assertEquals(0x18, frame.startAddress)
|
||||||
@@ -28,7 +28,7 @@ class BalsamCanProtocolTest {
|
|||||||
)
|
)
|
||||||
assertEquals(0x00BA_0002L, wire.canId)
|
assertEquals(0x00BA_0002L, wire.canId)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(0xC0, 0x28, 0x12, 0x34, 0xFE, 0xDC, 0x00, 0x00),
|
listOf(0xFE, 0xDC, 0x00, 0x00, 0xC0, 0x28, 0x12, 0x34),
|
||||||
wire.data.map { it.toInt() and 0xFF },
|
wire.data.map { it.toInt() and 0xFF },
|
||||||
)
|
)
|
||||||
val decoded = requireNotNull(BalsamCanProtocol.decode(wire.canId, wire.data))
|
val decoded = requireNotNull(BalsamCanProtocol.decode(wire.canId, wire.data))
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ static size_t indexed_name(const char *prefix, unsigned index,
|
|||||||
|
|
||||||
int balsam_can_is_id(uint32_t can_id)
|
int balsam_can_is_id(uint32_t can_id)
|
||||||
{
|
{
|
||||||
|
can_id &= 0x1FFFFFFFUL;
|
||||||
|
if (can_id == 0x00745009UL || can_id == 0x00745019UL) return 1;
|
||||||
uint32_t relative = (can_id & 0x1FFFFFFFUL) - BALSAM_CAN_BASE_ID;
|
uint32_t relative = (can_id & 0x1FFFFFFFUL) - BALSAM_CAN_BASE_ID;
|
||||||
return relative < BALSAM_CAN_NODE_COUNT
|
return relative < BALSAM_CAN_NODE_COUNT
|
||||||
|| (relative >= BALSAM_CAN_DATA_OFFSET
|
|| (relative >= BALSAM_CAN_DATA_OFFSET
|
||||||
@@ -54,12 +56,18 @@ int balsam_can_decode(uint32_t can_id, const uint8_t *data, size_t size,
|
|||||||
output->direction = relative >= BALSAM_CAN_DATA_OFFSET
|
output->direction = relative >= BALSAM_CAN_DATA_OFFSET
|
||||||
? BALSAM_CAN_FROM_NODE : BALSAM_CAN_TO_NODE;
|
? BALSAM_CAN_FROM_NODE : BALSAM_CAN_TO_NODE;
|
||||||
output->device = (uint8_t)((relative & 0x0FU) + 1U);
|
output->device = (uint8_t)((relative & 0x0FU) + 1U);
|
||||||
header = get_be16(data);
|
if (can_id == 0x00745009UL || can_id == 0x00745019UL) {
|
||||||
|
output->device = 10U;
|
||||||
|
output->direction = can_id == 0x00745019UL
|
||||||
|
? BALSAM_CAN_FROM_NODE : BALSAM_CAN_TO_NODE;
|
||||||
|
}
|
||||||
|
/* eCAN sends MDL before MDH: value2, value3, address/mask, value1. */
|
||||||
|
header = get_be16(&data[4]);
|
||||||
output->present_mask = (uint8_t)((header >> 13) & 0x07U);
|
output->present_mask = (uint8_t)((header >> 13) & 0x07U);
|
||||||
output->start_address = (uint16_t)(header & 0x1FFFU);
|
output->start_address = (uint16_t)(header & 0x1FFFU);
|
||||||
output->values[0] = get_be16(&data[2]);
|
output->values[0] = get_be16(&data[6]);
|
||||||
output->values[1] = get_be16(&data[4]);
|
output->values[1] = get_be16(&data[0]);
|
||||||
output->values[2] = get_be16(&data[6]);
|
output->values[2] = get_be16(&data[2]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +76,7 @@ const char *balsam_can_device_name(uint8_t device)
|
|||||||
static const char *const names[BALSAM_CAN_NODE_COUNT] = {
|
static const char *const names[BALSAM_CAN_NODE_COUNT] = {
|
||||||
"Трансформатор 1", "Трансформатор 2", "Силовой блок 1",
|
"Трансформатор 1", "Трансформатор 2", "Силовой блок 1",
|
||||||
"Силовой блок 2", "УМП 1", "УМП 2", "Двигатель", "ВЭП",
|
"Силовой блок 2", "УМП 1", "УМП 2", "Двигатель", "ВЭП",
|
||||||
"Задатчик", "Узел 10", "Узел 11", "Узел 12", "Терминал"
|
"Задатчик", "Пульт (МПУ)", "Узел 11", "Узел 12", "Терминал"
|
||||||
};
|
};
|
||||||
return (device >= 1U && device <= BALSAM_CAN_NODE_COUNT)
|
return (device >= 1U && device <= BALSAM_CAN_NODE_COUNT)
|
||||||
? names[device - 1U] : "Неизвестный узел";
|
? names[device - 1U] : "Неизвестный узел";
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
const uint8_t packet[8] = { 0xE0, 0x18, 0x00, 0x29, 0xFF, 0xFE, 0x12, 0x34 };
|
const uint8_t packet[8] = { 0xFF, 0xFE, 0x12, 0x34, 0xE0, 0x18, 0x00, 0x29 };
|
||||||
|
const uint8_t command[8] = { 0, 0, 0, 0, 0x80, 0x7F, 0, 0x80 };
|
||||||
balsam_can_frame_t frame;
|
balsam_can_frame_t frame;
|
||||||
char name[64];
|
char name[64];
|
||||||
|
|
||||||
@@ -26,5 +27,13 @@ int main(void)
|
|||||||
CHECK(balsam_can_register_name(1U, 0x18U, name, sizeof name) > 0U);
|
CHECK(balsam_can_register_name(1U, 0x18U, name, sizeof name) > 0U);
|
||||||
CHECK(strstr(name, "T° 1") != NULL);
|
CHECK(strstr(name, "T° 1") != NULL);
|
||||||
CHECK(balsam_can_decode(0x00BA0010UL, packet, 7U, &frame) == -2);
|
CHECK(balsam_can_decode(0x00BA0010UL, packet, 7U, &frame) == -2);
|
||||||
|
CHECK(balsam_can_decode(0x00BA0004UL, command, 8U, &frame) == 1);
|
||||||
|
CHECK(frame.device == 5U && frame.direction == BALSAM_CAN_TO_NODE);
|
||||||
|
CHECK(frame.start_address == 127U && frame.present_mask == 4U);
|
||||||
|
CHECK(frame.values[0] == 0x80U);
|
||||||
|
CHECK(balsam_can_decode(0x00745009UL, command, 8U, &frame) == 1);
|
||||||
|
CHECK(frame.device == 10U && frame.direction == BALSAM_CAN_TO_NODE);
|
||||||
|
CHECK(balsam_can_decode(0x00745019UL, packet, 8U, &frame) == 1);
|
||||||
|
CHECK(frame.device == 10U && frame.direction == BALSAM_CAN_FROM_NODE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from .native import NativeProtocolUnavailable, get_native_protocol
|
|||||||
BASE_ID = 0x00BA0000
|
BASE_ID = 0x00BA0000
|
||||||
DATA_OFFSET = 0x10
|
DATA_OFFSET = 0x10
|
||||||
NODE_COUNT = 13
|
NODE_COUNT = 13
|
||||||
|
PULT_COMMAND_ID = 0x00745009
|
||||||
|
PULT_DATA_ID = 0x00745019
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -33,25 +35,46 @@ class BalsamFrame:
|
|||||||
present_mask: int
|
present_mask: int
|
||||||
registers: tuple[BalsamRegister, ...]
|
registers: tuple[BalsamRegister, ...]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def route(self) -> str:
|
||||||
|
return ("%s → ПМ" % self.device_name if self.from_device
|
||||||
|
else "ПМ → %s" % self.device_name)
|
||||||
|
|
||||||
|
def command_description(self, value: int) -> str:
|
||||||
|
"""Command register 127; named UMP bits from BALZAM_167 automats.h."""
|
||||||
|
if value == 0:
|
||||||
|
return "нет активных команд"
|
||||||
|
names = ({0x0004: "сохранение уставок", 0x0010: "установка нулевого уровня"}
|
||||||
|
if 1 <= self.device <= 7 else {})
|
||||||
|
if self.device in (5, 6):
|
||||||
|
names.update({0x0040: "останов заряда", 0x0080: "пуск заряда",
|
||||||
|
0x0100: "инициализация ЦАП", 0x8000: "сброс УМП"})
|
||||||
|
return ", ".join(names.get(1 << bit, "бит %d" % bit)
|
||||||
|
for bit in range(16) if value & (1 << bit))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def summary(self) -> str:
|
def summary(self) -> str:
|
||||||
direction = "данные" if self.from_device else "команда"
|
direction = "данные" if self.from_device else "команда"
|
||||||
values = ", ".join(
|
values = ", ".join(
|
||||||
"%s=0x%04X (%d)" % (item.name or "R%04X" % item.address,
|
"%s=0x%04X (%d)" % (item.name or "R%04X" % item.address,
|
||||||
item.value, item.signed_value)
|
item.value, item.signed_value)
|
||||||
|
+ (" — " + self.command_description(item.value)
|
||||||
|
if not self.from_device and item.address == 0x7F else "")
|
||||||
for item in self.registers
|
for item in self.registers
|
||||||
) or "нет отмеченных регистров"
|
) or "нет отмеченных регистров"
|
||||||
return "BALZAM · %s · %s · %s" % (self.device_name, direction, values)
|
return "BALZAM · %s · %s · %s" % (self.route, direction, values)
|
||||||
|
|
||||||
|
|
||||||
def is_balsam_id(can_id: int) -> bool:
|
def is_balsam_id(can_id: int) -> bool:
|
||||||
|
if can_id & 0x1FFFFFFF in (PULT_COMMAND_ID, PULT_DATA_ID):
|
||||||
|
return True
|
||||||
relative = (can_id & 0x1FFFFFFF) - BASE_ID
|
relative = (can_id & 0x1FFFFFFF) - BASE_ID
|
||||||
return (0 <= relative < NODE_COUNT
|
return (0 <= relative < NODE_COUNT
|
||||||
or DATA_OFFSET <= relative < DATA_OFFSET + NODE_COUNT)
|
or DATA_OFFSET <= relative < DATA_OFFSET + NODE_COUNT)
|
||||||
|
|
||||||
|
|
||||||
def decode(can_id: int, data: bytes, native=None) -> BalsamFrame | None:
|
def decode(can_id: int, data: bytes, native=None) -> BalsamFrame | None:
|
||||||
"""Decode one abstract Balsam frame: BE mask/address plus three BE words."""
|
"""Decode wire order: BE value2, value3, mask/address, value1."""
|
||||||
if native is None:
|
if native is None:
|
||||||
try:
|
try:
|
||||||
native = get_native_protocol()
|
native = get_native_protocol()
|
||||||
@@ -82,17 +105,22 @@ def _decode_fallback(can_id: int, data: bytes) -> BalsamFrame | None:
|
|||||||
raise ValueError("BALZAM CAN frame must contain exactly 8 data bytes")
|
raise ValueError("BALZAM CAN frame must contain exactly 8 data bytes")
|
||||||
relative = (can_id & 0x1FFFFFFF) - BASE_ID
|
relative = (can_id & 0x1FFFFFFF) - BASE_ID
|
||||||
device = (relative & 0x0F) + 1
|
device = (relative & 0x0F) + 1
|
||||||
header = int.from_bytes(data[:2], "big")
|
from_device = relative >= DATA_OFFSET
|
||||||
|
if can_id & 0x1FFFFFFF in (PULT_COMMAND_ID, PULT_DATA_ID):
|
||||||
|
device = 10
|
||||||
|
from_device = (can_id & 0x1FFFFFFF) == PULT_DATA_ID
|
||||||
|
header = int.from_bytes(data[4:6], "big")
|
||||||
values = tuple(int.from_bytes(data[offset:offset + 2], "big")
|
values = tuple(int.from_bytes(data[offset:offset + 2], "big")
|
||||||
for offset in (2, 4, 6))
|
for offset in (6, 0, 2))
|
||||||
names = {
|
names = {
|
||||||
1: "Трансформатор 1", 2: "Трансформатор 2",
|
1: "Трансформатор 1", 2: "Трансформатор 2",
|
||||||
3: "Силовой блок 1", 4: "Силовой блок 2", 5: "УМП 1", 6: "УМП 2",
|
3: "Силовой блок 1", 4: "Силовой блок 2", 5: "УМП 1", 6: "УМП 2",
|
||||||
7: "Двигатель", 8: "ВЭП", 9: "Задатчик", 13: "Терминал",
|
7: "Двигатель", 8: "ВЭП", 9: "Задатчик", 10: "Пульт (МПУ)", 13: "Терминал",
|
||||||
}
|
}
|
||||||
start, mask = header & 0x1FFF, (header >> 13) & 7
|
start, mask = header & 0x1FFF, (header >> 13) & 7
|
||||||
registers = tuple(BalsamRegister(start + i, values[i], "")
|
registers = tuple(BalsamRegister(start + i, values[i],
|
||||||
|
"Команды" if start + i == 0x7F else "")
|
||||||
for i in range(3) if mask & (4 >> i))
|
for i in range(3) if mask & (4 >> i))
|
||||||
return BalsamFrame(can_id & 0x1FFFFFFF, device,
|
return BalsamFrame(can_id & 0x1FFFFFFF, device,
|
||||||
names.get(device, "Узел %d" % device),
|
names.get(device, "Узел %d" % device),
|
||||||
relative >= DATA_OFFSET, start, mask, registers)
|
from_device, start, mask, registers)
|
||||||
|
|||||||
@@ -31,12 +31,15 @@ def decode(raw_id: int, data: bytes, *, ide: bool = True,
|
|||||||
)
|
)
|
||||||
result = pc.Decoded(pid, len(data), bytes(data), ide=ide, rtr=rtr,
|
result = pc.Decoded(pid, len(data), bytes(data), ide=ide, rtr=rtr,
|
||||||
protocol="BALZAM 167", device_label=frame.device_name,
|
protocol="BALZAM 167", device_label=frame.device_name,
|
||||||
message_label="BALZAM регистры")
|
message_label=("BALZAM данные" if frame.from_device
|
||||||
|
else "BALZAM команды"))
|
||||||
result.summary = frame.summary
|
result.summary = frame.summary
|
||||||
result.fields = [
|
result.fields = [
|
||||||
("Протокол", "BALZAM 167 legacy eCAN"),
|
("Протокол", "BALZAM 167 legacy eCAN"),
|
||||||
("Узел", "%d — %s" % (frame.device, frame.device_name)),
|
("Узел", "%d — %s" % (frame.device, frame.device_name)),
|
||||||
("Направление", "от устройства" if frame.from_device else "к устройству"),
|
("Направление", frame.route),
|
||||||
|
("Отправитель", frame.device_name if frame.from_device else "ПМ"),
|
||||||
|
("Получатель", "ПМ" if frame.from_device else frame.device_name),
|
||||||
("Начальный адрес", "0x%04X" % frame.start_address),
|
("Начальный адрес", "0x%04X" % frame.start_address),
|
||||||
("Маска трёх слов", "0b%s" % format(frame.present_mask, "03b")),
|
("Маска трёх слов", "0b%s" % format(frame.present_mask, "03b")),
|
||||||
]
|
]
|
||||||
@@ -46,6 +49,8 @@ def decode(raw_id: int, data: bytes, *, ide: bool = True,
|
|||||||
"0x%04X %s" % (item.address, item.name or "регистр"),
|
"0x%04X %s" % (item.address, item.name or "регистр"),
|
||||||
"0x%04X (%d)" % (item.value, item.signed_value),
|
"0x%04X (%d)" % (item.value, item.signed_value),
|
||||||
))
|
))
|
||||||
|
if not frame.from_device and item.address == 0x7F:
|
||||||
|
result.fields.append(("Команды ПМ", frame.command_description(item.value)))
|
||||||
if frame.present_mask == 0:
|
if frame.present_mask == 0:
|
||||||
result.warnings.append("В адресном слове не отмечен ни один регистр")
|
result.warnings.append("В адресном слове не отмечен ни один регистр")
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user