Расскажу о подходе к оживлению цепей и функциональных блоков в своей среде разработки под ПЛК. Я не использую никаких графических фреймворков и библиотек — это как раз тот случай, когда самому сделать быстрее, чем изучать что-то новое. Все функциональные блоки программы, отображаемые в среде на ПК, рисуются примитивами (прямоугольниками, линиями, точками, текстом и т. д.).

На этапе проектирования платформы я сразу поставил перед собой цель — добиться максимальной гибкости: пользователь может сам нарисовать функциональный блок, обозначить его сигналы и описать поведение в цепи. Для этого я создал шаблон:

 <LD_Unit Name="SEND" Group="Message" Width="120" Height="80">
      <Variables>
         -----Входящие сигналы/Переменные-----------
      </Variables>
      <types>
               SEND typedef
              {
                 Структура
              }
      </types>
      <lists>
           Линия -------
           Квадрат------
           Текст--------
      </lists>
      <code>
        <Line>
             ------------------------- КОД -----------------------
       </Line>
      </code>
    </LD_Unit>

Главное - те примитивы которые будут анимированы, привязываются к переменным:

        <Item Type="LINE" color="#ffffff" Style="onoff:#96C1FF">
          <Value>this</Value>
...
После запуска проекта, Item Type="LINE" - конвертируется в обьект.

Style="onoff:#96C1FF" как раз и указывает, что делать: если значение переменной больше нуля, меняются цвет, положение, текст и другие эффекты.

Компилятор.

Компилятор собственной разработки, а не на базе LLVM/Clang или подобных конструкторов. Всю логику расстановки переменных и инструкций в памяти я разрабатывал сам. Вместе с программой для ПЛК создается файл наподобие .elf:

--- DISASSEMBLY & BINARY CODE ---
Instruction [Offset]                          | Binary Representation (MSB..LSB)
------------------------------------------------------------------------
0] LDRI R3 Address: 818                       | 00000110 00110000 00000011 00110010
4] LDPHY R3 R3 R0                             | 00001001 00011000 11000000 00000000
8] SDRI R3 Address: 814                       | 00001101 00110000 00000011 00101110
12] MOV R0 R28 R0                             | 00111100 00000111 00000000 00000000
16] SDRI R0 Address: 818                      | 00001101 00000000 00000011 00110010

--- SYMBOLS & VARIABLES MEMORY MAP ---
Address    | Name                           | Type            | Size  
--------------------------------------------------------------------------------------
818        | system.AddrTime                | dword           | 4     
814        | system.Time                    | dword           | 4     
778        | system.IN_phy                  | pointer         | 4     
798        | system.emodule_.DI_0           | byte            | 1     
800        | system.emodule_.DI_2           | byte            | 1     
804        | system.emodule_.DI_6           | byte            | 1 
--- SYMBOLS & VARIABLES MEMORY MAP ---
Address    | Name                           | Type            | Size  
--------------------------------------------------------------------------------------
818        | system.AddrTime                | dword           | 4     
814        | system.Time                    | dword           | 4     
778        | system.IN_phy                  | pointer         | 4     
798        | system.emodule_.DI_0           | byte            | 1     
800        | system.emodule_.DI_2           | byte            | 1     
804        | system.emodule_.DI_6           | byte            | 1 

Вместе с человекочитаемым текстом формируется файл для моей среды разработки:

#####XML_SYMBOLS_BEGIN#####
<?xml version="1.0" encoding="UTF-8"?>
<symbols>
  <symbol name="system.AddrTime" type="dword" address="818" size="4"/>
  <symbol name="system.Time" type="dword" address="814" size="4"/>
  <symbol name="system.IN_phy" type="pointer" address="778" size="4"/>
  <symbol name="system.emodule_.DI_0" type="byte" address="798" size="1"/>
  <symbol name="system.emodule_.DI_2" type="byte" address="800" size="1"/>

Здесь содержатся имя переменной, ее тип и адрес. Это позволяет не только отправлять команды на запись значений в переменные, но и читать их:

Установка значения переменной в программе ПЛК.
Установка значения переменной в программе ПЛК.

Теперь, когда компилятор вернул нам регион памяти переменных, мы отправляем команду на МК — просто прислать этот регион (а память у нас виртуальная, обычный массив uint8_t memory[size]).

Получаем в среде этот регион из массива, и среда уже понимает, какая переменная за что отвечает, а движок отрисовки на основе этих значений знает, что делать.

В целом графика выглядит так:

Очень легкая среда, которая даже в режиме отрисовки занимает в ОЗУ всего 120 МБ и может работать даже на первых версиях Raspberry Pi (среда разработана с использованием OpenJDK).