OPC UA. Подписка на события

Получать данные от сервера по запросу клиента, конечно, хорошо, но…

Но бывают ситуации, когда данные меняются очень редко, а реагировать на их изменение нужно достаточно быстро. Ну, например, у нас есть какое-то устройство (сервер), который следит за напряжением «в розетке». Пока напряжение находится в норме (допустим, это 220 В ±10%, что составляет от 198 до 242 В), то на стороне клиента всё должно работать как обычно. Если напряжение выходит за допустимые пределы, то клиент должен предпринять определённые действия. Например, запустить дизель или включить аварийное освещение.

Понятно, что если клиент будет периодически опрашивать сервер, то это решение — плохое решение. Ведь помимо того, что клиент будет сам отвлекаться и отвлекать от полезной работы сервер, окажутся ещё загружены и линии связи. В правильно спроектированных системах такого быть не должно. Было бы куда правильнее чтобы при наступлении определённых событий сервер сам проявлял инициативу и посылал клиенту соответствующее сообщение.

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

Существует ещё один способ — подписка на события. Клиенты, кому это действительно надо, подписываются у серверов на интересующие их (клиентов) события. Таким образом, у серверов появляются таблицы рассылок. И при наступлении того или иного события сервер оповещает о нём всех тех (клиентов), кто подписался на это событие. Поскольку предполагается, что события случаются не часто, то линии связи будут загружены минимально, а клиенты и серверы будут отвлекаться от своих основных работ только по мере возникновения событий.

Ниже приведён текст программы сервера, которая «измеряет» напряжение и, в случае выхода его за допустимые пределы, посылает оповещение об этом событии клиентам, которые подписались на
это. Когда напряжение снова возвращается в норму, сервер ещё раз посылает сообщение.

Помимо этой работы сервер периодически выводит в консоль «измеренное» напряжение и печатает график в виде звёздочек. Если напряжение в норме, то звёздочка будет зелёная. Когда напряжение выше на корню или ниже допустимых порогов, звёздочка будет красная.

Измеренное напряжение моделируется генератором случайных чисел. Программа будет «измерять» напряжение 10 раз в секунду. Выход из программы — по нажатию Ctrl-C.

#!/usr/bin/env python3

''' v2s.py '''

URL = "opc.tcp://0.0.0.0:4840"

RED     = "\033[01;31m"
GREEN   = "\033[01;32m"
BLUE    = "\033[01;34m"
DEFAULT = "\033[00m"

TOP    = 242.0
BOTTOM = 198.0

import time
import random
from opcua import ua, Server

if __name__ == "__main__":
  server = Server()
  server.set_endpoint(URL)

  ns         = server.register_namespace("Моё пространство имён")
  objects    = server.get_objects_node()
  oVoltmeter = objects.add_object(ns, "Voltmeter")
  et220      = server.create_custom_event_type(ns, 'Event220', ua.ObjectIds.BaseEventType,
                 [('Voltage', ua.VariantType.Float),
                  ('State', ua.VariantType.String)])
  eg         = server.get_event_generator(et220, oVoltmeter)

  preV = V = 220.0
  server.start()

  try:
    while True:
      dV = random.uniform(-1.00, 1.00)
      V += dV

      if BOTTOM <= V < TOP:
        color = GREEN
      else:
        color = RED

      s = " " * int((V / 4)) + color + "*" + DEFAULT

      print("{0:6.1f} В{1:s}".format(V, s))

      eg.event.Voltage = V

      if (preV <= TOP and V > TOP):
        eg.event.State = "High"
        eg.trigger()
      if (preV >= TOP and V < TOP): eg.event.State = "Normal" eg.trigger() elif (preV >= BOTTOM and V < BOTTOM):
        eg.event.State = "Low"
        eg.trigger()
      elif (preV <= BOTTOM and V > BOTTOM):
        eg.event.State = "Normal"
        eg.trigger()        

      preV = V

      time.sleep(0.1)
  finally:
    server.stop()

Программа клиента:

#!/usr/bin/env python3
# coding:utf-8

''' v2c.py '''

URL = "opc.tcp://localhost:4840"

RED     = "\033[01;31m"
GREEN   = "\033[01;32m"
BLUE    = "\033[01;34m"
DEFAULT = "\033[00m"

import sys
import time
import subprocess
from opcua import Client

class Handler220(object):
  def event_notification(self, event):
    if event.State == "Low":
      print(BLUE, end='')
      print("Напряжение ниже нормы ({:.1f} В)".format(event.Voltage), end='')
      subprocess.run(["beep", "-f 1900 -l 100"])
    elif event.State == "High":
      print(RED, end='')
      print("Напряжение выше нормы ({:.1f} В)".format(event.Voltage), end='')
      subprocess.run(["beep", "-f 2100 -l 100"])
    elif event.State == "Normal":
      print(GREEN, end='')
      print("Напряжение нормализовалось: {:.1f} В".format(event.Voltage), end='')
      #subprocess.run(["beep", "-f 1500 -l 100"])
    print(DEFAULT)

if __name__ == "__main__":
  client = Client(URL)
  try:
    client.connect()
    root = client.get_root_node()

    oVoltmeter = root.get_child(["0:Objects", "2:Voltmeter"])
    # print("oVoltmeter : ", oVoltmeter)

    event220 = root.get_child(["0:Types", "0:EventTypes", "0:BaseEventType", "2:Event220"])
    # print("event220 : ", event220)

    handler220 = Handler220()
    subscribe = client.create_subscription(100, handler220)
    h = subscribe.subscribe_events(oVoltmeter, event220)

    while True:
      time.sleep(1)
      print(".")

    subscribe.unsubscribe(h)
    subscribe.delete()
  finally:
    client.disconnect()

В начале своей работы клиент подключается к серверу и подписывается на сервере на событие «Event200». После этого клиент входит в бесконечный цикл, в котором выполняет полезную работу. Поскольку это учебная программа, то полезная работа клиента заключается в печатании точек. Таким образом мы сможем видеть, что клиент живёт, а не «повесился». (Эх, хорошо сказал!)

В момент, когда от сервера поступил оповещение о событии, сработает функция event_notification, которая прописана в классе Handler220. Но чтобы эта функция сработала, мы должны создать объект этого класса, а затем передать этот объект в качестве параметра методу create_subscription. Изучайте код, он не такой сложный! Я его специально упростил до… до… до безобразия, Причём, местами вульгарного.

Хорошо. А что сделает функция event_notification, когда получит квант процессорного времени?

В зависимости от параметра State, переданного в событии, на экран консоли будет выведено соответствующее сообщение о напряжении. Цвет сообщения так же зависит от параметра State. Затем в динамике компа (системного блока) раздастся короткий звуковой сигнал.

Вот, как выглядит экран клиента, когда напряжение «плохое»:

Вертикально расположенные светлые точки в начале строк — это основная работа клиента. Они появляются раз в секунду.

Пикание я ввёл в программу только для того, чтобы не сидеть перед экраном монитора и не ждать появления события. Когда происходит событие, комп пикает, и тогда можно отвлечься и посмотреть на экран. Вреде удобно. Но проблема в том, что мир со времён DOS-а сильно изменился. И по нынешним временам проще запустить на воспроизведение какой-нибудь звуковой файл, чем просто пикнуть динамиком.

Прежде всего, чтобы пикнуть динамиком, в Debian-е нужно установить пакет beep.

# apt install beep

После того, как установили пакет, не выходя из-под root-а, наберите команду:

# beep -f 1500 -l 500

Эта команда заставит динамик пищать в течении полусекунды (-l 500) на частоте 1500 Гц.

Мне не удалось легко и быстро прописать эту возможность пользователю. В ранних версиях Ubutntu и Debian пищать динамиком было позволено всем пользователям. Теперь, на сколько я понял, в целях безопасности (это не шутка!) — можно пищать только тем, кого администратор наделит соответствующими правами.

В результате так получилось, что организовать запуск программы клиента из-под root-a оказалось проще, чем продираться сквозь кордон разрешительно-защитных особенностей системы.
Обратите внимание, что команда beep достаточно умна и способна отследить, что её запустили с преамбулой «sudo». То есть «$ sudo beep» тоже не отработает. Эта пикалка запускается только в сессии root-а. Поэтому нашу программу клиента тоже нужно запускать в сессии root-а.

Вообще, для чистоты эксперимента было бы хорошо запустить сервер и клиента на разных компах и посмотреть на трафик между ними.. Например, сервер запустить на Raspberry Pi, а клиента на обычном компе.

Кажется, эта статья последняя на тему OPC UA. Не потому, что мне сказать более нечего. Есть веские причины.

Всем спасибо. Расходимся.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google photo

Для комментария используется ваша учётная запись Google. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s