note about peripherals
[msc-thesis1617.git] / arch.itasks.tex
1 The server part of the system is written in \gls{iTasks}. Functions for
2 managing devices, \glspl{Task} and \glspl{SDS} are available. Furthermore, an
3 interactive web application has been created that provides an interactive
4 management console for the \gls{mTask} system. This interface provides
5 functionality to list \glspl{SDS}, add and remove \glspl{Task}, administrate
6 devices and view the state of the system.
7
8 \subsection{Device Storage}
9 Everything that a device encompasses is stored in the \CI{MTaskDevice} record
10 type which is in turn stored in an \gls{SDS}. This includes management for the
11 \glspl{SDS} and \glspl{Task} stored on the device. The \CI{MTaskDevice}
12 definition is shown in Listing~\ref{lst:mtaskdevice} accompanied with the
13 necessary classes and sub types. Devices added to the system must be reachable
14 asynchronously. This implies that the programmer only needs to keep hold of
15 the reference to the device and not the actual device record.
16
17 \begin{lstlisting}[caption={Device type},label={lst:mtaskdevice}]
18 :: Channels :== ([MTaskMSGRecv], [MTaskMSGSend], Bool)
19 :: BCState = ... // Compiler state, explained in later sections
20 :: MTaskDeviceSpec = ... // Also explained in later sections
21 :: MTaskMSGRecv = ... // Message format, explained in later sections
22 :: MTaskMSGSend = ... // Also explained in later sections
23 :: MTaskResource
24 = TCPDevice TCPSettings
25 | SerialDevice TTYSettings
26 | ...
27 :: MTaskDevice =
28 { deviceTask :: Maybe TaskId
29 , deviceError :: Maybe String
30 , deviceChannels :: String
31 , deviceName :: String
32 , deviceState :: BCState
33 , deviceTasks :: [MTaskTask]
34 , deviceResource :: MTaskResource
35 , deviceSpec :: Maybe MTaskDeviceSpec
36 , deviceShares :: [MTaskShare]
37 }
38
39 channels :: MTaskDevice -> Shared Channels
40
41 class MTaskDuplex a where
42 synFun :: a (Shared Channels) -> Task ()
43 \end{lstlisting}
44
45 The \CI{deviceResource} component of the record must implement the
46 \CI{MTaskDuplex} interface that provides a function that launches a \gls{Task}
47 used for synchronizing the channels. The \CI{deviceChannels} field can be used
48 to get the memory \gls{SDS} containing the channels. This field does not
49 contain the channels itself because they update often. The field is used to
50 get a memory \gls{SDS} containing the actual channel data when calling the
51 \CI{channels} function. The \CI{deviceTask} stores the \gls{Task}-id for this
52 \gls{Task} when active so that it can be checked upon. This top-level task has
53 the duty to report exceptions and errors as they are thrown by setting the
54 \CI{deviceError} field. All communication goes via these channels. To send a
55 message to the device, the system just puts it in the channels. Messages sent
56 from the client to the server are also placed in there. In the case of the
57 \gls{TCP} device type, the \gls{Task} is just a simple wrapper around the
58 existing \CI{tcpconnect} function in \gls{iTasks}. In case of a device
59 connected by a serial connection, it uses the newly developed serial port
60 library of \gls{Clean}\footnote{\url{%
61 https://gitlab.science.ru.nl/mlubbers/CleanSerial}}. The implementation and
62 semantics for the \CI{MTaskMSGRecv} and \CI{MTaskMSGSend} types are given in
63 Section~\ref{sec:communication}.
64
65 Besides all the communication information, the record also keeps track of the
66 \glspl{Task} currently on the device, the compiler state (see
67 Section~\ref{sec:compiler}) and the according \glspl{SDS}. Finally, it stores
68 the specification of the device that is received when connecting. All of this
69 is given in Listing~\ref{lst:mtaskdevice}. The definitions of the message
70 format are explained in the following section.
71
72 \subsection{Shares}
73 \Glspl{SDS} on the device can be accessed by both the device and the server.
74 While it would be possible to only store the \glspl{SDS} on the device, this
75 would require a lot of communication because every read operation will then
76 result in sending messages to-and-fro the device. Thus, the \gls{Task}
77 requesting the shared information can just be provided with the synchronized
78 value. As mentioned before, the device has to explicitly publish an update.
79 This has the implication that the server and the client can get out of sync.
80 However, this is by design and well documented. In the current system, an
81 \gls{SDS} can only reside on a single device.
82
83 % Single share per share
84 There are several possible approaches for storing \glspl{SDS} on the server
85 each with their own level of control. A possible way is to --- in the device
86 record --- add a list of references to \gls{iTasks}-\glspl{SDS} that represent
87 the \gls{SDS} on the device. The problem with this is the fact that an
88 \gls{SDS} can become an orphan. The \gls{SDS} is still accessible even when the
89 device is long gone. There is no way of knowing whether the \gls{SDS} is
90 unreachable because of the device being gone, or the \gls{SDS} itself is gone
91 on the device. Accessing the \gls{SDS} happens by calling the \CI{get},
92 \CI{set} and \CI{upd} functions directory on the actual \gls{SDS}.
93
94 % Single share per device
95 Another approach would be to have reference to an \gls{SDS} containing a table
96 of \gls{SDS} values per device. This approach suffers the same orphan problem
97 as before. Accessing a single \gls{SDS} on the device happens by calling the
98 \CI{get}, \CI{set} and \CI{upd} functions on the actual table \gls{SDS} with an
99 applied \CI{mapReadWrite}. Using parametric lenses can circumvent the problem
100 of watchers getting notified for other shares that are written. Error handling
101 is better than the previously mentioned approach because an \gls{SDS} can know
102 whether the \gls{SDS} has really gone because it will not be available anymore
103 in the table. It still does not know whether the device is still available.
104
105 % One share to rule them all
106 Finally, all devices containing all of their \glspl{SDS} in a table could be
107 stored in a single big \gls{SDS}. While the \CI{mapReadWrite} functions require
108 a bit more logic, they can determine the source of the error and act upon it.
109 Also, the parametric lenses must contain more logic. A downside of this
110 approach is that updating a single \gls{SDS} requires an update of the entire
111 object. In practise, this is not a real issue since almost all information can
112 be reused and \gls{SDS} residing on devices are often not updated with a very
113 high frequency.
114
115 \subsection{Parametric Lenses}
116 The type for the parametric lens of the \gls{SDS} containing all devices is
117 \CI{Maybe (MTaskDevice, Int)}. There are several levels of abstraction that
118 have to be introduced. First, the \gls{SDS} responsible for storing the entire
119 list of devices is called the global \gls{SDS}. Secondly, an \gls{SDS} can focus
120 on a single device, such \glspl{SDS} are called local \glspl{SDS}. Finally, an
121 \gls{SDS} can focus on a single \gls{SDS} on a single device. These \glspl{SDS}
122 are called share \glspl{SDS}. Using parametric lenses, the notifications can be
123 directed to only the watchers interested. Moreover, using parametric lenses,
124 the \gls{SDS} can know whether it is updating a single \gls{SDS} on a single
125 device and synchronize the value with the actual device. This means that when
126 writing to a share \gls{SDS} the update is also transformed to messages that
127 are put in the channels of the corresponding device to also notify the device
128 of the update. The \gls{SDS} is tailor-made and uses an actual standard
129 \gls{SDS} that writes to a file or memory as the storage. The tailor-made read
130 and write functions are only used to detect whether it is required to send an
131 update to the actual device.
132
133 Listing~\ref{lst:actualdev} shows the implementation of the big \gls{SDS}. From
134 this \gls{SDS} all other \glspl{SDS} are derived. The following paragraphs show
135 how this is achieved for the global \gls{SDS} local \gls{SDS} and the share
136 \gls{SDS}. In the big \gls{SDS}, reading the value is just a matter of reading
137 the standard \gls{SDS} that serves as the actual storage of the \gls{SDS}. The
138 derived shares will filter the output read accordingly. Writing the share
139 requires some extra work because it might be possible that an actual device has
140 to be notified. First, the actual storage of the \gls{SDS} is written. If the
141 parameter was \CI{Nothing} --- the global \gls{SDS} --- the write operation is
142 done. If the parameter was \CI{Just (d, -1)} --- a local \gls{SDS} --- nothing
143 has to be done as well. The final case is the special case, when the parameter
144 is \CI{Just (d, i)}, this means that the \gls{SDS} was focussed on device
145 \CI{d} and \gls{SDS} \CI{i} and thus it needs to write to it. First it locates
146 the device in the list, followed by the location of the share to check whether
147 is still exists. Finally the actual update messages are added to the device
148 channels using the \CI{sendMessagesIW} function.
149
150 All of the methods share the same \CI{SDSNotifyPred p} which is a function
151 \CI{p -> Bool} and determines for the given \CI{p} whether a notification is
152 required. The predicate function has the \CI{p} of the writer curried in and
153 can determine whether the second argument --- the reader --- needs to be
154 notified. In practice, the reader only needs to be notified when the parameters
155 are exactly the same.
156
157 \begin{lstlisting}[label={lst:actualdev},%
158 caption={Device \gls{SDS}}]
159 ($<) a fb = fmap (const a) fb
160
161 deviceStore :: RWShared (Maybe (MTaskDevice, Int)) [MTaskDevice] [MTaskDevice]
162 deviceStore = SDSSource {SDSSource | name="deviceStore", read=realRead, write=realWrite}
163 where
164 realRead :: (Maybe (MTaskDevice,Int)) *IWorld -> (MaybeError TaskException [MTaskDevice], *IWorld)
165 realRead p iw = read realDeviceStore iw
166
167 realWrite :: (Maybe (MTaskDevice,Int)) [MTaskDevice] *IWorld -> (MaybeError TaskException (SDSNotifyPred (Maybe (MTaskDevice,Int))), *IWorld)
168 realWrite mi w iw
169 # (merr, iw) = write w realDeviceStore iw
170 | isError merr || isNothing mi = (merr $> gEq{|*|} mi, iw)
171 # (Just (dev, ident)) = mi
172 | ident == -1 = (merr $> gEq{|*|} mi, iw)
173 = case find ((==)dev) w of
174 Nothing = (Error $ exception "Device lost", iw)
175 Just {deviceShares} = case find (\d->d.identifier == ident) deviceShares of
176 Nothing = (Error $ exception "Share lost", iw)
177 Just s = case sendMessagesIW [MTUpd ident s.MTaskShare.value] dev iw of
178 (Error e, iw) = (Error e, iw)
179 (Ok _, iw) = (Ok $ gEq{|*|} mi, iw)
180
181 realDeviceStore :: Shared [MTaskDevice]
182 realDeviceStore = sharedStore "mTaskDevices" []
183 \end{lstlisting}
184
185 \subsubsection{Global \glspl{SDS}}
186 Accessing the global \gls{SDS} is just a matter of focussing the
187 \CI{deviceStore} to \CI{Nothing}. In this way, \glspl{Task} watching the
188 \gls{SDS} will only be notified if a device is added or removed. The actual
189 code is as follows:
190
191 \begin{lstlisting}[caption={Global \gls{SDS}}]
192 deviceStoreNP :: Shared [MTaskDevice]
193 deviceStoreNP = sdsFocus Nothing deviceStore
194 \end{lstlisting}
195
196 \subsubsection{Local \glspl{SDS}}
197 Accessing a single device can be done using the \CI{deviceShare} function.
198 Since device comparison is shallow, the device that is given is allowed to be
199 an old version. The identification of devices is solely done on the name of the
200 channels and is unique throughout the system. This type of \gls{SDS} will only
201 be notified if the device itself changed. It will not be notified when only a
202 single \gls{SDS} on the device changes. The implementation is as follows:
203
204 \begin{lstlisting}[caption={Local \gls{SDS}}]
205 deviceShare :: MTaskDevice -> Shared MTaskDevice
206 deviceShare d = mapReadWriteError
207 ( \ds->case find ((==)d) of
208 Nothing = exception "Device lost"
209 Just d = Ok d)
210 , \w ds->case splitWith ((==)d) ds of
211 ([], _) = Error $ exception "Device lost"
212 ([_:_], ds) = Ok $ Just [w:ds])
213 $ sdsFocus (Just (d, -1)) deviceStore
214 \end{lstlisting}
215
216 \subsubsection{Local-\gls{SDS} specific \glspl{SDS}}
217 A single \gls{SDS} on a single device can be accessed using the \CI{shareShare}
218 function. This function focusses the global \gls{SDS} on a single \gls{SDS}
219 from a single device. It can use old share references in the same fashion as
220 the local \gls{SDS} only treating it as references. It uses the
221 \CI{mapReadWrite} functions to serve the correct part of the information. When
222 a \gls{Task} writes to this \gls{SDS}, the global \gls{SDS} will know this
223 through the parameter and propagate the value to the device.
224
225 \begin{lstlisting}[caption={Local \gls{SDS}}]
226 shareShare :: MTaskDevice MTaskShare -> Shared BCValue
227 shareShare dev share = sdsFocus ()
228 $ mapReadWriteError (read, write)
229 $ sdsFocus (Just (dev, share.identifier))
230 $ deviceStore
231 where
232 read :: [MTaskDevice] -> MaybeError TaskException BCValue
233 read devs = case find ((==)dev) devs of
234 Nothing = exception "Device lost"
235 Just d = case find ((==)share) d.deviceShares of
236 Nothing = exception "Share lost"
237 Just s = Ok s.MTaskShare.value
238
239 write :: BCValue [MTaskDevice] -> MaybeError TaskException (Maybe [MTaskDevice])
240 write val devs = case partition ((==)dev) devs of
241 ([], _) = Error $ exception "Device doesn't exist anymore"
242 ([_,_:_], _) = Error $ exception "Multiple matching devices"
243 ([d=:{deviceShares}], devs) = case partition ((==)share) deviceShares of
244 ([], _) = Error $ exception "Share doesn't exist anymore"
245 ([_,_:_], _) = Error $ exception "Multiple matching shares"
246 ([s], shares) = Ok $ Just [{MTaskDevice | d &
247 deviceShares=[{MTaskShare | s & value=val}:shares]}:devs]
248 \end{lstlisting}