| SerialDevice TTYSettings
| ...
:: MTaskDevice =
- { deviceTask :: Maybe TaskId
- , deviceError :: Maybe String
- , deviceChannels :: String
- , deviceName :: String
- , deviceState :: BCState
- , deviceTasks :: [MTaskTask]
- , deviceResource :: MTaskResource
- , deviceSpec :: Maybe MTaskDeviceSpec
- , deviceShares :: [MTaskShare]
- }
+ { deviceTask :: Maybe TaskId
+ , deviceError :: Maybe String
+ , deviceChannels :: String
+ , deviceName :: String
+ , deviceState :: BCState
+ , deviceTasks :: [MTaskTask]
+ , deviceResource :: MTaskResource
+ , deviceSpec :: Maybe MTaskDeviceSpec
+ , deviceShares :: [MTaskShare]
+ }
channels :: MTaskDevice -> Shared Channels
The \CI{deviceResource} component of the record must implement the
\CI{MTaskDuplex} interface that provides a function that launches a \gls{Task}
-used for synchronizing the channels. The \CI{deviceTask} stores the
-\gls{Task}-id for this \gls{Task} when active so that it can be checked upon.
-This top-level task has the duty to report exceptions and errors as they are
-thrown by setting the \CI{deviceError} field. All communication goes via these
-channels. To send a message to the device, the system just puts it
-in the channels. Messages sent from the client to the server are also placed
-in there. In the case of the \gls{TCP} device type, the \gls{Task} is just a
-simple wrapper around the existing \CI{tcpconnect} function in \gls{iTasks}. In
-case of a device connected by a serial connection, it uses the newly developed
-serial port library of \gls{Clean}\footnote{\url{%
-https://gitlab.science.ru.nl/mlubbers/CleanSerial}}.
-The implementation and semantics for the \CI{MTaskMSGRecv} and
-\CI{MTaskMSGSend} types is given in Section~\ref{sec:communication}.
+used for synchronizing the channels. The \CI{deviceChannels} field can be used
+to get the memory \gls{SDS} containing the channels. This field does not
+contain the channels itself because they update often. The field is used to
+get a memory \gls{SDS} containing the actual channel data when calling the
+\CI{channels} function. The \CI{deviceTask} stores the \gls{Task}-id for this
+\gls{Task} when active so that it can be checked upon. This top-level task has
+the duty to report exceptions and errors as they are thrown by setting the
+\CI{deviceError} field. All communication goes via these channels. To send a
+message to the device, the system just puts it in the channels. Messages sent
+from the client to the server are also placed in there. In the case of the
+\gls{TCP} device type, the \gls{Task} is just a simple wrapper around the
+existing \CI{tcpconnect} function in \gls{iTasks}. In case of a device
+connected by a serial connection, it uses the newly developed serial port
+library of \gls{Clean}\footnote{\url{%
+https://gitlab.science.ru.nl/mlubbers/CleanSerial}}. The implementation and
+semantics for the \CI{MTaskMSGRecv} and \CI{MTaskMSGSend} types are given in
+Section~\ref{sec:communication}.
Besides all the communication information, the record also keeps track of the
\glspl{Task} currently on the device, the compiler state (see
format are explained in the following section.
\subsection{Shares}
-\Glspl{SDS} on the device are synchronized in the server and thus stored both
-on the server and on the client. In this way the communication is kept to the
-bare minimum because no communication with the device is needed for a
-\gls{SDS} read operation. In this case the \gls{Task} requesting can just be
-provided with the synchronized value. As mentioned before, the device has to
-explicitly publish an update. This has the implication that the server and the
-client can get out of sync. However, this is not a problem since it is well
-documented and by design.
+\Glspl{SDS} on the device can be accessed by both the device and the server.
+While it would be possible to only store the \glspl{SDS} on the device, this
+would require a lot of communication because every read operation will then
+result in sending messages to-and-fro the device. Thus, the \gls{Task}
+requesting the shared information can just be provided with the synchronized
+value. As mentioned before, the device has to explicitly publish an update.
+This has the implication that the server and the client can get out of sync.
+However, this is by design and well documented. In the current system, an
+\gls{SDS} can only reside on a single device.
% TODO
-The system keeps track of the \glspl{SDS} stored on the client in a big
-\gls{SDS} containing a list of devices. Client-\glspl{SDS} can be stored on one
-device at the same time. This means that if an \gls{SDS} updates, everyone
-watching it will be notified. This would result in a lot of notifications that
-are not meant for the watcher. Moreover, when a client updates the \gls{SDS}
-this is processed by the connection handler and results in an update of the
-real \gls{SDS}. Finally, the \gls{SDS} of a client must be synchronized with
-the actual device. Thus, when an \gls{iTasks}-\gls{Task} writes the
-client-\gls{SDS}, it must be propagated to the real device. There are several
-ways of tackling this problem each with their own level of granularity.
-
-First an actual \gls{iTasks}-\gls{SDS} for every \gls{SDS} used in a client can
-be instantiated with one \gls{iTasks}-\gls{Task} listening to the \gls{SDS} and
-synchronizing it with the device when an update occurred. This approach is very
-expensive as it requires a lot of listening \glspl{Task}.
-
-Improved on this, a single \gls{iTasks}-\gls{SDS} can be created for every
-devices that stores the respective \glspl{SDS}. Using the \CI{mapReadWrite}
-functions, a single \gls{SDS} per device can be created as a lens that allows
-mapping on a single client-\gls{SDS}. However, this approach still requires
-\glspl{Task} listening to the \gls{SDS} and when an \gls{SDS} is written,
-everyone is notified, even if the \gls{Task} only uses the value of a single
-different \gls{SDS}.
-
-Finally, the current approach --- a single \gls{SDS} for the entire system
---- was explored. To create \glspl{SDS} per device or per client-\glspl{SDS} a
-\CI{mapReadWrite} can be used but it suffers from the same problem as mentioned
-before. Moreover, a \gls{Task} still has to watch the \gls{SDS} and communicate
-the client-\gls{SDS} updates to the actual device. Both of these problems can
-be solved by using a tailor made \gls{SDS} that heavily depends on parametric
-lenses.
+% Single share per share
+There are several possible approaches for storing \glspl{SDS} on the server
+each with their own level of control. A possible way is to --- in the device
+record --- add a list of references to \gls{iTasks}-\glspl{SDS} that represent
+the \gls{SDS} on the device. The problem with this is the fact that an
+\gls{SDS} can become an orphan. The \gls{SDS} is still accessible even when the
+device is long gone. There is no way of knowing whether the \gls{SDS} is
+unreachable because of the device being gone, or the \gls{SDS} itself is gone
+on the device. Accessing the \gls{SDS} happens by calling the \CI{get},
+\CI{set} and \CI{upd} functions directory on the actual \gls{SDS}.
+
+% Single share per device
+Another approach would be to have reference to a \gls{SDS} containing a table
+of \gls{SDS} values per device. This approach poses the same orphan problem as
+before. Accessing a single \gls{SDS} on the device happens by calling the
+\CI{get}, \CI{set} and \CI{upd} functions on the actual table \gls{SDS} with an
+applied \CI{mapReadWrite}. Using parametric lenses can circumvent problem of
+watchers getting notified for other shares that are written. Error handling is
+better than the previously mentioned approach because a \gls{SDS} can know
+whether the \gls{SDS} has really gone because it will not be available anymore
+in the table. It still does not know whether the device is still available.
+
+% One share to rule them all
+Finally, all devices containing all of their \glspl{SDS} in a table could be
+stored in a single big \gls{SDS}. While the \CI{mapReadWrite} functions require
+a bit more logic, they can determine the source of the error and act upon it.
+Also, the parametric lenses must contain more logic. A downside of this
+approach is that updating a single \gls{SDS} requires an update of the entire
+object. In practise, this is not a real issue since almost all information can
+be reused and \gls{SDS} residing on devices are often not updated with a very
+high frequency.
\subsection{Parametric Lenses}
-The type for the parametric lens of the big \gls{SDS} is \CI{Maybe
-(MTaskDevice, Int)}. The \gls{SDS} is responsible for storing the entire list
-of devices, from now on global. Moreover, the \gls{SDS} can focus on a single
-device, from now on local. A local \gls{SDS} can also specifically focus on a
-single \gls{SDS} on a single device, from now on called local-share. The
-implementation of the real \gls{SDS} is given in Listing~\ref{lst:actualdev}.
-The \gls{SDS} is a lens on an actual \gls{SDS} that writes to a file or memory.
-Reading the \gls{SDS} is nothing more than reading the real \gls{SDS}. Writing
-the \gls{SDS} is a little bit more involved. If the write operation originated
-from an \gls{SDS} focussed on a single client-\gls{SDS}, the write action must
-also be relayed to the actual device. If the write originated from an \gls{SDS}
-focussed the devices or on one device only, nothing needs to be done. The
-notification predicate determines whether a watcher gets a notification update.
+The type for the parametric lens of the \gls{SDS} containing all devices is
+\CI{Maybe (MTaskDevice, Int)}. There are several levels of abstraction that
+have to be introduced. First, the \gls{SDS} responsible for storing the entire
+list of devices is called the global \gls{SDS}. Secondly, a \gls{SDS} can focus
+on a single device, such \glspl{SDS} are called local \glspl{SDS}. Finally, a
+\gls{SDS} can focus on a single \gls{SDS} on a single device. These \glspl{SDS}
+are called share \glspl{SDS}. Using parametric lenses, the notifications can be
+directed to only the watchers interested. Moreover, using parametric lenses,
+the \gls{SDS} can know whether it is updating a single \gls{SDS} on a single
+device and synchronize the value with the actual device. This means that when
+writing to a share \gls{SDS} the update is also transformed to messages that
+are put in the channels of the corresponding device to also notify the device
+of the update. The \gls{SDS} is tailor-made and uses an actual standard
+\gls{SDS} that writes to a file or memory as the storage. The tailor-made read
+and write functions are only used to detect whether it is required to send an
+update to the actual device.
+
+Listing~\ref{lst:actualdev} shows the implementation of the big \gls{SDS}. From
+this \gls{SDS} all other \glspl{SDS} are derived. The following paragraphs show
+how this is achieved for the global \gls{SDS} local \gls{SDS} and the share
+\gls{SDS}. In the big \gls{SDS}, reading the value is just a matter of reading
+the standard \gls{SDS} that serves as the actual storage of the \gls{SDS}. The
+derived shares will filter the output read accordingly. Writing the share
+requires some extra work because it might be possible that an actual device has
+to be notified. First, the actual storage of the \gls{SDS} is written. If the
+parameter was \CI{Nothing} --- the global \gls{SDS} --- the write operation is
+done. If the parameter was \CI{Just (d, -1)} --- a local \gls{SDS} --- nothing
+has to be done as well. The final case is the special case, when the parameter
+is \CI{Just (d, i)}, this means that the \gls{SDS} was focussed on device
+\CI{d} and \gls{SDS} \CI{i} and thus it needs to write to it. First it locates
+the device in the list, followed by the location of the share to check whether
+is still exists. Finally the actual update messages are added to the device
+channels using the \CI{sendMessagesIW} function.
+
+All of the methods share the same \CI{SDSNotifyPred p} which is a function
+\CI{p -> Bool} and determines for the given \CI{p} whether a notification is
+required. The predicate function has the \CI{p} of the writer curried in and
+can determine whether the second argument --- the reader --- needs to be
+notified. In practice, the reader only needs to be notified when the parameters
+are exactly the same.
\begin{lstlisting}[label={lst:actualdev},%
caption={Device \gls{SDS}}]
+($<) a fb = fmap (const a) fb
+
deviceStore :: RWShared (Maybe (MTaskDevice, Int)) [MTaskDevice] [MTaskDevice]
-deviceStore = SDSSource {SDSSource | name="deviceStore", read = realRead, write= realWrite}
+deviceStore = SDSSource {SDSSource | name="deviceStore", read=realRead, write=realWrite}
where
realRead :: (Maybe (MTaskDevice,Int)) *IWorld -> (MaybeError TaskException [MTaskDevice], *IWorld)
realRead p iw = read realDeviceStore iw
realWrite :: (Maybe (MTaskDevice,Int)) [MTaskDevice] *IWorld -> (MaybeError TaskException (SDSNotifyPred (Maybe (MTaskDevice,Int))), *IWorld)
realWrite mi w iw
# (merr, iw) = write w realDeviceStore iw
- | isError merr || isNothing mi = (merr $> notifyPred mi, iw)
+ | isError merr || isNothing mi = (merr $> gEq{|*|} mi, iw)
# (Just (dev, ident)) = mi
- | ident == -1 = (merr $> notifyPred mi, iw)
+ | ident == -1 = (merr $> gEq{|*|} mi, iw)
= case find ((==)dev) w of
Nothing = (Error $ exception "Device lost", iw)
Just {deviceShares} = case find (\d->d.identifier == ident) deviceShares of
Nothing = (Error $ exception "Share lost", iw)
Just s = case sendMessagesIW [MTUpd ident s.MTaskShare.value] dev iw of
(Error e, iw) = (Error e, iw)
- (Ok _, iw) = (Ok $ notifyPred mi, iw)
-
- notifyPred :: (Maybe (MTaskDevice, Int)) (Maybe (MTaskDevice, Int)) -> Bool
- notifyPred Nothing Nothing = True // Global watcher looking at a global event
- notifyPred Nothing (Just _) = False // Global watcher looking at a local event
- notifyPred (Just _) Nothing = False // Local watcher looking at a global event
- // Local device watcher looking at a local event
- notifyPred (Just (d1, -1)) (Just (d2, _)) = d1 == d2
- // Local share watcher looking at a local share event
- notifyPred (Just (d1, i1)) (Just (d2, i2)) = d1 == d2 && i1 == i2
+ (Ok _, iw) = (Ok $ gEq{|*|} mi, iw)
realDeviceStore :: Shared [MTaskDevice]
realDeviceStore = sharedStore "mTaskDevices" []
\subsubsection{Global \glspl{SDS}}
Accessing the global \gls{SDS} is just a matter of focussing the
-\CI{deviceStore} with the \CI{Nothing} parameter as follows:
+\CI{deviceStore} to \CI{Nothing}. In this way, \glspl{Task} watching the
+\gls{SDS} will only be notified if a device is added or removed. The actual
+code is as follows:
\begin{lstlisting}[caption={Global \gls{SDS}}]
deviceStoreNP :: Shared [MTaskDevice]
\end{lstlisting}
\subsubsection{Local \glspl{SDS}}
-Accessing a single device can be done using the \CI{mapReadWrite} function.
+Accessing a single device can be done using the \CI{deviceShare} function.
Since device comparison is shallow, the device that is given is allowed to be
an old version. The identification of devices is solely done on the name of the
-channels and is unique throughout the system. The implementation is as follows:
+channels and is unique throughout the system. This type of \gls{SDS} will only
+be notified if the device itself changed. It will not be notified when only a
+single \gls{SDS} on the device changes. The implementation is as follows:
\begin{lstlisting}[caption={Local \gls{SDS}}]
deviceShare :: MTaskDevice -> Shared MTaskDevice
\subsubsection{Local-\gls{SDS} specific \glspl{SDS}}
A single \gls{SDS} on a single device can be accessed using the \CI{shareShare}
-function. This function focusses the real big \gls{SDS} on a single \gls{SDS}
-and uses the \CI{mapReadWrite} functions to serve the correct part of the
-information.
+function. This function focusses the global \gls{SDS} on a single \gls{SDS}
+from a single device. It can use old share references in the same fashion as
+the local \gls{SDS} only treating it as references. It uses the
+\CI{mapReadWrite} functions to serve the correct part of the information. When
+a \gls{Task} writes to this \gls{SDS}, the global \gls{SDS} will know this
+through the parameter and propagate the value to the device.
\begin{lstlisting}[caption={Local \gls{SDS}}]
shareShare :: MTaskDevice MTaskShare -> Shared BCValue