# This file is part of Beremiz
# Copyright (C) 2019: Edouard TISSERANT
# See COPYING file for copyrights details.
from __future__ import absolute_import
from threading import RLock, Timer
from runtime.spawn_subprocess import Popen
from subprocess import Popen
from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor
from twisted.web.static import File
from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
from autobahn.websocket.protocol import WebSocketProtocol
from autobahn.twisted.resource import WebSocketResource
max_svghmi_sessions = None
svghmi_wait = PLCBinary.svghmi_wait
svghmi_wait.restype = ctypes.c_int # error or 0
svghmi_wait.argtypes = []
svghmi_continue_collect = ctypes.c_int.in_dll(PLCBinary, "svghmi_continue_collect")
svghmi_send_collect = PLCBinary.svghmi_send_collect
svghmi_send_collect.restype = ctypes.c_int # error or 0
svghmi_send_collect.argtypes = [
ctypes.POINTER(ctypes.c_uint32), # size
ctypes.POINTER(ctypes.c_void_p)] # data ptr
svghmi_reset = PLCBinary.svghmi_reset
svghmi_reset.restype = ctypes.c_int # error or 0
svghmi_reset.argtypes = [
svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch
svghmi_recv_dispatch.restype = ctypes.c_int # error or 0
svghmi_recv_dispatch.argtypes = [
ctypes.c_char_p] # data ptr
class HMISessionMgr(object):
self.multiclient_sessions = set()
self.watchdog_session = None
greatest = max(self.indexes)
holes = set(range(greatest)) - self.indexes
index = min(holes) if holes else greatest+1
def free_index(self, index):
self.indexes.remove(index)
def register(self, session):
global max_svghmi_sessions
if session.is_watchdog_session:
# Creating a new watchdog session closes pre-existing one
if self.watchdog_session is not None:
self.unregister(self.watchdog_session)
assert(self.session_count < max_svghmi_sessions)
self.watchdog_session = session
assert(self.session_count < max_svghmi_sessions)
self.multiclient_sessions.add(session)
session.session_index = self.next_index()
def unregister(self, session):
if session.is_watchdog_session :
if self.watchdog_session != session:
self.watchdog_session = None
self.multiclient_sessions.remove(session)
self.free_index(session.session_index)
for session in self.iter_sessions():
lst = list(self.multiclient_sessions)
if self.watchdog_session is not None:
lst = [self.watchdog_session]+lst
svghmi_session_manager = HMISessionMgr()
class HMISession(object):
def __init__(self, protocol_instance):
self.protocol_instance = protocol_instance
self._session_index = None
def is_watchdog_session(self):
return self.protocol_instance.has_watchdog
return self._session_index
def session_index(self, value):
self._session_index = value
return svghmi_reset(self.session_index)
self.protocol_instance.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
def onMessage(self, msg):
# pass message to the C side recieve_message()
return svghmi_recv_dispatch(self.session_index, len(msg), msg)
def sendMessage(self, msg):
self.protocol_instance.sendMessage(msg, True)
def __init__(self, initial_timeout, interval, callback):
self._callback = callback
self.initial_timeout = initial_timeout
def _start(self, rearm=False):
duration = self.interval if rearm else self.initial_timeout
self.timer = Timer(duration, self.trigger)
if self.timer is not None:
def feed(self, rearm=True):
# Don't repeat trigger periodically
# # wait for initial timeout on re-start
class HMIProtocol(WebSocketServerProtocol):
def __init__(self, *args, **kwargs):
self.has_watchdog = False
WebSocketServerProtocol.__init__(self, *args, **kwargs)
def onConnect(self, request):
self.has_watchdog = request.params.get("mode", [None])[0] == "watchdog"
return WebSocketServerProtocol.onConnect(self, request)
global svghmi_session_manager
assert(self._hmi_session is None)
_hmi_session = HMISession(self)
registered = svghmi_session_manager.register(_hmi_session)
self._hmi_session = _hmi_session
def onClose(self, wasClean, code, reason):
global svghmi_session_manager
if self._hmi_session is None : return
self._hmi_session.notify_closed()
svghmi_session_manager.unregister(self._hmi_session)
def onMessage(self, msg, isBinary):
if self._hmi_session is None : return
result = self._hmi_session.onMessage(msg)
if result == 1 and self.has_watchdog: # was heartbeat
if svghmi_watchdog is not None:
class HMIWebSocketServerFactory(WebSocketServerFactory):
svghmi_send_thread = None
# python's errno on windows seems to have no ENODATA
ENODATA = errno.ENODATA if hasattr(errno,"ENODATA") else None
global svghmi_session_manager
while svghmi_continue_collect:
for svghmi_session in svghmi_session_manager.iter_sessions():
res = svghmi_send_collect(
svghmi_session.session_index,
ctypes.byref(size), ctypes.byref(ptr))
svghmi_session.sendMessage(
ctypes.string_at(ptr.value,size.value))
# this happens when there is no data after wakeup
# because of hmi data refresh period longer than
# this happens when finishing
def AddPathToSVGHMIServers(path, factory, *args, **kwargs):
for k,v in svghmi_servers.iteritems():
svghmi_root, svghmi_listener, path_list = v
svghmi_root.putChild(path, factory(*args, **kwargs))
# Called by PLCObject at start
def _runtime_00_svghmi_start():
global svghmi_send_thread
# start a thread that call the C part of SVGHMI
svghmi_send_thread = Thread(target=SendThreadProc, name="SVGHMI Send")
svghmi_send_thread.start()
# Called by PLCObject at stop
def _runtime_00_svghmi_stop():
global svghmi_send_thread, svghmi_session
svghmi_session_manager.close_all()
# plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread
svghmi_send_thread.join()
svghmi_send_thread = None
def render_GET(self, request):
request.setHeader(b"Cache-Control", b"no-cache, no-store")
return File.render_GET(self, request)