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()) |