results
[msc-thesis1617.git] / results.arch.tex
1 The goal of the total system is to facilitate an ecosystem in which an
2 \gls{iTasks}-system can add, change and remove devices at runtime. Moreover,
3 the \gls{iTasks}-system can send \glspl{mTask} --- compiled at runtime to
4 bytecode by the \gls{mTask}-view --- to the device. The device runs an
5 interpreter which can execute the \gls{Task}'s bytecode. Device profiles should
6 be persistent during reboots of the \gls{iTasks}-system. The methods of
7 interacting with \glspl{mTask} should be analogous to interacting with
8 \gls{iTasks}-\glspl{Task}. Meaning that programmers can access the \glspl{SDS}
9 made for a device in the same way as a regular \gls{SDS} and they can execute
10 \glspl{mTask} as if it where a normal \gls{iTasks}-\gls{Task}.
11
12 The following terms will be used throughout the following chapter:
13 \begin{itemize}
14 \item Device, Client
15
16 This denotes the actual device connected to the system. This can be a
17 real device such as a microcontroller but it can also just be a program
18 on the same machine as the server functioning as a client.
19 \item Server, \gls{iTasks}-System
20
21 This is the actual executable serving the \gls{iTasks} application. The
22 system contains \glspl{Task} taking care of the communication with the
23 clients.
24 \item System
25
26 The system describes the complete ecosystem, containing both the server
27 and the clients including the communication between them.
28 \item Engine
29
30 The runtime system of the client is called the engine. This program
31 handles communicating with the server and runs the interpreter for the
32 \glspl{Task} on the client.
33 \end{itemize}
34
35 \section{Devices}
36 A device is suitable for the system if it can run the engine.
37 The engine is compiled from one codebase and devices implement (part of) the
38 device specific interface. The shared codebase only uses standard \gls{C} and
39 no special libraries or tricks are used. Therefore, the code is compilable for
40 almost any device or system. Note that it is not needed to implement a full
41 interface. The full interface --- excluding the device specific settings --- is
42 listed in Appendix~\ref{app:device-interface}. The interface works in a
43 similar fashion as the \gls{EDSL}. Devices do not have to implement all
44 functionality, this is analogous to the fact that views do not have to
45 implement all type classes in the \gls{EDSL}. When the device connects for the
46 first time with a server the specifications of what is implemented is
47 communicated.
48
49 At the time of writing the following device families are supported and can run
50 the device software.
51 \begin{itemize}
52 \item \texttt{POSIX} compatible systems connected via \gls{TCP}.
53
54 This includes systems running \emph{Linux} and \emph{MacOS}.
55 \item \texttt{STM32} family microcontrollers supported by \texttt{ChibiOS}
56 connected via serial communication.
57
58 This is tested in particular on the \texttt{STM32f7x} series \gls{ARM}
59 development board.
60 \item Microcontrollers who are programmable in the \gls{Arduino} \gls{IDE}
61 connected via serial communication or via \gls{TCP} over WiFi.
62
63 This does not only include \gls{Arduino} compatible boards but also
64 other boards capable of running \gls{Arduino} code. A port of the
65 client has been made for the \texttt{ESP8266} powered \emph{NodeMCU}
66 that is connected via \gls{TCP} over WiFi. A port also has been made
67 for the regular \gls{Arduino} \emph{UNO} board which only boasts a
68 meager \emph{2K} of \emph{RAM}. The stack size and storage for such
69 small amount of \emph{RAM} have to be smaller than default but it still
70 suitable to hold a hand full of \glspl{Task}.
71 \end{itemize}
72
73 \subsection{Client}
74 \subsubsection*{Engine}
75 The client is in a constant loop listening for input and waiting to execute
76 \gls{Task}. The pseudocode for this is shown in Algorithm~\ref{alg:client}.
77
78 \todo{make algorithm}
79 \begin{algorithm}[H]
80 \KwData{\textbf{stack} stack, \textbf{time} $t, t_p$}
81
82 $t\leftarrow\text{now}()$\;
83 \Begin{
84 \While{true}{
85 $t_p\leftarrow t$\;
86 $t\leftarrow\text{now}()$\;
87 \If{notEmpty$($queue$)$}{
88 $task\leftarrow \text{queue.pop}()$\;
89 $task$.wait $\leftarrow task$.wait $-(t-t_p)$\;
90 \eIf{$task.wait>t_0$}{
91 queue.append$(task)$\;
92 }{
93 run\_task$(task)$\;
94 }
95 }
96 }
97 }
98 \caption{Engine pseudocode}\label{alg:client}
99 \end{algorithm}
100
101 \subsubsection*{Storage}
102 \glspl{Task} and \glspl{SDS} are stored on the client in one big memory space
103 that is fully allocated at the start of the program. The space could also have
104 been dynamically allocated but that would require using the heap which is
105 unwanted in small memory environments. \Glspl{Task} grow from the bottom up
106 and \glspl{SDS} grow from the top down. When a \gls{Task} or \gls{SDS} is
107 removed, all \glspl{Task} are relocated in the memory space to not leave
108 holes. Both \glspl{Task} and \glspl{SDS} are stored as structs and helper
109 functions are available to loop through them without having to fiddle in the
110 memory space. The instance for \glspl{Task} and \glspl{SDS} are shown in
111 Listing~\ref{lst:structs} accompanied by the helper functions for \glspl{Task}.
112 \Glspl{Task} consist the length, interval, last run time, id and the bytecode.
113 \Glspl{SDS} consist just of an id, value and type. The pointer to the bytecode
114 of the \gls{Task} always points to the location in the memory space.
115
116 \begin{lstlisting}[language=C,label={lst:structs},%
117 caption={The data type storing the \glspl{Task}}]
118 struct task {
119 uint16_t tasklength;
120 uint16_t interval;
121 unsigned long lastrun;
122 uint8_t taskid;
123 uint8_t *bc;
124 };
125
126 struct task *task_head(void);
127 struct task *task_next(struct task *t);
128
129 struct sds {
130 int id;
131 int value;
132 char type;
133 };
134
135 struct sds *sds_head(void);
136 struct sds *sds_next(struct sds *s);
137 \end{lstlisting}
138
139 \subsubsection*{Interpretation}
140 Execution of a \gls{Task} always start with prepared the stack and the program
141 counter and stack pointer are set to zero and the bottom respectively. When
142 finished, the interpreter executes one step at the time while the program
143 counter is smaller than the program length. The code for this is listed in
144 Listing~\ref{lst:interpr}. One execution step is basically a big switch
145 statement going over all possible bytecode instructions. Some instructions are
146 detailed upon in the listing. The \CI{BCPush} instruction is a little more
147 complicated in the real code because some decoding will take place as not all
148 \CI{BCValue}'s are of the same length and are encoded.
149
150 \begin{lstlisting}[language=C,label={lst:interpr},%
151 caption={Rough code outline for interpretation}]
152 #define f16(p) program[pc]*265+program[pc+1]
153
154 void run_task(struct task *t){
155 uint8_t *program = t->bc;
156 int plen = t->tasklength;
157 int pc = 0;
158 int sp = 0;
159 while(pc < plen){
160 switch(program[pc++]){
161 case BCNOP:
162 break;
163 case BCPUSH:
164 stack[sp++] = pc++ //Simplified
165 break;
166 case BCPOP:
167 sp--;
168 break;
169 case BCSDSSTORE:
170 sds_store(f16(pc), stack[--sp]);
171 pc+=2;
172 break;
173 // ...
174 case BCADD: trace("add");
175 stack[sp-2] = stack[sp-2] + stack[sp-1];
176 sp -= 1;
177 break;
178 // ...
179 case BCJMPT: trace("jmpt to %d", program[pc]);
180 pc = stack[--sp] ? program[pc]-1 : pc+1;
181 break;
182 }
183 \end{lstlisting}
184
185 \subsection{Specification}
186 The server stores a description for every device available in a record type
187 which are stored in a \gls{SDS}. From the macro settings in
188 the interface file, a profile is created for the device that describes the
189 specification. When a connection between the server and a client is established
190 the server will send a request for specification. The client will serialize his
191 specification and send it to the server so that the server knows what the
192 client is capable of. The exact specification is shown in
193 Listing~\ref{lst:devicespec} and stores the peripheral availability, the memory
194 available for storing \glspl{Task} and \glspl{SDS} and the size of the stack.
195
196 \begin{lstlisting}[label={lst:devicespec},
197 caption={Device specification for \glspl{mTask}}]
198 :: MTaskDeviceSpec =
199 { haveLed :: Bool
200 , haveLcd :: Bool
201 , have...
202 , bytesMemory :: Int
203 , stackSize :: Int
204 , aPins :: Int
205 , dPins :: Int
206 }
207 \end{lstlisting}
208
209 \section{iTasks}
210 The server part of the system is written in \gls{iTasks}. Functions for
211 managing devices, \glspl{Task} and \glspl{SDS} have been created to support the
212 functionality. An interactive application has been created that allows an
213 interactive management console for the \gls{mTask} system. This interface
214 provides functionality to list \glspl{SDS}, add \glspl{Task}, remove
215 \glspl{Task}, administrate devices and view the state of the system.
216
217 \subsection{Device Storage}
218 All devices that have been connected to the server are stored in a \gls{SDS}.
219 The \gls{SDS} contains a list of \CI{MTaskDevice}s. The \CI{MTaskDevice}
220 definition is shown in Listing~\ref{lst:mtaskdevice} accompanied with the used
221 classes and types.
222
223 \begin{lstlisting}[caption={Device type},label={lst:mtaskdevice}]
224 deviceStoreNP :: Shared [MTaskDevice]
225 deviceShare :: MTaskDevice -> Shared MTaskDevice
226
227 :: Channels :== ([MTaskMSGRecv], [MTaskMSGSend], Bool)
228 :: BCState = ... // Compiler state, explained in later sections
229 :: MTaskResource
230 = TCPDevice TCPSettings
231 | SerialDevice TTYSettings
232 :: MTaskDevice =
233 { deviceTask :: Maybe TaskId
234 , deviceError :: Maybe String
235 , deviceChannels :: String
236 , deviceName :: String
237 , deviceState :: BCState
238 , deviceTasks :: [MTaskTask]
239 , deviceData :: MTaskResource
240 , deviceSpec :: Maybe MTaskDeviceSpec
241 , deviceShares :: [MTaskShare]
242 }
243
244 channels :: MTaskDevice -> Shared Channels
245
246 class MTaskDuplex a where
247 synFun :: a (Shared Channels) -> Task ()
248 \end{lstlisting}
249
250 The \CI{deviceResource} component of the record must implement the
251 \CI{MTaskDuplex} interface that provides a function that launches a \gls{Task}
252 used for synchronizing the channels. The \CI{deviceTask} stores the
253 \gls{Task}-id for this \gls{Task} when active so that it can be checked upon.
254 This top-level task has the duty to report exceptions and errors as they are
255 thrown by setting the \CI{deviceError} field. All communication goes via these
256 channels. If the system wants to send a message to the device, it just puts it
257 in the channels. Messages sent from the client to the server are also placed
258 in there. In the case of the \gls{TCP} device type, the \gls{Task} is just a
259 simple wrapper around the existing \CI{tcpconnect} function in \gls{iTasks}. In
260 case of a device connected by a serial connection, it uses the newly developed
261 serial port library of \gls{Clean}\footnote{\url{%
262 https://gitlab.science.ru.nl/mlubbers/CleanSerial}}.
263
264 Besides all the communication information, the record also keeps track of the
265 \glspl{Task} currently on the device, the compiler state (see
266 Section~\ref{sec:compiler}) and the according \glspl{SDS}. Finally, it stores
267 the specification of the device that is received when connecting. All of this
268 is given in Listing~\ref{lst:mtaskdevice}. The definitions of the message
269 format are explained in the following section.
270
271 \subsection{Integration}
272 When the system starts up, the devices from the previous execution still
273 residing in the \gls{SDS} must be cleaned up. It might be the case that they
274 contain \glspl{Task}, \glspl{SDS} or errors that are no longer applicable in
275 this run. A user or programmer can later choose to reconnect to some devices.
276
277 \begin{lstlisting}[caption={Starting up the devices},%
278 label={lst:startupdevs}]
279 startupDevices :: Task [MTaskDevice]
280 startupDevices = upd (map reset) deviceStoreNP
281 where reset d = {d & deviceTask=Nothing, deviceTasks=[], deviceError=Nothing}
282 \end{lstlisting}
283
284 The system's management is done through the interface of a single \gls{Task}
285 called \CI{mTaskManager}. To manage the system, a couple of different
286 functionalities are needed and are launched. An image of the management
287 interface is shown in Figure~\ref{lst:manage}. The left sidebar of the
288 interface shows the list of example \glspl{Task} that are present in the
289 system. When clicking a \gls{Task}, a dialog opens in which you can select the
290 device to send the \gls{Task} to. The dialog might contain user specified
291 variables. All example \glspl{mTask} are of the type
292 \CI{Task (Main (ByteCode () Stmt))} and can thus ask for user input first if
293 needed for parameterized \glspl{mTask}. The bottom panel shows the device
294 information. In this panel, the devices can be created and modified. Moreover,
295 this panel allows the user to reconnect with a device after a restart of the
296 server application.
297
298 \todo{redo this image}
299 \begin{figure}[H]
300 \centering
301 \includegraphics[width=\linewidth]{manage}
302 \caption{The device management interface}\label{lst:manage}
303 \end{figure}
304
305 \subsection{Shares}
306 The architecture of the system keeps track of the \glspl{SDS} stored on
307 the client in the big devices \gls{SDS}. Client-\glspl{SDS} can be stored on
308 one device at the same time. This means that if a \gls{SDS} updates, everyone
309 watching it will be notified. This would result in to a lot of notifications
310 that are not meant for the listener. Moreover, when a client updates the
311 \gls{SDS} this is processed by the connection handler and results in an update
312 of the real \gls{SDS}. Finally, the \gls{SDS} of a client must be synchronized
313 with the actual device. Thus, when an \gls{iTasks}-\gls{Task} writes the
314 client-\gls{SDS}, it must be propagated to the real device. There are several
315 ways of tackling this problem each with their own pros and cons and their own
316 level of abstraction.
317
318 First an actual \gls{iTasks}-\gls{SDS} for every \gls{SDS} used in a client can
319 be instantiated with one \gls{iTasks}-\gls{Task} listening to the \gls{SDS} and
320 synchronizing it with the device when an update occured. This approach is very
321 expensive as it requires a lot of listening \glspl{Task}.
322
323 Improved on this, a single \gls{iTasks}-\gls{SDS} can be created for every
324 devices that stores the respective \glspl{SDS}. Using the \CI{mapReadWrite}
325 functions, a single \gls{SDS} per device can be created as a lens that allows
326 mapping on a single client-\gls{SDS}. However, This approach still requires
327 \glspl{Task} listening to the \gls{SDS} and when a \gls{SDS} is written,
328 everyone is notified, even if the \gls{Task} wanted to only watch a single
329 different \gls{SDS}.
330
331 Ultimately, the current approach --- a single \gls{SDS} for the entire system
332 --- was explored. To create \glspl{SDS} per device or per client-\glspl{SDS} a
333 \CI{mapReadWrite} can be used but it suffers the same problem as mentioned
334 before. Moreover, a \gls{Task} still has to watch the \gls{SDS} and communicate
335 the client-\gls{SDS} updates to the actual device. Both of these problems can
336 be solved using a tailor made share heavily depending on parametric lenses. The
337 type signature of the share then is as listed in Listing~\ref{lst:actualdev}.
338
339 \begin{lstlisting}[label={lst:actualdev},%
340 caption={Device \gls{SDS}}]
341 deviceStore :: Shared [MTaskDevice]
342 \end{lstlisting}
343
344 \subsection{Parametric Lenses}
345 The type of the parametric lens is \CI{Maybe (MTaskDevice, Int)}. The \gls{SDS}
346 can be responsible for the entire list of devices, from now on global.
347 Moreover, the \gls{SDS} can focus on a single device, from now on local. A
348 local \gls{SDS} can also specifically focus on a single \gls{SDS} on a single
349 device, from now on called local-share.
350
351 \paragraph{Global \glspl{SDS}: }
352 Accessing the global \gls{SDS} is just a matter of focussing the
353 \CI{deviceStore} with the \CI{Nothing} parameter. The signature for
354 \CI{deviceStore} was given in Chapter~\ref{chp:arch}. The actual implementation
355 is as in Listing~\ref{lst:global}
356
357 \begin{lstlisting}[label={lst:shareimpl},%
358 caption={Base share implementation}]
359 deviceStoreNP :: RWShared (Maybe (MTaskDevice, Int)) [MTaskDevice] [MTaskDevice]
360 deviceStoreNP = sdsFocus Nothing deviceStore
361 \end{lstlisting}
362
363
364
365
366 \paragraph{Local \glspl{SDS}: }
367 \paragraph{Local-share specific \glspl{SDS}: }
368
369 The implementation for the share is shown in Listing~\ref{lst:shareimpl}. The
370 \CI{realDeviceStore} \gls{SDS} is not exported through the header files. This
371 \gls{SDS} contains the actual \gls{SDS} that writes to disk or memory.
372 \CI{Int} is the identifier of the \gls{SDS}. The \gls{iTasks} way of applying
373 lenses is through the \CI{sdsFocus} function and through the \CI{sdsLens}
374 functions. \CI{sdsFocus} allows the programmer to fix the parameter.
375 \CI{sdsLens} is basically a \CI{mapReadWrite} that has access to the parameter.
376 This allows the programmer to create filters and lenses. Both of the methods
377 are not good enough for the device \gls{SDS} because they do not achieve the
378 writing to the actual device. Writing to a device requires being able to write
379 to \glspl{SDS}. To solve this problem, a real base \gls{SDS} is created. All
380 the details are visible in Listing~\ref{lst:shareimpl}.
381
382 \section{Communication}
383 The communication from the server to the client and vice versa is just a
384 character stream containing encoded \gls{mTask} messages. The specific encoding
385 is visible in Appendix~\ref{app:communication-protocol}. The type holding the
386 messages in Listing~\ref{lst:avmsg}. Detailed explanation about the message
387 types will be given in the following subsections.
388
389 \begin{lstlisting}[label={lst:avmsg},caption={Available messages}]
390 :: MTaskId :== Int
391 :: MSDSId :== Int
392 :: MTaskFreeBytes :== Int
393 :: MTaskMSGRecv
394 = MTTaskAck MTaskId MTaskFreeBytes | MTTaskDelAck MTaskId
395 | MTSDSAck MSDSId | MTSDSDelAck MSDSId
396 | MTPub MSDSId BCValue | MTMessage String
397 | MTDevSpec MTaskDeviceSpec | MTEmpty
398
399 :: MTaskMSGSend
400 = MTTask MTaskInterval String | MTTaskDel MTaskId
401 | MTShutdown | MTSds MSDSId BCValue
402 | MTUpd MSDSId BCValue | MTSpec
403
404 :: MTaskInterval = OneShot | OnInterval Int | OnInterrupt Int
405 \end{lstlisting}
406
407 \subsection{Add a device}
408 A device can be added by filling in the \CI{MTaskDevice} record as much as
409 possible and running the \CI{connectDevice} function. This function grabs the
410 channels, starts the synchronization \gls{Task}, makes sure the errors are
411 handled when needed and runs a processing function in parallel to react on the
412 incoming messages. Moreover, it sends a specification request to the device in
413 question to determine the details of the device and updates the record to
414 contain the top-level \gls{Task}-id. All the device functionality heavily
415 depends on the specific \CI{deviceShare} function that applies a function a device in
416 the \gls{SDS} when they are equal. Device equality is defined as equality on
417 their channels. This allows you to give an old device record to the function
418 and still update the latest instance. Listing~\ref{lst:connectDevice} shows the
419 connection function.
420
421 \begin{lstlisting}[label={lst:connectDevice},%
422 caption={Connect a device}]
423 withDevices :: MTaskDevice (MTaskDevice -> MTaskDevice) -> Task [MTaskDevice]
424
425 connectDevice :: (MTaskDevice (Shared Channels) -> Task ()) MTaskDevice -> Task Channels
426 connectDevice procFun device = let ch = channels device
427 in appendTopLevelTask 'DM'.newMap True
428 (procFun device ch -||- catchAll (getSynFun d.deviceData ch) errHdl)
429 >>= \tid->withDevices device (\d->{d&deviceTask=Just tid,deviceError=Nothing})
430 >>| upd (\(r,s,ss)->(r,s++[MTSpec],ss)) ch
431 where
432 errHdl e = withDevices device (\d->{d & deviceTask=Nothing, deviceError=Just e}) @! ()
433 \end{lstlisting}
434
435 Figure~\ref{fig:handshake} shows the connection diagram. The client responds to
436 the server with their device specification. This is detected by the processing
437 function and the record is updated accordingly.
438
439 \begin{figure}[H]
440 \centering
441 \begin{sequencediagram}
442 \newthread{s}{Server}
443 \newinst[4]{c}{Client}
444 \begin{call}{s}{MTSpec}{c}{MTDevSpec}
445 \end{call}
446 \end{sequencediagram}
447 \caption{Connect a device}\label{fig:handshake}
448 \end{figure}
449
450 \subsection{\glspl{Task} \& \glspl{SDS}}
451 When a \gls{Task} is sent to the device it is added to the device record
452 without an identifier. The actual identifier is added to the record when the
453 acknowledgement of the \gls{Task} by the device is received. The connection
454 diagram is shown in Figure~\ref{fig:tasksend}.
455
456 \begin{figure}[H]
457 \centering
458 \begin{sequencediagram}
459 \newthread{s}{Server}
460 \newinst[4]{c}{Client}
461 \begin{call}{s}{MTSDS}{c}{MTSDSAck}
462 \end{call}
463 \begin{call}{s}{MTTask}{c}{MTTaskAck}
464 \end{call}
465 \end{sequencediagram}
466 \caption{Sending a \gls{Task} to a device}\label{fig:tasksend}
467 \end{figure}
468
469 The function for sending a \gls{Task} to the device is shown in
470 Listing~\ref{lst:sendtask}. First the \gls{Task} is compiled into messages. The
471 details of the compilation process are given in Section~\ref{sec:compiler}.
472 The new \glspl{SDS} that were made during compilation are added to the
473 deviceshares that were made during the compilation are merged with the existing
474 shares on the device. Furthermore the messages are placed in the channel share
475 of the device. This will result in sending the actual \gls{SDS} specification
476 and \gls{Task} specifications to the device. A \gls{Task} record is created
477 with the identifier $-1$ to denote a \gls{Task} not yet acknowledged. Finally
478 the device itself is updated with the new state and with the new \gls{Task}.
479 When the device returns an acknowledgement the \gls{Task} is updated
480 accordingly.
481
482 \begin{lstlisting}[label={lst:sendtask},%
483 caption={Sending a \gls{Task} to a device}]
484 makeTask :: String Int -> Task MTaskTask
485 makeTask name ident = get currentDateTime @ \dt->{MTaskTask | name=name, ident=ident, dateAdded=dt}
486
487 makeShare :: String Int BCValue -> MTaskShare
488 makeShare withTask identifier value = {MTaskShare | withTask=[withTask], identifier=identifier, value=value}
489
490 sendTaskToDevice :: String (Main (ByteCode a Stmt)) (MTaskDevice, MTaskInterval) -> Task [MTaskDevice]
491 sendTaskToDevice wta mTask (device, timeout)
492 # (msgs, newState) = toMessages timeout mTask device.deviceState
493 # shares = [makeShare wta sdsi sdsval\\{sdsi,sdsval}<-newState.sdss, (MTSds sdsi` _)<-msgs | sdsi == sdsi`]
494 = updateShares device ((++) shares)
495 >>| sendMessages msgs device
496 >>| makeTask wta -1
497 >>= withDevices device o addTaskUpState newState
498 where
499 addTaskUpState :: BCState MTaskTask MTaskDevice -> MTaskDevice
500 addTaskUpState st task device = {MTaskDevice | device &
501 deviceState=st, deviceTasks=[task:device.deviceTasks]}
502 \end{lstlisting}
503
504 \subsection{Miscellaneous Messages}
505 There exists one special type of message that is sent to the device only when
506 it needs to reboot. When the server wants to stop the bond with the device it
507 sends the \CI{MTShutdown} message. The device will then clear his memory, thus
508 losing all the \glspl{SDS} and \glspl{Task} that were stored and reset itself.
509 Shortly after the shutdown message a new server can connect to the device
510 because the device is back in listening mode.
511
512 \section{Lifting mTask to iTasks}
513 \todo{task lifting}
514
515 \section{Examples}
516 \todo{example program (demo)}