1 The goal of the system as a whole is to offer a framework of functions with
2 which an
\gls{iTasks
}-system can add, change and remove devices at runtime.
3 Moreover, the
\gls{iTasks
}-system can send
\gls{mTask
}-
\glspl{Task
} ---
4 compiled at runtime to bytecode by the
\gls{mTask
}-view --- to the device. The
5 device runs an interpreter which can execute the
\gls{Task
}'s bytecode. Device
6 profiles should be persistent during reboots of the
\gls{iTasks
}-system. The
7 methods of interacting with
\gls{mTask
}-
\gls{Task
} should be analogous to
8 interacting with
\gls{iTasks
}-
\glspl{Task
}. This means that programmers can
9 access the
\glspl{SDS
} made for a device in the same way as regular
\glspl{SDS
}
10 and they can execute
\gls{mTask
}-
\glspl{Task
} as if they where normal
11 \gls{iTasks
}-
\glspl{Task
}.
13 The following terms will be used throughout the following chapter:
17 These terms denotes the actual device connected to the system. This can
18 be a real device such as a microcontroller but it can also just be a
19 program on the same machine as the server functioning as a client.
20 \item Server,
\gls{iTasks
}-System
22 This is the actual executable serving the
\gls{iTasks
} application. The
23 system contains
\glspl{Task
} taking care of the communication with the
27 The system describes the complete ecosystem, containing both the server
28 and the clients including the communication between them.
31 The runtime system of the client is called the engine. This program
32 handles communicating with the server and runs the interpreter for the
33 \glspl{Task
} on the client.
37 A device is suitable for the system if it can run the engine.
38 The engine is compiled from one codebase and devices implement (part of) the
39 device specific interface. The shared codebase only uses standard
\gls{C
} and
40 no special libraries or tricks are used. Therefore, the code is compilable for
41 almost any device or system. Note that it is not needed to implement a full
42 interface. The full interface --- excluding the device specific settings --- is
43 listed in Appendix~
\ref{app:device-interface
}. The interface works in a
44 similar fashion as the
\gls{EDSL
}. Devices do not have to implement all
45 functionality, this is analogous to the fact that views do not have to
46 implement all type classes in the
\gls{EDSL
}. When the device connects with
47 the server for the first time, the specifications of what is implemented is
50 At the time of writing the following device families are supported and can run
53 \item \texttt{POSIX
} compatible systems connected via the
\gls{TCP
}.
55 This includes systems running
\emph{Linux
} and
\emph{MacOS
}.
56 \item The
\texttt{STM32
} microcontrollers family supported by
57 \texttt{ChibiOS
} connected via serial communication.
59 This is tested in particular on the
\texttt{STM32f7x
} series
\gls{ARM
}
61 \item Microcontrollers which are programmable in the
\gls{Arduino
} \gls{IDE
}
62 connected via serial communication or via
\gls{TCP
} over WiFi or
65 This does not only include
\gls{Arduino
} compatible boards but also
66 other boards capable of running
\gls{Arduino
} code. A port of the
67 client has been made for the
\texttt{ESP8266
} powered
\emph{NodeMCU
}
68 that is connected via
\gls{TCP
} over WiFi. A port also has been made
69 for the regular
\gls{Arduino
} \emph{UNO
} board which only boasts a
70 meager
\emph{2K
} \emph{RAM
}. The stack size and storage available for
71 devices boasting this little
\emph{RAM
} has to be smaller than default
72 but are still suitable to hold a hand full of
\glspl{Task
}.
76 \subsubsection{Engine
}
77 The client is in a constant loop listening for input and waiting to execute
78 \glspl{Task
}. The pseudocode for this is shown in Algorithm~
\ref{alg:client
}.
79 The
\CI{input
\_available} function waits for input, but has a timeout set which
80 can be interrupted. The timeout of the function determines the amount of loops
81 per time interval and is a parameter that can be set during compilation for a
86 \textbf{list
} $tasks$,
92 \If{input
\_available$()$
}{
96 $tm
\leftarrow \text{now
}()$\;
97 \ForEach{$t
\leftarrow tasks$
}{
98 \uIf{is
\_interrupt$(t)$
\textbf{and
} had
\_interrupt$(t)$
}{
101 \ElseIf{$tm-t.
\text{lastrun
} > t.
\text{interval
}$
}{
103 \uIf{$t.
\text{interval
}==
0$
}{
106 $t.
\text{lastrun
}\leftarrow t$\;
112 \caption{Engine pseudocode
}\label{alg:client
}
115 \subsubsection{Storage
}
116 \glspl{Task
} and
\glspl{SDS
} are stored on the client in memory. Some devices
117 have very little memory and therefore memory space is very expensive and needs
118 to be used optimally. Almost all microcontrollers support heaps nowadays,
119 however, the functions for allocating and freeing the memory on the heap are
120 not very space optimal and often leave holes in the heap if allocations are not
121 freed in reverse order. To overcome this problem the client will allocate a big
122 memory segment in the global data block. This block of memory resides under the
123 stack and its size can be set in the interface implementation. This block of
124 memory will be managed in a similar way as the entire memory space of the
125 device is managed.
\Glspl{Task
} will grow from the bottom up and
\glspl{SDS
}
126 will grow from the top down.
128 When a
\gls{Task
} is received, the program will traverse the memory space from
129 the bottom up, jumping over all
\glspl{Task
}. A
\gls{Task
} is stored as the
130 structure followed directly by its bytecode. Therefore it only takes two jumps
131 to determine the size of the
\gls{Task
}. When the program arrived at the last
132 \gls{Task
}, this place is returned and the newly received
\gls{Task
} can be
133 copied to there. This method is analogously applied for
\glspl{SDS
}, however,
134 the
\glspl{SDS
} grow from the bottom down.
136 When a
\gls{Task
} or
\gls{SDS
} is removed, all remaining objects are compressed
137 again. This means that if the first received
\gls{Task
} is removed, all
138 \glspl{Task
} received later will have to move back. Obviously, this is quite
139 time intensive but it can not be permitted to leave holes in the memory since
140 the memory space is so limited. This techniques allows for even the smallest
141 tested microcontrollers with only $
2K$
\emph{RAM
} to hold several
\glspl{Task
}
142 and
\glspl{SDS
}. If this technique would not be used the memory space will
143 decrease over time and the client can then not run for very long since holes
144 are evidently created at some point.
146 The structure instances and helper functions for traversing them in memory for
147 \glspl{Task
} and
\glspl{SDS
} are shown in Listing~
\ref{lst:structs
}.
149 \begin{lstlisting
}[language=C,label=
{lst:structs
},
%
150 caption=
{The data type storing the
\glspl{Task
}},float
]
154 unsigned long lastrun;
159 struct task *task_head(void);
160 struct task *task_next(struct task *t);
168 struct sds *sds_head(void);
169 struct sds *sds_next(struct sds *s);
172 \subsubsection{Interpretation
}
173 The execution of a
\gls{Task
} is started by running the
\CI{run
\_task} function
174 and always starts with setting the program counter and stack
175 pointer to zero and the bottom respectively. When finished, the
176 interpreter executes one step at the time while the program counter is smaller
177 than the program length. This code is listed in Listing~
\ref{lst:interpr
}. One
178 execution step is basically a big switch statement going over all possible
179 bytecode instructions. Of some instructions, the implementations are shown in
180 the listing. The
\CI{BCPush
} instruction is a little more complicated in the
181 real code because some decoding will take place as not all
\CI{BCValue
}s are of
182 the same length and are encoded.
184 \begin{lstlisting
}[language=C,label=
{lst:interpr
},
%
185 caption=
{Rough code outline for interpretation
}]
186 #define f16(p) program
[pc
]*
265+program
[pc+
1]
188 void run_task(struct task *t)
{
189 uint8_t *program = t->bc;
190 int plen = t->tasklength;
194 switch(program
[pc++
])
{
198 stack
[sp++
] = pc++ //Simplified
204 sds_store(f16(pc), stack
[--sp
]);
208 case BCADD: trace("add");
209 stack
[sp-
2] = stack
[sp-
2] + stack
[sp-
1];
213 case BCJMPT: trace("jmpt to
%d", program[pc]);
214 pc = stack
[--sp
] ? program
[pc
]-
1 : pc+
1;
219 \subsection{Specification
}
220 The server stores a description for every device available in a record type.
221 From the macro settings in the interface file, a profile is created that
222 describes the specification of the device. When the connection between the
223 server and a client is established, the server will send a request for
224 specification. The client serializes its specification and send it to the
225 server so that the server knows what the client is capable of. The exact
226 specification is shown in Listing~
\ref{lst:devicespec
} and stores the
227 peripheral availability, the memory available for storing
\glspl{Task
} and
228 \glspl{SDS
} and the size of the stack.
230 \begin{lstlisting
}[label=
{lst:devicespec
},
231 caption=
{Device specification for
\gls{mTask
}-
\glspl{Task
}}]
244 The server part of the system is written in
\gls{iTasks
}. Functions for
245 managing devices,
\glspl{Task
} and
\glspl{SDS
} have been created to support the
246 functionality. An interactive web application has been created that provides an
247 interactive management console for the
\gls{mTask
} system. This interface
248 provides functionality to list
\glspl{SDS
}, add and remove
\glspl{Task
},
249 administrate devices and view the state of the system.
251 \subsection{Device Storage
}
252 Everything that a device encompasses is stored in the
\CI{MTaskDevice
} record
253 type. This includes management for the
\glspl{SDS
} and
\glspl{Task
} stored on
254 the device. The
\CI{MTaskDevice
} definition is shown in
255 Listing~
\ref{lst:mtaskdevice
} accompanied with the necessary classes and sub
258 \begin{lstlisting
}[caption=
{Device type
},label=
{lst:mtaskdevice
}]
259 :: Channels :== (
[MTaskMSGRecv
],
[MTaskMSGSend
], Bool)
260 :: BCState = ... // Compiler state, explained in later sections
262 = TCPDevice TCPSettings
263 | SerialDevice TTYSettings
266 { deviceTask :: Maybe TaskId
267 , deviceError :: Maybe String
268 , deviceChannels :: String
269 , deviceName :: String
270 , deviceState :: BCState
271 , deviceTasks ::
[MTaskTask
]
272 , deviceResource :: MTaskResource
273 , deviceSpec :: Maybe MTaskDeviceSpec
274 , deviceShares ::
[MTaskShare
]
277 channels :: MTaskDevice -> Shared Channels
279 class MTaskDuplex a where
280 synFun :: a (Shared Channels) -> Task ()
283 The
\CI{deviceResource
} component of the record must implement the
284 \CI{MTaskDuplex
} interface that provides a function that launches a
\gls{Task
}
285 used for synchronizing the channels. The
\CI{deviceTask
} stores the
286 \gls{Task
}-id for this
\gls{Task
} when active so that it can be checked upon.
287 This top-level task has the duty to
report exceptions and errors as they are
288 thrown by setting the
\CI{deviceError
} field. All communication goes via these
289 channels. To send a message to the device, the system just puts it
290 in the channels. Messages sent from the client to the server are also placed
291 in there. In the case of the
\gls{TCP
} device type, the
\gls{Task
} is just a
292 simple wrapper around the existing
\CI{tcpconnect
} function in
\gls{iTasks
}. In
293 case of a device connected by a serial connection, it uses the newly developed
294 serial port library of
\gls{Clean
}\footnote{\url{%
295 https://gitlab.science.ru.nl/mlubbers/CleanSerial
}}.
297 Besides all the communication information, the record also keeps track of the
298 \glspl{Task
} currently on the device, the compiler state (see
299 Section~
\ref{sec:compiler
}) and the according
\glspl{SDS
}. Finally, it stores
300 the specification of the device that is received when connecting. All of this
301 is given in Listing~
\ref{lst:mtaskdevice
}. The definitions of the message
302 format are explained in the following section.
305 The system keeps track of the
\glspl{SDS
} stored on
306 the client in a big
\gls{SDS
} containing a list of devices. Client-
\glspl{SDS
}
307 can be stored on one device at the same time. This means that if an
\gls{SDS
}
308 updates, everyone watching it will be notified. This would result in a lot
309 of notifications that are not meant for the watcher. Moreover, when a client
310 updates the
\gls{SDS
} this is processed by the connection handler and results
311 in an update of the real
\gls{SDS
}. Finally, the
\gls{SDS
} of a client must be
312 synchronized with the actual device. Thus, when an
\gls{iTasks
}-
\gls{Task
}
313 writes the client-
\gls{SDS
}, it must be propagated to the real device. There
314 are several ways of tackling this problem each with their own level of
317 First an actual
\gls{iTasks
}-
\gls{SDS
} for every
\gls{SDS
} used in a client can
318 be instantiated with one
\gls{iTasks
}-
\gls{Task
} listening to the
\gls{SDS
} and
319 synchronizing it with the device when an update occurred. This approach is very
320 expensive as it requires a lot of listening
\glspl{Task
}.
322 Improved on this, a single
\gls{iTasks
}-
\gls{SDS
} can be created for every
323 devices that stores the respective
\glspl{SDS
}. Using the
\CI{mapReadWrite
}
324 functions, a single
\gls{SDS
} per device can be created as a lens that allows
325 mapping on a single client-
\gls{SDS
}. However, this approach still requires
326 \glspl{Task
} listening to the
\gls{SDS
} and when an
\gls{SDS
} is written,
327 everyone is notified, even if the
\gls{Task
} only uses the value of a single
330 Finally, the current approach --- a single
\gls{SDS
} for the entire system
331 --- was explored. To create
\glspl{SDS
} per device or per client-
\glspl{SDS
} a
332 \CI{mapReadWrite
} can be used but it suffers from the same problem as mentioned
333 before. Moreover, a
\gls{Task
} still has to watch the
\gls{SDS
} and communicate
334 the client-
\gls{SDS
} updates to the actual device. Both of these problems can
335 be solved by using a tailor made
\gls{SDS
} that heavily depends on parametric
338 \subsection{Parametric Lenses
}
339 The type for the parametric lens of the big
\gls{SDS
} is
\CI{Maybe
340 (MTaskDevice, Int)
}. The
\gls{SDS
} is responsible for storing the entire list
341 of devices, from now on global. Moreover, the
\gls{SDS
} can focus on a single
342 device, from now on local. A local
\gls{SDS
} can also specifically focus on a
343 single
\gls{SDS
} on a single device, from now on called local-share. The
344 implementation of the real
\gls{SDS
} is given in Listing~
\ref{lst:actualdev
}.
345 The
\gls{SDS
} is a lens on an actual
\gls{SDS
} that writes to a file or memory.
346 Reading the
\gls{SDS
} is nothing more than reading the real
\gls{SDS
}. Writing
347 the
\gls{SDS
} is a little bit more involved. If the write operation originated
348 from an
\gls{SDS
} focussed on a single client-
\gls{SDS
}, the write action must
349 also be relayed to the actual device. If the write originated from an
\gls{SDS
}
350 focussed the devices or on one device only, nothing needs to be done. The
351 notification predicate determines whether a watcher gets a notification update.
353 \begin{lstlisting
}[label=
{lst:actualdev
},
%
354 caption=
{Device
\gls{SDS
}}]
355 deviceStore :: RWShared (Maybe (MTaskDevice, Int))
[MTaskDevice
] [MTaskDevice
]
356 deviceStore = SDSSource
{SDSSource | name="deviceStore", read = realRead, write= realWrite
}
358 realRead :: (Maybe (MTaskDevice,Int)) *IWorld -> (MaybeError TaskException
[MTaskDevice
], *IWorld)
359 realRead p iw = read realDeviceStore iw
361 realWrite :: (Maybe (MTaskDevice,Int))
[MTaskDevice
] *IWorld -> (MaybeError TaskException (SDSNotifyPred (Maybe (MTaskDevice,Int))), *IWorld)
363 # (merr, iw) = write w realDeviceStore iw
364 | isError merr || isNothing mi = (merr $> notifyPred mi, iw)
365 # (Just (dev, ident)) = mi
366 | ident == -
1 = (merr $> notifyPred mi, iw)
367 = case find ((==)dev) w of
368 Nothing = (Error $ exception "Device lost", iw)
369 Just
{deviceShares
} = case find (
\d->d.identifier == ident) deviceShares of
370 Nothing = (Error $ exception "Share lost", iw)
371 Just s = case sendMessagesIW
[MTUpd ident s.MTaskShare.value
] dev iw of
372 (Error e, iw) = (Error e, iw)
373 (Ok _, iw) = (Ok $ notifyPred mi, iw)
375 notifyPred :: (Maybe (MTaskDevice, Int)) (Maybe (MTaskDevice, Int)) -> Bool
376 notifyPred Nothing Nothing = True // Global watcher looking at a global event
377 notifyPred Nothing (Just _) = False // Global watcher looking at a local event
378 notifyPred (Just _) Nothing = False // Local watcher looking at a global event
379 // Local device watcher looking at a local event
380 notifyPred (Just (d1, -
1)) (Just (d2, _)) = d1 == d2
381 // Local share watcher looking at a local share event
382 notifyPred (Just (d1, i1)) (Just (d2, i2)) = d1 == d2 && i1 == i2
384 realDeviceStore :: Shared
[MTaskDevice
]
385 realDeviceStore = sharedStore "mTaskDevices"
[]
388 \subsubsection{Global
\glspl{SDS
}}
389 Accessing the global
\gls{SDS
} is just a matter of focussing the
390 \CI{deviceStore
} with the
\CI{Nothing
} parameter as follows:
392 \begin{lstlisting
}[caption=
{Global
\gls{SDS
}}]
393 deviceStoreNP :: Shared
[MTaskDevice
]
394 deviceStoreNP = sdsFocus Nothing deviceStore
397 \subsubsection{Local
\glspl{SDS
}}
398 Accessing a single device can be done using the
\CI{mapReadWrite
} function.
399 Since device comparison is shallow, the device that is given is allowed to be
400 an old version. The identification of devices is solely done on the name of the
401 channels and is unique throughout the system. The implementation is as follows:
403 \begin{lstlisting
}[caption=
{Local
\gls{SDS
}}]
404 deviceShare :: MTaskDevice -> Shared MTaskDevice
405 deviceShare d = mapReadWriteError
406 (
\ds->case find ((==)d) of
407 Nothing = exception "Device lost"
409 ,
\w ds->case splitWith ((==)d) ds of
410 (
[], _) = Error $ exception "Device lost"
411 (
[_:_
], ds) = Ok $ Just
[w:ds
])
412 $ sdsFocus (Just (d, -
1)) deviceStore
415 \subsubsection{Local-
\gls{SDS
} specific
\glspl{SDS
}}
416 A single
\gls{SDS
} on a single device can be accessed using the
\CI{shareShare
}
417 function. This function focusses the real big
\gls{SDS
} on a single
\gls{SDS
}
418 and uses the
\CI{mapReadWrite
} functions to serve the correct part of the
421 \begin{lstlisting
}[caption=
{Local
\gls{SDS
}}]
422 shareShare :: MTaskDevice MTaskShare -> Shared BCValue
423 shareShare dev share = sdsFocus ()
424 $ mapReadWriteError (read, write)
425 $ sdsFocus (Just (dev, share.identifier))
428 read ::
[MTaskDevice
] -> MaybeError TaskException BCValue
429 read devs = case find ((==)dev) devs of
430 Nothing = exception "Device lost"
431 Just d = case find ((==)share) d.deviceShares of
432 Nothing = exception "Share lost"
433 Just s = Ok s.MTaskShare.value
435 write :: BCValue
[MTaskDevice
] -> MaybeError TaskException (Maybe
[MTaskDevice
])
436 write val devs = case partition ((==)dev) devs of
437 (
[], _) = Error $ exception "Device doesn't exist anymore"
438 (
[_,_:_
], _) = Error $ exception "Multiple matching devices"
439 (
[d=:
{deviceShares
}], devs) = case partition ((==)share) deviceShares of
440 (
[], _) = Error $ exception "Share doesn't exist anymore"
441 (
[_,_:_
], _) = Error $ exception "Multiple matching shares"
442 (
[s
], shares) = Ok $ Just
[{MTaskDevice | d &
443 deviceShares=
[{MTaskShare | s & value=val
}:shares
]}:devs
]
446 \section{Communication
}
447 The communication from the server to the client and vice versa is just a
448 character stream containing encoded
\gls{mTask
} messages. The
\CI{synFun
}
449 belonging to the device is responsible for sending the content in the left
450 channel and putting received messages in the right channel. Moreover, the
451 boolean value should be set to
\CI{True
} when the connection is terminated. The
452 specific encoding of the messages is visible in
453 Appendix~
\ref{app:communication-protocol
}. The type holding the messages is
454 shown in Listing~
\ref{lst:avmsg
}. Detailed explanation about the message types
455 and according actions will be given in the following subsections.
457 \begin{lstlisting
}[label=
{lst:avmsg
},caption=
{Available messages
}]
460 :: MTaskFreeBytes :== Int
462 = MTTaskAck MTaskId MTaskFreeBytes | MTTaskDelAck MTaskId
463 | MTSDSAck MSDSId | MTSDSDelAck MSDSId
464 | MTPub MSDSId BCValue | MTMessage String
465 | MTDevSpec MTaskDeviceSpec | MTEmpty
468 = MTTask MTaskInterval String | MTTaskDel MTaskId
469 | MTShutdown | MTSds MSDSId BCValue
470 | MTUpd MSDSId BCValue | MTSpec
472 :: MTaskInterval = OneShot | OnInterval Int | OnInterrupt Int
475 \subsection{Add a device
}
476 A device can be added by filling in the
\CI{MTaskDevice
} record as much as
477 possible and running the
\CI{connectDevice
} function. This function grabs the
478 channels, starts the synchronization
\gls{Task
} (
\CI{synFun
}), makes sure the
479 errors are handled when needed and runs a processing function in parallel to
480 react on the incoming messages. Moreover, it sends a specification request to
481 the device in question to determine the details of the device and updates the
482 record to contain the top-level
\gls{Task
}-id. All device functionality
483 heavily depends on the specific
\CI{deviceShare
} function that generates an
484 \gls{SDS
} for a specific device. This allows giving an old device record to the
485 function and still update the latest instance. Listing~
\ref{lst:connectDevice
}
486 shows the connection function.
488 \begin{lstlisting
}[label=
{lst:connectDevice
},
%
489 caption=
{Connect a device
}]
490 connectDevice :: (MTaskDevice (Shared Channels) -> Task ()) MTaskDevice -> Task Channels
491 connectDevice procFun device = set (
[],
[], False) ch
492 >>| appendTopLevelTask 'DM'.newMap True
493 ( procFun device ch -||- catchAll (getSynFun device.deviceData ch) errHdl)
494 >>=
\tid->upd (
\d->
{d&deviceTask=Just tid,deviceError=Nothing
}) (deviceShare device)
495 >>| upd (\(r,s,ss)->(r,s++
[MTSpec
],ss)) ch
497 errHdl e = upd (
\d->
{d & deviceTask=Nothing, deviceError=Just e
}) (deviceShare device) @! ()
501 Figure~
\ref{fig:handshake
} shows the connection diagram. The client responds to
502 the server with their device specification. This is detected by the processing
503 function and the record is updated accordingly.
507 \begin{sequencediagram
}
508 \newthread{s
}{Server
}
509 \newinst[4]{c
}{Client
}
510 \begin{call
}{s
}{MTSpec
}{c
}{MTDevSpec
}
512 \end{sequencediagram
}
513 \caption{Connect a device
}\label{fig:handshake
}
516 \subsection{\glspl{Task
} \&
\glspl{SDS
}}
517 When a
\gls{Task
} is sent to the device it is added to the device record
518 without an identifier. The actual identifier is added to the record when the
519 acknowledgement of the
\gls{Task
} by the device is received. The connection
520 diagram is shown in Figure~
\ref{fig:tasksend
}.
524 \begin{sequencediagram
}
525 \newthread{s
}{Server
}
526 \newinst[4]{c
}{Client
}
527 \begin{call
}{s
}{MTSDS
}{c
}{MTSDSAck
}
529 \begin{call
}{s
}{MTTask
}{c
}{MTTaskAck
}
531 \end{sequencediagram
}
532 \caption{Sending a
\gls{Task
} to a device
}\label{fig:tasksend
}
535 The function for sending a
\gls{Task
} to the device is shown in
536 Listing~
\ref{lst:sendtask
}. First the
\gls{Task
} is compiled into messages. The
537 details of the compilation process are given in Section~
\ref{sec:compiler
}.
538 The new
\glspl{SDS
} that were generated during compilation are merged with the
539 existing device's
\glspl{SDS
}. Furthermore the messages are placed in the
540 channel
\gls{SDS
} of the device. This will result in sending the actual
\gls{SDS
}
541 specification and
\gls{Task
} specifications to the device. A
\gls{Task
} record
542 is created with the identifier $-
1$ to denote a
\gls{Task
} not yet
543 acknowledged. Finally the device itself is updated with the new state and with
544 the new
\gls{Task
}. After waiting for the acknowledgement the device is
545 updated again and the
\gls{Task
} returns.
547 \begin{lstlisting
}[label=
{lst:sendtask
},
%
548 caption=
{Sending a
\gls{Task
} to a device
}]
549 makeTask :: String Int -> Task MTaskTask
550 makeTask name ident = get currentDateTime @
\dt->
{MTaskTask | name=name, ident=ident, dateAdded=dt
}
552 makeShare :: String Int BCValue -> MTaskShare
553 makeShare withTask identifier value =
{MTaskShare | withTask=
[withTask
], identifier=identifier, value=value
}
555 sendTaskToDevice :: String (Main (ByteCode a Stmt)) (MTaskDevice, MTaskInterval) -> Task MTaskTask
556 sendTaskToDevice wta mTask (device, timeout)
557 # (msgs, newState=:
{sdss
}) = toMessages timeout mTask device.deviceState
558 # shares =
[makeShare wta "" sdsi sdsval\\
{sdsi,sdsval
}<-sdss, (MTSds sdsi` _)<-msgs | sdsi == sdsi`
]
559 = updateShares device ((++) shares)
560 >>| sendMessages msgs device
562 >>=
\t->upd (addTaskUpState newState t) (deviceShare device)
563 >>| wait "Waiting for task to be acked" (taskAcked t) (deviceShare device)
566 addTaskUpState :: BCState MTaskTask MTaskDevice -> MTaskDevice
567 addTaskUpState st task device =
{MTaskDevice | device & deviceState=st, deviceTasks=
[task:device.deviceTasks
]}
568 taskAcked t d = maybe True (
\t->t.ident <> -
1) $ find (eq t) d.deviceTasks
569 eq t1 t2 = t1.dateAdded == t2.dateAdded && t1.MTaskTask.name == t2.MTaskTask.name
572 \subsection{Miscellaneous Messages
}
573 One special type of message is available which is sent to the device only when
574 it needs to reboot. When the server wants to stop the bond with the device it
575 sends the
\CI{MTShutdown
} message. The device will then clear its memory, thus
576 losing all the
\glspl{SDS
} and
\glspl{Task
} that were stored and reset itself.
577 Shortly after the shutdown message a new server can connect to the device
578 because the device is back in listening mode.
580 \subsection{Integration
}
581 When the system starts up, the devices from the previous execution still
582 residing in the
\gls{SDS
} must be cleaned up. It might be the case that they
583 contain
\glspl{Task
},
\glspl{SDS
} or errors that are no longer applicable in
584 this run. A user or programmer can later choose to reconnect to some devices.
586 \begin{lstlisting
}[caption=
{Starting up the devices
},
%
587 label=
{lst:startupdevs
}]
588 startupDevices :: Task
[MTaskDevice
]
589 startupDevices = upd (map reset) deviceStoreNP
590 where reset d =
{d & deviceTask=Nothing, deviceTasks=
[], deviceError=Nothing
}
593 The system's management is done through the interface of a single
\gls{Task
}
594 called
\CI{mTaskManager
}. To manage the system, a couple of different
595 functionalities are necessary and are launched. An image of the management
596 interface is shown in Figure~
\ref{lst:manage
}. The left sidebar of the
597 interface shows the list of example
\glspl{Task
} that are present in the
598 system. When clicking a
\gls{Task
}, a dialog opens in which a device can be
599 selected to send the
\gls{Task
} to. The dialog might contain user specified
600 variables. All example
\gls{mTask
}-
\glspl{Task
} are of the type
\CI{Task (Main
601 (ByteCode () Stmt))
} and can thus ask for user input first if needed for
602 parameterized
\gls{mTask
}-
\glspl{Task
}. The bottom panel shows the device
603 information. In this panel, the devices can be created and modified. Moreover,
604 this panel allows the user to reconnect with a device after a restart of the
609 \includegraphics[width=
\linewidth]{manage
}
610 \caption{The device management interface
}\label{lst:manage
}
613 \section[Lifting mTasks to iTasks-Tasks
]%
614 {Lifting
\gls{mTask
}-
\glspl{Task
} to
\gls{iTasks
}-
\glspl{Task
}}
615 If the user does not want to know where and when a
\gls{mTask
} is actually
616 executed and is just interested in the results it can lift the
\gls{mTask
} to
617 an
\gls{iTasks
}-
\gls{Task
}. The function is called with a name,
\gls{mTask
},
618 device and interval specification and it will return a
\gls{Task
} that finishes
619 if and only if the
\gls{mTask
} has returned.
621 \begin{lstlisting
}[caption=
{Starting up the devices
}]
622 liftmTask :: String (Main (ByteCode () Stmt)) (MTaskDevice, MTaskInterval) -> Task ()
623 liftmTask wta mTask c=:(dev, _)= sendTaskToDevice wta mTask c
624 >>=
\t->wait "Waiting for mTask to return" (taskRemoved t) (deviceShare dev)
625 >>| viewInformation "Done!"
[] ()
627 taskRemoved t d = isNothing $ find (
\t1->t1.ident==t.ident) d.deviceTasks
631 Here comes a description of the demo program.