svghmi/svghmi_server.py
branchsvghmi
changeset 3270 38f7122ccbf9
parent 3269 5d174cdf4d98
child 3271 561dbd1e3e04
equal deleted inserted replaced
3269:5d174cdf4d98 3270:38f7122ccbf9
    21 
    21 
    22 from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
    22 from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
    23 from autobahn.websocket.protocol import WebSocketProtocol
    23 from autobahn.websocket.protocol import WebSocketProtocol
    24 from autobahn.twisted.resource import  WebSocketResource
    24 from autobahn.twisted.resource import  WebSocketResource
    25 
    25 
    26 # TODO multiclient :
    26 max_svghmi_sessions = None
    27 # session list lock
       
    28 # svghmi_sessions = []
       
    29 # svghmi_watchdogs = []
       
    30 
       
    31 svghmi_session = None
       
    32 svghmi_watchdog = None
    27 svghmi_watchdog = None
    33 
    28 
    34 svghmi_send_collect = PLCBinary.svghmi_send_collect
    29 svghmi_send_collect = PLCBinary.svghmi_send_collect
    35 svghmi_send_collect.restype = ctypes.c_int # error or 0
    30 svghmi_send_collect.restype = ctypes.c_int # error or 0
    36 svghmi_send_collect.argtypes = [
    31 svghmi_send_collect.argtypes = [
       
    32     ctypes.c_uint32,  # index
    37     ctypes.POINTER(ctypes.c_uint32),  # size
    33     ctypes.POINTER(ctypes.c_uint32),  # size
    38     ctypes.POINTER(ctypes.c_void_p)]  # data ptr
    34     ctypes.POINTER(ctypes.c_void_p)]  # data ptr
    39 # TODO multiclient : switch to arrays
       
    40 
    35 
    41 svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch
    36 svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch
    42 svghmi_recv_dispatch.restype = ctypes.c_int # error or 0
    37 svghmi_recv_dispatch.restype = ctypes.c_int # error or 0
    43 svghmi_recv_dispatch.argtypes = [
    38 svghmi_recv_dispatch.argtypes = [
       
    39     ctypes.c_uint32,  # index
    44     ctypes.c_uint32,  # size
    40     ctypes.c_uint32,  # size
    45     ctypes.c_char_p]  # data ptr
    41     ctypes.c_char_p]  # data ptr
    46 # TODO multiclient : switch to arrays
    42 
       
    43 class HMISessionMgr(object):
       
    44     def __init__(self):
       
    45         self.multiclient_sessions = set()
       
    46         self.watchdog_session = None
       
    47         self.session_count = 0
       
    48         self.lock = RLock()
       
    49         self.indexes = set()
       
    50 
       
    51     def next_index(self):
       
    52         if self.indexes:
       
    53             greatest = max(self.indexes)
       
    54             holes = set(range(greatest)) - self.indexes
       
    55             index = min(holes) if holes else greatest+1
       
    56         else:
       
    57             index = 0
       
    58         self.indexes.add(index)
       
    59         return index
       
    60 
       
    61     def free_index(self, index):
       
    62         self.indexes.remove(index)
       
    63 
       
    64     def register(self, session):
       
    65         global max_svghmi_sessions
       
    66         with self.lock:
       
    67             if session.is_watchdog_session:
       
    68                 # Creating a new watchdog session closes pre-existing one
       
    69                 if self.watchdog_session is not None:
       
    70                     self.watchdog_session.close()
       
    71                     self.unregister(self.watchdog_session)
       
    72                     self.free_index(self.watchdog_session.session_index)
       
    73                 else:
       
    74                     assert(self.session_count < max_svghmi_sessions)
       
    75                     self.session_count += 1
       
    76 
       
    77                 self.watchdog_session = session
       
    78             else:
       
    79                 assert(self.session_count < max_svghmi_sessions)
       
    80                 self.multiclient_sessions.add(session)
       
    81                 self.session_count += 1
       
    82             session.session_index = self.next_index()
       
    83 
       
    84     def unregister(self, session):
       
    85         with self.lock:
       
    86             if session.is_watchdog_session:
       
    87                 assert(self.watchdog_session == session)
       
    88                 self.watchdog_session = None
       
    89             else:
       
    90                 self.multiclient_sessions.remove(self)
       
    91             self.free_index(self.watchdog_session.get_index())
       
    92             self.session_count -= 1
       
    93         
       
    94     def close_all(self):
       
    95         with self.lock:
       
    96             close_list = list(self.multiclient_sessions)
       
    97             if self.watchdog_session:
       
    98                 close_list.append(self.watchdog_session)
       
    99             for session in close_list:
       
   100                 session.close()
       
   101                 self.unregister(session)
       
   102         
       
   103     def iter_sessions(self):
       
   104         with self.lock:
       
   105             nxt_session = self.watchdog_session
       
   106         if nxt_session is not None:
       
   107             yield nxt_session
       
   108         idx = 0
       
   109         while True:
       
   110             with self.lock:
       
   111                 if idx >= len(self.multiclient_sessions):
       
   112                     return
       
   113                 nxt_session = self.multiclient_sessions[idx]
       
   114             yield nxt_session
       
   115             idx += 1
       
   116 
       
   117 
       
   118 svghmi_session_manager = HMISessionMgr()
       
   119 
    47 
   120 
    48 class HMISession(object):
   121 class HMISession(object):
    49     def __init__(self, protocol_instance):
   122     def __init__(self, protocol_instance):
    50         global svghmi_session
       
    51         
       
    52         # Single client :
       
    53         # Creating a new HMISession closes pre-existing HMISession
       
    54         if svghmi_session is not None:
       
    55             svghmi_session.close()
       
    56         svghmi_session = self
       
    57         self.protocol_instance = protocol_instance
   123         self.protocol_instance = protocol_instance
    58 
   124         self._session_index = None
    59         # TODO multiclient :
   125 
    60         # svghmi_sessions.append(self)
   126     @property
    61         # get a unique bit index amont other svghmi_sessions,
   127     def is_watchdog_session(self):
    62         # so that we can match flags passed by C->python callback
   128         return self.protocol_instance.has_watchdog
       
   129 
       
   130     @property
       
   131     def session_index(self):
       
   132         return self._session_index
       
   133 
       
   134     @session_index.setter
       
   135     def session_index(self, value):
       
   136         self._session_index = value
    63 
   137 
    64     def close(self):
   138     def close(self):
    65         global svghmi_session
       
    66         if svghmi_session == self:
       
    67             svghmi_session = None
       
    68         self.protocol_instance.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
   139         self.protocol_instance.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
    69 
   140 
    70     def onMessage(self, msg):
   141     def onMessage(self, msg):
    71         # pass message to the C side recieve_message()
   142         # pass message to the C side recieve_message()
    72         return svghmi_recv_dispatch(len(msg), msg)
   143         return svghmi_recv_dispatch(self.session_index, len(msg), msg)
    73 
       
    74         # TODO multiclient : pass client index as well
       
    75 
   144 
    76     def sendMessage(self, msg):
   145     def sendMessage(self, msg):
    77         self.protocol_instance.sendMessage(msg, True)
   146         self.protocol_instance.sendMessage(msg, True)
    78         return 0
   147         return 0
    79 
   148 
   109             self._stop()
   178             self._stop()
   110             self._start(rearm)
   179             self._start(rearm)
   111 
   180 
   112     def trigger(self):
   181     def trigger(self):
   113         self._callback()
   182         self._callback()
   114         # wait for initial timeout on re-start
   183         # Don't repeat trigger periodically
   115         self.feed(rearm=False)
   184         # # wait for initial timeout on re-start
       
   185         # self.feed(rearm=False)
   116 
   186 
   117 class HMIProtocol(WebSocketServerProtocol):
   187 class HMIProtocol(WebSocketServerProtocol):
   118 
   188 
   119     def __init__(self, *args, **kwargs):
   189     def __init__(self, *args, **kwargs):
   120         self._hmi_session = None
   190         self._hmi_session = None
       
   191         self.has_watchdog = False
   121         WebSocketServerProtocol.__init__(self, *args, **kwargs)
   192         WebSocketServerProtocol.__init__(self, *args, **kwargs)
   122 
   193 
   123     def onConnect(self, request):
   194     def onConnect(self, request):
   124         self.has_watchdog = request.params.get("mode", [None])[0] == "watchdog"
   195         self.has_watchdog = request.params.get("mode", [None])[0] == "watchdog"
   125         return WebSocketServerProtocol.onConnect(self, request)
   196         return WebSocketServerProtocol.onConnect(self, request)
   126 
   197 
   127     def onOpen(self):
   198     def onOpen(self):
       
   199         global svghmi_session_manager
   128         assert(self._hmi_session is None)
   200         assert(self._hmi_session is None)
   129         self._hmi_session = HMISession(self)
   201         self._hmi_session = HMISession(self)
       
   202         svghmi_session_manager.register(self._hmi_session)
   130 
   203 
   131     def onClose(self, wasClean, code, reason):
   204     def onClose(self, wasClean, code, reason):
       
   205         global svghmi_session_manager
       
   206         svghmi_session_manager.unregister(self._hmi_session)
   132         self._hmi_session = None
   207         self._hmi_session = None
   133 
   208 
   134     def onMessage(self, msg, isBinary):
   209     def onMessage(self, msg, isBinary):
       
   210         global svghmi_watchdog
   135         assert(self._hmi_session is not None)
   211         assert(self._hmi_session is not None)
   136 
   212 
   137         result = self._hmi_session.onMessage(msg)
   213         result = self._hmi_session.onMessage(msg)
   138         if result == 1 :  # was heartbeat
   214         if result == 1 and self.has_watchdog:  # was heartbeat
   139             if svghmi_watchdog is not None:
   215             if svghmi_watchdog is not None:
   140                 svghmi_watchdog.feed()
   216                 svghmi_watchdog.feed()
   141 
   217 
   142 class HMIWebSocketServerFactory(WebSocketServerFactory):
   218 class HMIWebSocketServerFactory(WebSocketServerFactory):
   143     protocol = HMIProtocol
   219     protocol = HMIProtocol
   144 
   220 
   145 svghmi_servers = {}
   221 svghmi_servers = {}
   146 svghmi_send_thread = None
   222 svghmi_send_thread = None
   147 
   223 
   148 def SendThreadProc():
   224 def SendThreadProc():
   149     global svghmi_session
   225     global svghmi_session_manager
   150     size = ctypes.c_uint32()
   226     size = ctypes.c_uint32()
   151     ptr = ctypes.c_void_p()
   227     ptr = ctypes.c_void_p()
   152     res = 0
   228     res = 0
   153     while True:
   229     finished = False
   154         res=svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr))
   230     while not(finished):
   155         if res == 0:
   231         for svghmi_session in svghmi_session_manager.iter_sessions():
   156             # TODO multiclient : dispatch to sessions
   232             res = svghmi_send_collect(
   157             if svghmi_session is not None:
   233                 svghmi_session.session_index,
   158                 svghmi_session.sendMessage(ctypes.string_at(ptr.value,size.value))
   234                 ctypes.byref(size), ctypes.byref(ptr))
   159         elif res == errno.ENODATA:
   235             if res == 0:
   160             # this happens when there is no data after wakeup
   236                 svghmi_session.sendMessage(
   161             # because of hmi data refresh period longer than PLC common ticktime
   237                     ctypes.string_at(ptr.value,size.value))
   162             pass 
   238             elif res == errno.ENODATA:
   163         else:
   239                 # this happens when there is no data after wakeup
   164             # this happens when finishing
   240                 # because of hmi data refresh period longer than 
   165             break
   241                 # PLC common ticktime
       
   242                 pass 
       
   243             else:
       
   244                 # this happens when finishing
       
   245                 finished = True
       
   246                 break
   166 
   247 
   167 def AddPathToSVGHMIServers(path, factory):
   248 def AddPathToSVGHMIServers(path, factory):
   168     for k,v in svghmi_servers.iteritems():
   249     for k,v in svghmi_servers.iteritems():
   169         svghmi_root, svghmi_listener, path_list = v
   250         svghmi_root, svghmi_listener, path_list = v
   170         svghmi_root.putChild(path, factory())
   251         svghmi_root.putChild(path, factory())
   180 
   261 
   181 # Called by PLCObject at stop
   262 # Called by PLCObject at stop
   182 def _runtime_00_svghmi_stop():
   263 def _runtime_00_svghmi_stop():
   183     global svghmi_send_thread, svghmi_session
   264     global svghmi_send_thread, svghmi_session
   184 
   265 
   185     if svghmi_session is not None:
   266     svghmi_session_manager.close_all()
   186         svghmi_session.close()
   267 
   187     # plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread
   268     # plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread
   188     svghmi_send_thread.join()
   269     svghmi_send_thread.join()
   189     svghmi_send_thread = None
   270     svghmi_send_thread = None
   190 
   271 
   191 
   272