196 lines
6.0 KiB
C
196 lines
6.0 KiB
C
#include "gui_catalog.h"
|
||
|
||
static size_t name_len(const char *name)
|
||
{
|
||
size_t n = 0U;
|
||
if (name == NULL) {
|
||
return 0U;
|
||
}
|
||
while ((name[n] != '\0') && (n <= GUI_NAME_SIZE)) {
|
||
n++;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
static uint8_t object_registers(uint8_t type)
|
||
{
|
||
return ((type == GUI_OBJ_U32) || (type == GUI_OBJ_I32)) ? 2U : 1U;
|
||
}
|
||
|
||
bool gui_catalog_validate(const gui_catalog_t *catalog, const pcan_gas_map_t *map)
|
||
{
|
||
if ((catalog == NULL) || ((catalog->count != 0U) && (catalog->items == NULL))) {
|
||
return false;
|
||
}
|
||
|
||
for (uint16_t i = 0U; i < catalog->count; i++) {
|
||
const gui_object_t *item = &catalog->items[i];
|
||
if (name_len(item->name) > GUI_NAME_SIZE) {
|
||
return false;
|
||
}
|
||
if (item->type > GUI_OBJ_BITS) {
|
||
return false;
|
||
}
|
||
if (map != NULL) {
|
||
uint8_t words = object_registers(item->type);
|
||
for (uint8_t w = 0U; w < words; w++) {
|
||
uint32_t addr = (uint32_t)item->address + w;
|
||
if (addr > 0xFFFFUL) {
|
||
return false;
|
||
}
|
||
if (pcan_gas_find(map, (uint16_t)addr) == NULL) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
size_t gui_catalog_encode(const gui_catalog_t *catalog, uint16_t start_index,
|
||
uint16_t max_count, uint8_t *out, size_t out_size)
|
||
{
|
||
if ((catalog == NULL) || (out == NULL) || (start_index >= catalog->count)) {
|
||
return 0U;
|
||
}
|
||
|
||
uint16_t available = (uint16_t)(catalog->count - start_index);
|
||
uint16_t count = ((max_count == 0U) || (max_count > available)) ? available : max_count;
|
||
if (count > GUI_ENTRIES_PER_FRAME) {
|
||
count = (uint16_t)GUI_ENTRIES_PER_FRAME;
|
||
}
|
||
size_t total = (size_t)GUI_CATALOG_HEADER + ((size_t)count * GUI_ENTRY_SIZE);
|
||
if (out_size < total) {
|
||
return 0U;
|
||
}
|
||
|
||
gui_put_u16(&out[0], catalog->count);
|
||
gui_put_u16(&out[2], start_index);
|
||
gui_put_u16(&out[4], count);
|
||
|
||
for (uint16_t i = 0U; i < count; i++) {
|
||
const gui_object_t *item = &catalog->items[start_index + i];
|
||
uint8_t *dst = &out[GUI_CATALOG_HEADER + ((size_t)i * GUI_ENTRY_SIZE)];
|
||
|
||
gui_put_u16(&dst[0], item->address);
|
||
dst[2] = item->type;
|
||
dst[3] = item->flags;
|
||
dst[4] = (uint8_t)item->scale_pow10;
|
||
dst[5] = item->unit;
|
||
dst[6] = 0U;
|
||
dst[7] = 0U;
|
||
|
||
size_t n = name_len(item->name);
|
||
if (n > GUI_NAME_SIZE) {
|
||
n = GUI_NAME_SIZE;
|
||
}
|
||
for (size_t c = 0U; c < n; c++) {
|
||
dst[8U + c] = (uint8_t)item->name[c];
|
||
}
|
||
for (size_t c = n; c < GUI_NAME_SIZE; c++) {
|
||
dst[8U + c] = 0U;
|
||
}
|
||
}
|
||
return total;
|
||
}
|
||
|
||
void gui_watch_init(gui_watch_t *watch)
|
||
{
|
||
watch->period_ms = 0U;
|
||
watch->count = 0U;
|
||
watch->next_ms = 0U;
|
||
watch->sent = 0U;
|
||
watch->skipped = 0U;
|
||
}
|
||
|
||
uint16_t gui_watch_apply(gui_watch_t *watch, const pcan_gas_map_t *map,
|
||
const uint8_t *payload, uint16_t size)
|
||
{
|
||
if (size < 4U) {
|
||
return 0xFFFFU;
|
||
}
|
||
uint16_t period = gui_get_u16(&payload[0]);
|
||
uint16_t requested = gui_get_u16(&payload[2]);
|
||
if (size != (uint16_t)(4U + (requested * 2U))) {
|
||
return 0xFFFFU;
|
||
}
|
||
if (requested > GUI_WATCH_MAX) {
|
||
return 0xFFFFU;
|
||
}
|
||
|
||
uint16_t accepted = 0U;
|
||
for (uint16_t i = 0U; i < requested; i++) {
|
||
uint16_t address = gui_get_u16(&payload[4U + (i * 2U)]);
|
||
/* Адрес вне карты молча пропускаем: иначе одна опечатка в GUI
|
||
отменяла бы всю подписку. */
|
||
if ((map != NULL) && (pcan_gas_find(map, address) == NULL)) {
|
||
continue;
|
||
}
|
||
watch->address[accepted++] = address;
|
||
}
|
||
|
||
watch->count = accepted;
|
||
watch->period_ms = (accepted == 0U) ? 0U : period;
|
||
watch->next_ms = 0U;
|
||
return accepted;
|
||
}
|
||
|
||
size_t gui_watch_encode_ack(const gui_watch_t *watch, uint8_t *out, size_t out_size)
|
||
{
|
||
if (out_size < 4U) {
|
||
return 0U;
|
||
}
|
||
gui_put_u16(&out[0], watch->period_ms);
|
||
gui_put_u16(&out[2], watch->count);
|
||
return 4U;
|
||
}
|
||
|
||
bool gui_watch_due(const gui_watch_t *watch, uint32_t now_ms)
|
||
{
|
||
if ((watch->period_ms == 0U) || (watch->count == 0U)) {
|
||
return false;
|
||
}
|
||
/* Разность беззнаковая: переполнение счётчика миллисекунд обрабатывается
|
||
само собой, сравнение с абсолютным временем - нет. */
|
||
return (uint32_t)(now_ms - watch->next_ms) < 0x80000000UL;
|
||
}
|
||
|
||
size_t gui_watch_encode_data(const gui_watch_t *watch, const pcan_gas_map_t *map,
|
||
uint32_t timestamp_ms, uint8_t *out, size_t out_size)
|
||
{
|
||
if ((watch->count == 0U) || (out == NULL)) {
|
||
return 0U;
|
||
}
|
||
size_t total = 6U + ((size_t)watch->count * 2U);
|
||
if (out_size < total) {
|
||
return 0U;
|
||
}
|
||
|
||
gui_put_u32(&out[0], timestamp_ms);
|
||
gui_put_u16(&out[4], watch->count);
|
||
for (uint16_t i = 0U; i < watch->count; i++) {
|
||
uint16_t value = 0U;
|
||
/* Регистр мог исчезнуть между подпиской и опросом - тогда 0,
|
||
а не мусор из стека. */
|
||
(void)pcan_gas_read(map, watch->address[i], &value);
|
||
gui_put_u16(&out[6U + ((size_t)i * 2U)], value);
|
||
}
|
||
return total;
|
||
}
|
||
|
||
void gui_watch_advance(gui_watch_t *watch, uint32_t now_ms)
|
||
{
|
||
if (watch->period_ms == 0U) {
|
||
return;
|
||
}
|
||
uint32_t next = watch->next_ms + watch->period_ms;
|
||
/* Если отстали больше чем на такт (линия была занята), не пытаемся
|
||
нагнать пачкой пакетов - идём от текущего момента. */
|
||
if ((uint32_t)(now_ms - next) >= 0x80000000UL) {
|
||
watch->next_ms = next;
|
||
} else {
|
||
watch->next_ms = now_ms + watch->period_ms;
|
||
}
|
||
watch->sent++;
|
||
}
|