# 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
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
# TODO multiclient : switch to arrays
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
# TODO multiclient : switch to arrays
class HMISession(object):
def __init__(self, protocol_instance):
# Creating a new HMISession closes pre-existing HMISession
if svghmi_session is not None:
self.protocol_instance = protocol_instance
# svghmi_sessions.append(self)
# get a unique bit index amont other svghmi_sessions,
# so that we can match flags passed by C->python callback
if svghmi_session == self:
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(len(msg), msg)
# TODO multiclient : pass client index as well
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):
# wait for initial timeout on re-start
class HMIProtocol(WebSocketServerProtocol):
def __init__(self, *args, **kwargs):
WebSocketServerProtocol.__init__(self, *args, **kwargs)
assert(self._hmi_session is None)
self._hmi_session = HMISession(self)
def onClose(self, wasClean, code, reason):
def onMessage(self, msg, isBinary):
assert(self._hmi_session is not None)
result = self._hmi_session.onMessage(msg)
if result == 1 : # was heartbeat
if svghmi_watchdog is not None:
class HMIWebSocketServerFactory(WebSocketServerFactory):
svghmi_send_thread = None
res=svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr))
# TODO multiclient : dispatch to sessions
if svghmi_session is not None:
svghmi_session.sendMessage(ctypes.string_at(ptr.value,size.value))
elif res == errno.ENODATA:
# this happens when there is no data after wakeup
# because of hmi data refresh period longer than PLC common ticktime
# this happens when finishing
print("SVGHMI watchdog trigger")
# Called by PLCObject at start
def _runtime_00_svghmi_start():
global svghmi_listener, svghmi_root, svghmi_send_thread
svghmi_root.putChild("ws", WebSocketResource(HMIWebSocketServerFactory()))
svghmi_listener = reactor.listenTCP(8008, Site(svghmi_root))
# 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_listener, svghmi_root, svghmi_send_thread, svghmi_session
if svghmi_session is not None:
svghmi_root.delEntity("ws")
svghmi_listener.stopListening()
# 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)