Примеры
Первая программа
Напишем и разберём программу, которая мигает светодиодом RUN каждый цикл работы. Стиль написания: Structured Text-подобный.
#Моргаем светодиодом RUN
from pyplc.config import plc,board
def hello():
board.run = not board.run
plc.run( instances=[ hello ], ctx=globals() )
Первая строчка подключает и импортирует из модуля pyplc 2 объекта: plc и board. plc необходим для организации цикла работы, board имеет атрибуты, которые отвечают за светодиоды (4 шт) и переключатели (2 шт) контроллера. Далее идет объявление функции с именем hello, в теле которой атрибуту run присваивается логическое отрицание его текущего значения. board.run отвечает за светодиод RUN, имеет тип bool (логический, может быть False -выключен или True — включён). Последняя строка используя объект plc` запускает цикл работы программы. Для этого вызывается метод run` с 2 параметрами: instances и ctx.
instances имеет тип списка (встроенный тип python). Все элементы этого списка вызываются каждый цикл работы программы. Мы в списке только одну функцию указали (обратите внимание, что мы не результат выполнения функции, а саму функцию передали)
ctx имеет тип словаря (dict), и мы ему присваиваем словарь глобальных переменных текущего контекста. plc.run выполняется до прерывания цикла работы (в случае ошибки или исключения).
Включить исполнительный механизм по срабатыванию датчика
Такая задача встречается как вспомогательная, например в случае набора расходной емкости водой по нижнему уровню. В нашем случае нам необходимо подключить 1 дискретный вход (сигнал с датчика) и 1 дискретный выход (управление механизмом, например насосом). Для этого потребуется 8-ми канальный KRAX DI-430 и 8-ми канальный KRAX DO-530.
1from pyplc.config import plc
2from pyplc.channel import IBool,QBool
3from pyplc.ld import LD
4
5SWITCH_ON_1 = IBool.at('%IX8.0')
6POWER_ON_1 = QBool.at('%QX9.0')
7
8# SWITCH_ON_1 POWER_ON_1
9# ├───────┤├───────────────(S)───────┤
10turn_on = LD.no(SWITCH_ON_1).set(POWER_ON_1).end()
11plc.run( instances=[turn_on], ctx=globals() )
В строке №3 подключаем модуль, который позволяет писать код, напоминающий Ladder Diagram (строка 8,9) В строке 5,6 объявлены переменные ввода-вывода. Значение этих переменных синхронизируется с состояниями каналов модулей ввода вывода. Для того чтобы объявить переменную ввода вывода мы выбираем ей имя (SWITCH_ON_1 & POWER_ON_1), и присваиваем им результат выполнения метода at` соответствующего типа. Например дискретный вход это IBool, дискретный выход QBool. В качестве параметра указываем строку, которая указывает по какому адресу в памяти ввода вывода она находится. Подробную информацию об адресации каналов ввода вывода смотрите в документации к библиотеки PYPLC.
Обратите внимание, turn_on тоже использована как функция. Дело в том, что в python объекты могут переопределять метод __call__, и тогда можно написать turn_on() - это будет вызов нашей логики один раз. А просто turn_on — это наша программа. Необходимо отметить, что переменные ввода-вывода (SWITCH_ON_1 & POWER_ON_1) представляют механизм доступа к физическим сигналом, но это тоже объекты. И это может приводить к путанице. Например код
if SWITCH_ON_1: POWER_ON_1 = True
сработает не как ожидается. Первая часть (if SWITCH_ON_1:) работает вполне ожидаемо, то есть проверит какое состояние дискретного сигнала SWITCH_ON_1 и если он True, то выполнит код после :, а именно
POWER_ON_1 = True.
Можно ожидать, что наш дискретный выход будет установлен в True, но в действительности наш объект POWER_ON_1 будет заменен на новый объект, тип которого будет bool. Чтобы избежать этого, лучше к переменным ввода-вывода обращаться через атрибуты объекта hw, который находится в pyplc.config. Наш пример будет выглядеть так:
if SWITCH_ON_1: hw.POWER_ON_1 = True
Еще возможны следующие способы изменения значения переменных вывода, все равнозначные:
POWER_ON_1(True)
POWER_ON_1.write(True)
hw.POWER_ON_1 = True
Подробная информация по работе с переменными ввода-вывода в документации к PYPLC.
Дискретный вход, выход и аналоговый вход
Задача: включить по дискретному входу №1, выключить по входу №2 или уровню аналогового сигнала. Потребуется еще модуль подключения токового сигнала 4-20 мА KRAX-AI455. Он 4-х канальный. Порядок сопряжения с модулями такой: 932 — 455 — 430 — 530.
from pyplc.config import plc,hw
from pyplc.channel import IBool,QBool,IWord
from pyplc.ld import LD
SWITCH_ON_1 = IBool.at( '%IX8.0' )
SWITCH_OFF_1= IBool.at( '%IX8.1' )
LEVEL_1 = IWord.at( '%IW0' )
POWER_ON_1 = QBool.at( '%QX9.0' )
# SWITCH_ON_1 POWER_ON_1
# ├───────┤ ├──────────────(S)────────┤
turn_on = LD.no(SWITCH_ON_1).set(POWER_ON_1).end()
# в Ladder Diagram аналога нет, можно описать как несколько параллельных
# цепей (тут 2), и результатом их выполнения будет True если они все False
# SWITCH_OFF_1 POWER_ON_1
# ├──┤ ├────────────┬───┤NOT├─────────────────────────────────────────(R)─────────┤
# LEVEL_1>32767 │
# ├──┤ ├────────────┘
turn_off=LD.nor(LD.no(SWITCH_OFF_1),LD.no(lambda:LEVEL_1>32767)).rst(POWER_ON_1).end()
plc.run( instances=[turn_on,turn_off], ctx=globals() )
Обратите внимание, в качестве условия LEVEL_1>32767 используется lambda: LEVEL_1>32767. Если написать LEVEL_1>32767, то результат будет вычислен сразу же, будет True если в момент вычисления LEVEL_1>32767 или False иначе. А надо каждый цикл работы вычислять. Для этого нужно создать функцию
def check_level()->bool:
return LEVEL_1>32767
и её поставить. Не очень удобно создавать функцию для любого выражения. На счастье в python есть возможность создавать анонимные функции, используя ключевое слово lambda. Этим пользуемся.
Аналоговый вход, дискретный выход и функциональные блоки
В модуле pyplc есть набор часто используемых блоков. Разберем как их использовать. Задача: включить исполнительный механизм если уровень на аналоговом входе >75%, выключить если <25%.
from pyplc.config import plc,hw
from pyplc.utils.latch import RS
from pyplc.channel import QBool,IWord
LEVEL_1 = IWord.at( '%IW0' )
POWER_ON_1 = QBool.at( '%QX8.0' )
logic = RS(reset = lambda: LEVEL_1<0x3FFF, set = lambda: LEVEL_1>0x7FFF, q = POWER_ON_1 )
plc.run( instances=[logic], ctx=globals() )
Применен блок RS. У него 2 входа (set,reset) и один выход (q). Если set==True и reset==False, то q становится True. Если reset==True, то q становится False. Настраиваем, что поступает на входы set/reset и куда выход q подключить. На вход и выход все программы могут принимать функции, каждый цикл работы они будет вызываться. Функции которые мы подключаем к входам должны работать без аргументов, а те что подключаются к выходам напротив должны работать как минимум с одним аргументом. Мы создаем 2 безымянные функции с помощью ключевого слова lambda. А выход мы подключаем к POWER_ON_1, который как упоминалось выше тоже может использоваться как функция (POWER_ON_1(True) как раз устанавливает новое значение True). Если бы мы на set подключали дискретный вход (как в примере №3) SWITCH_ON_1, то использовать lambda нет необходимости. Было бы просто set = SWITCH_ON_1. Блоки можно самостоятельно писать и потом их повторно использовать.
Создание блока и его использование
Рассмотрим как создать свой блок и как его использовать. Назначение блока: включение двигателя с авто-подхватом, перед включением дать звонок 2 сек. Включение производится дискретным выходом длительностью не менее 1 сек. Выключение другим дискретным выходом длительностью не менее 1 сек. Следовательно, наш блок должен иметь 3 выхода (ON,OFF,BELL) и один вход (MANUAL) для активации.
from pyplc.sfc import SFC,POU
class Motor(SFC):
manual = POU.input( False )
ison = POU.input(False,hidden=True)
on = POU.output(False,hidden=True)
off= POU.output(False,hidden=True)
bell = POU.output(False,hidden=True)
def __init__(self,manual: bool= False, on:bool = False,off:bool = False,bell: bool=False, id:str=None, parent:POU = None ) -> None:
super().__init__( )
self.on = on
self.off= off
self.bell=bell
self.manual = manual
def main(self):
yield from self.until(lambda: self.manual,step='Ждем manual')
self.bell = True
yield from self.pause(2000,step='Звонок')
self.bell = False
self.on = True
yield from self.till(lambda: self.manual,max=1000,step='Импульс ON')
self.on = False
yield from self.till(lambda: self.manual,step='Работаем')
self.off = True
yield from self.pause(2000,step='Импульс OFF')
self.off = False
Программа должна каждый цикл опроса должна завершать свою работу. Надо при получении сигнала manual надо включить звонок, подождать 2 сек и выключить его. SFC-блоки за счет использования ключевого слова yield позволяют функцию выполнять частями, например:
yield from self.until(lambda: self.manual,step='Ждем manual')
каждое сканирование проверяет состояние self.manual, пока оно не станет ==True. А как только оно выполнится перейдет к следующей части кода:
self.bell = True
yield from self.pause(2000,step='Звонок')
self.bell = False
В первой строке на выход bell подаем True, и потом снова конструкция с циклом на 2000 мсек. По окончанию которой на bell выдаем False. Обратите внимание: если self.bell = True нужно удерживать, то выглядеть это может так:
self.bell = True
for _ in self.pause(2000,step='Звонок'):
self.bell = True
self.bell = False
А так если к выходу bell подключен физический сигнал он изменится всего 2 раза (в начале и конце).
Пример использования нашего блока:
from pyplc.config import plc
from pyplc.channel import IBool,QBool
from motor import Motor
SWITCH_ON_1 = IBool.at( '%IX0.0' )
POWER_ON_1 = QBool.at( '%QX1.0' )
POWER_OFF_1 = QBool.at( '%QX1.1' )
BELL_ON_1 = QBool.at( '%QX1.2' )
motor = Motor(manual=SWITCH_ON_1,on=POWER_ON_1,off=POWER_OFF_1, bell = BELL_ON_1)
plc.run(instances=[motor],ctx=globals())
Тестирование и отладка
Для тестирования и отладки используется следующая методика: программа пишется, отлаживается на компьютере (без контроллера), так как синтаксис Python и MicroPython одинаковый, модуль pyplc написан на python и работает на любом ПК. В качестве среды разработки используется VSCode с дополнением для обмена файлами с контроллером KRAX. В VSCode доступны все привычные методы отладки (контрольные точки, просмотр значений любой переменной, пошаговое выполнение и т.п.). Модуль PyPLC позволяет прозрачно подключиться к контроллеру (если он есть), и тогда сигналы с датчиков и управляющие сигналы синхронизируются.
Советы по поиску и устранению проблем
Каждый цикл работы программы моргайте индикатором RUN. Это позволит с первого взгляда определить работает программа сейчас или закончила своё выполнение.
Пользуйтесь выводом на консоль (функция print, или POU.log ).
Если надо проверить состояние переменных во время выполнения программы в контроллере можно воспользоваться telnet подключением (доступно на порту 2455). После установки соединения можно написать выражение на python и оно будет выполнено или вычислено. Например если написать board.run вам в ответ будет показано текущее состояние светодиода RUN.