4 # This file is part of Beremiz |
4 # This file is part of Beremiz |
5 # Copyright (C) 2019: Edouard TISSERANT |
5 # Copyright (C) 2019: Edouard TISSERANT |
6 # See COPYING file for copyrights details. |
6 # See COPYING file for copyrights details. |
7 |
7 |
8 from __future__ import absolute_import |
8 from __future__ import absolute_import |
|
9 import errno |
9 |
10 |
10 from twisted.web.server import Site |
11 from twisted.web.server import Site |
11 from twisted.web.resource import Resource |
12 from twisted.web.resource import Resource |
12 from twisted.internet import reactor |
13 from twisted.internet import reactor |
13 from twisted.web.static import File |
14 from twisted.web.static import File |
14 |
15 |
15 from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol |
16 from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol |
|
17 from autobahn.websocket.protocol import WebSocketProtocol |
16 from autobahn.twisted.resource import WebSocketResource |
18 from autobahn.twisted.resource import WebSocketResource |
17 |
19 |
18 # TODO multiclient : |
20 # TODO multiclient : |
19 # session list lock |
21 # session list lock |
20 # svghmi_sessions = [] |
22 # svghmi_sessions = [] |
23 |
25 |
24 svghmi_send_collect = PLCBinary.svghmi_send_collect |
26 svghmi_send_collect = PLCBinary.svghmi_send_collect |
25 svghmi_send_collect.restype = ctypes.c_int # error or 0 |
27 svghmi_send_collect.restype = ctypes.c_int # error or 0 |
26 svghmi_send_collect.argtypes = [ |
28 svghmi_send_collect.argtypes = [ |
27 ctypes.POINTER(ctypes.c_uint32), # size |
29 ctypes.POINTER(ctypes.c_uint32), # size |
28 ctypes.POINTER(ctypes.c_char_p)] # data ptr |
30 ctypes.POINTER(ctypes.c_void_p)] # data ptr |
29 # TODO multiclient : switch to arrays |
31 # TODO multiclient : switch to arrays |
30 |
32 |
31 svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch |
33 svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch |
32 svghmi_recv_dispatch.restype = ctypes.c_int # error or 0 |
34 svghmi_recv_dispatch.restype = ctypes.c_int # error or 0 |
33 svghmi_recv_dispatch.argtypes = [ |
35 svghmi_recv_dispatch.argtypes = [ |
36 # TODO multiclient : switch to arrays |
38 # TODO multiclient : switch to arrays |
37 |
39 |
38 class HMISession(object): |
40 class HMISession(object): |
39 def __init__(self, protocol_instance): |
41 def __init__(self, protocol_instance): |
40 global svghmi_session |
42 global svghmi_session |
41 |
43 |
42 # TODO: kill existing session for robustness |
44 # Single client : |
43 assert(svghmi_session is None) |
45 # Creating a new HMISession closes pre-existing HMISession |
44 |
46 if svghmi_session is not None: |
|
47 svghmi_session.close() |
45 svghmi_session = self |
48 svghmi_session = self |
46 self.protocol_instance = protocol_instance |
49 self.protocol_instance = protocol_instance |
47 |
50 |
48 # TODO multiclient : |
51 # TODO multiclient : |
49 # svghmi_sessions.append(self) |
52 # svghmi_sessions.append(self) |
50 # get a unique bit index amont other svghmi_sessions, |
53 # get a unique bit index amont other svghmi_sessions, |
51 # so that we can match flags passed by C->python callback |
54 # so that we can match flags passed by C->python callback |
52 |
55 |
53 def __del__(self): |
56 def close(self): |
54 global svghmi_session |
57 global svghmi_session |
55 assert(svghmi_session) |
58 if svghmi_session == self: |
56 svghmi_session = None |
59 svghmi_session = None |
57 |
60 self.protocol_instance.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL) |
58 # TODO multiclient : |
|
59 # svghmi_sessions.remove(self) |
|
60 |
61 |
61 def onMessage(self, msg): |
62 def onMessage(self, msg): |
62 # pass message to the C side recieve_message() |
63 # pass message to the C side recieve_message() |
63 svghmi_recv_dispatch(len(msg), msg) |
64 svghmi_recv_dispatch(len(msg), msg) |
64 |
65 |
65 # TODO multiclient : pass client index as well |
66 # TODO multiclient : pass client index as well |
66 |
67 |
67 |
68 |
68 def sendMessage(self, msg): |
69 def sendMessage(self, msg): |
69 self.sendMessage(msg, True) |
70 self.protocol_instance.sendMessage(msg, True) |
|
71 return 0 |
70 |
72 |
71 class HMIProtocol(WebSocketServerProtocol): |
73 class HMIProtocol(WebSocketServerProtocol): |
72 |
74 |
73 def __init__(self, *args, **kwargs): |
75 def __init__(self, *args, **kwargs): |
74 self._hmi_session = None |
76 self._hmi_session = None |
75 WebSocketServerProtocol.__init__(self, *args, **kwargs) |
77 WebSocketServerProtocol.__init__(self, *args, **kwargs) |
76 |
78 |
77 def onOpen(self): |
79 def onOpen(self): |
|
80 assert(self._hmi_session is None) |
78 self._hmi_session = HMISession(self) |
81 self._hmi_session = HMISession(self) |
79 print "open" |
|
80 |
82 |
81 def onClose(self, wasClean, code, reason): |
83 def onClose(self, wasClean, code, reason): |
82 del self._hmi_session |
|
83 self._hmi_session = None |
84 self._hmi_session = None |
84 print "close" |
|
85 |
85 |
86 def onMessage(self, msg, isBinary): |
86 def onMessage(self, msg, isBinary): |
|
87 assert(self._hmi_session is not None) |
87 self._hmi_session.onMessage(msg) |
88 self._hmi_session.onMessage(msg) |
88 # print msg |
|
89 #self.sendMessage(msg, binary) |
|
90 |
89 |
91 class HMIWebSocketServerFactory(WebSocketServerFactory): |
90 class HMIWebSocketServerFactory(WebSocketServerFactory): |
92 protocol = HMIProtocol |
91 protocol = HMIProtocol |
93 |
92 |
94 svghmi_root = None |
93 svghmi_root = None |
96 svghmi_send_thread = None |
95 svghmi_send_thread = None |
97 |
96 |
98 def SendThreadProc(): |
97 def SendThreadProc(): |
99 global svghmi_session |
98 global svghmi_session |
100 size = ctypes.c_uint32() |
99 size = ctypes.c_uint32() |
101 ptr = ctypes.c_char_p() |
100 ptr = ctypes.c_void_p() |
102 res = 0 |
101 res = 0 |
103 while svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr)) == 0 and \ |
102 while True: |
104 svghmi_session is not None and \ |
103 res=svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr)) |
105 svghmi_session.sendMessage(ctypes.string_at(ptr,size)) == 0: |
104 if res == 0: |
106 pass |
105 # TODO multiclient : dispatch to sessions |
|
106 if svghmi_session is not None: |
|
107 svghmi_session.sendMessage(ctypes.string_at(ptr.value,size.value)) |
|
108 elif res not in [errno.EAGAIN, errno.ENODATA]: |
|
109 break |
107 |
110 |
108 # TODO multiclient : dispatch to sessions |
|
109 |
111 |
110 |
112 |
111 |
113 |
112 # Called by PLCObject at start |
114 # Called by PLCObject at start |
113 def _runtime_svghmi0_start(): |
115 def _runtime_svghmi0_start(): |
123 svghmi_send_thread.start() |
125 svghmi_send_thread.start() |
124 |
126 |
125 |
127 |
126 # Called by PLCObject at stop |
128 # Called by PLCObject at stop |
127 def _runtime_svghmi0_stop(): |
129 def _runtime_svghmi0_stop(): |
128 global svghmi_listener, svghmi_root, svghmi_send_thread |
130 global svghmi_listener, svghmi_root, svghmi_send_thread, svghmi_session |
|
131 if svghmi_session is not None: |
|
132 svghmi_session.close() |
129 svghmi_root.delEntity("ws") |
133 svghmi_root.delEntity("ws") |
130 svghmi_root = None |
134 svghmi_root = None |
131 svghmi_listener.stopListening() |
135 svghmi_listener.stopListening() |
132 svghmi_listener = None |
136 svghmi_listener = None |
133 # plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread |
137 # plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread |