devices
[msc-thesis1617.git] / arch.devices.tex
1 A device is suitable for the system as a client if it can run the engine.
2 The engine is compiled from one codebase and devices implement (part of) the
3 device specific interface. The shared codebase only uses standard \gls{C} and
4 no special libraries or tricks are used. Therefore, the code is compilable for
5 almost any device or system. Note that it is not needed to implement a full
6 interface. The full interface --- excluding the device specific settings --- is
7 listed in Appendix~\ref{app:device-interface}. The interface works in a
8 similar fashion as the \gls{EDSL}. Devices do not have to implement all
9 functionality, this is analogous to the fact that views do not have to
10 implement all type classes in the \gls{EDSL}. When the device connects with
11 the server for the first time, the specifications of what is implemented is
12 communicated. Devices must be available throughout sessions and cannot always
13 be kept in scope and therefore they are stored in an \gls{SDS}.
14
15 At the time of writing the following device families are supported and can run
16 the device software. Porting the client software to a new device does not
17 require a lot of work. For example, porting to the \texttt{mbed} device family
18 only took about an hour.
19 \begin{itemize}
20 \item \texttt{POSIX} compatible systems connected via the \gls{TCP}.
21
22 This port only uses functionality from the standard \gls{C} library and
23 therefore runs on \emph{Linux} and \emph{MacOS}.
24 \item Microcontrollers supported by the
25 \texttt{mbed}\footnote{\url{https://mbed.com}}.
26
27 This is tested in particular on the \texttt{STM32f7x} series \gls{ARM}
28 development board.
29 \item Microcontrollers family supported by
30 \texttt{ChibiOS}\footnote{\url{https://chibios.org}} connected via
31 serial communication.
32
33 This is also tested in particular on the \texttt{STM32f7x} series
34 \gls{ARM} development board.
35 \item Microcontrollers which are programmable in the \gls{Arduino} \gls{IDE}
36 connected via serial communication or via \gls{TCP} over WiFi or
37 Ethernet.
38
39 This does not only include \gls{Arduino} compatible boards but also
40 other boards capable of running \gls{Arduino} code. A port of the
41 client has been made for the \texttt{ESP8266} powered \emph{NodeMCU}
42 that is connected via \gls{TCP} over WiFi. A port also has been made
43 for the regular \gls{Arduino} \emph{Uno} board which only boasts a
44 meager \emph{2K} \emph{RAM}. The stack size and storage available for
45 devices boasting this little \emph{RAM} has to be smaller than default
46 but are still suitable to hold a hand full of \glspl{Task}.
47 \end{itemize}
48
49 \subsection{Client}
50 \subsubsection{Engine}
51 The client software is responsible for maintaining the communication and
52 executing the \glspl{Task} when scheduled. In practise, this means that the
53 client is in a constant loop, checking communication and executing
54 \glspl{Task}. The pseudocode for this is shown in Algorithm~\ref{alg:client}.
55 The \CI{input\_available} function waits for input, but has a timeout set which
56 can be interrupted. The timeout of the function determines the amount of loops
57 per time interval and is a parameter that can be set during compilation for a
58 device.
59
60 \begin{algorithm}
61 \KwData{
62 \textbf{list} $tasks$,
63 \textbf{time} $tm$
64 }
65
66 \Begin{
67 \While{true}{
68 \If{input\_available$()$}{
69 receive\_data()\;
70 }
71
72 $tm\leftarrow \text{now}()$\;
73 \ForEach{$t\leftarrow tasks$}{
74 \uIf{is\_interrupt$(t)$ \textbf{and} had\_interrupt$(t)$}{
75 run\_task$(t)$\;
76 }
77 \ElseIf{$tm-t.\text{lastrun} > t.\text{interval}$}{
78 run\_task$(t)$\;
79 \uIf{$t.\text{interval}==0$}{
80 delete\_task$(t)$\;
81 }\Else{
82 $t.\text{lastrun}\leftarrow t$\;
83 }
84 }
85 }
86 }
87 }
88 \caption{Engine pseudocode}\label{alg:client}
89 \end{algorithm}
90
91 \subsubsection{Storage}
92 \glspl{Task} and \glspl{SDS} are stored on the client not in program memory but
93 in memory. Some devices have very little memory and therefore memory space is
94 very expensive and needs to be used optimally. Almost all microcontrollers
95 support heaps nowadays, however, the functions for allocating and freeing the
96 memory on the heap are not very space optimal and often leave holes in the heap
97 if allocations are not freed in a last in first out fashion. To overcome this
98 problem, the client will allocate a big memory segment in the global data
99 block. This block of memory resides under the stack and its size can be set in
100 the interface implementation. This block of memory will be managed in a similar
101 way as the entire memory space of the device is managed. \Glspl{Task} will grow
102 from the bottom up and \glspl{SDS} will grow from the top down.
103
104 When a \gls{Task} is received, the program will traverse the memory space from
105 the bottom up, jumping over all \glspl{Task}. A \gls{Task} is stored as the
106 structure followed directly by its bytecode. Therefore it only takes two jumps
107 to determine the size of the \gls{Task}. When the program arrived at the last
108 \gls{Task}, this place is returned and the newly received \gls{Task} can be
109 copied to there. This method is analogously applied for \glspl{SDS}, however,
110 the \glspl{SDS} grow from the bottom down.
111
112 When a \gls{Task} or \gls{SDS} is removed, all the remaining objects in the
113 memory space are reordered in such a way that there are no holes left. In
114 practice this means that if the first received \gls{Task} is removed, all
115 \glspl{Task} received later will have to move back. Obviously, this is quite
116 time intensive but it can not be permitted to leave holes in the memory since
117 the memory space is so limited. This techniques allows for even the smallest
118 tested microcontrollers with only $2K$ \emph{RAM} to hold several \glspl{Task}
119 and \glspl{SDS}. Without this technique, the memory space will decrease over
120 time and the client can then not run for very long since holes are evidently
121 created at some point.
122
123 The structure instances and helper functions for traversing for \glspl{Task}
124 and \glspl{SDS} are shown in Listing~\ref{lst:structs}.
125
126 \begin{lstlisting}[language=C,label={lst:structs},%
127 caption={The data type storing the \glspl{Task}},float]
128 struct task {
129 uint16_t tasklength;
130 uint16_t interval;
131 unsigned long lastrun;
132 uint8_t taskid;
133 uint8_t *bc;
134 };
135
136 struct task *task_head(void);
137 struct task *task_next(struct task *t);
138
139 struct sds {
140 int id;
141 int value;
142 char type;
143 };
144
145 struct sds *sds_head(void);
146 struct sds *sds_next(struct sds *s);
147 \end{lstlisting}
148
149 \subsubsection{Interpretation}
150 The execution of a \gls{Task} is started by running the \CI{run\_task} function
151 and always starts with setting the program counter and stack
152 pointer to zero and the bottom respectively. When finished, the
153 interpreter executes one step at the time while the program counter is smaller
154 than the program length. This code is listed in Listing~\ref{lst:interpr}. One
155 execution step is basically a switch statement going over all possible bytecode
156 instructions. The implementation of some instructions is shown in the
157 listing. The \CI{BCPush} instruction is a little more complicated in the real
158 code because some decoding will take place as not all \CI{BCValue}s are of the
159 same length and are encoded.
160
161 \begin{lstlisting}[language=C,label={lst:interpr},%
162 caption={Rough code outline for interpretation}]
163 #define f16(p) program[pc]*265+program[pc+1]
164
165 void run_task(struct task *t){
166 uint8_t *program = t->bc;
167 int plen = t->tasklength;
168 int pc = 0;
169 int sp = 0;
170 while(pc < plen){
171 switch(program[pc++]){
172 case BCNOP:
173 break;
174 case BCPUSH:
175 stack[sp++] = pc++ //Simplified
176 break;
177 case BCPOP:
178 sp--;
179 break;
180 case BCSDSSTORE:
181 sds_store(f16(pc), stack[--sp]);
182 pc+=2;
183 break;
184 // ...
185 case BCADD: trace("add");
186 stack[sp-2] = stack[sp-2] + stack[sp-1];
187 sp -= 1;
188 break;
189 // ...
190 case BCJMPT: trace("jmpt to %d", program[pc]);
191 pc = stack[--sp] ? program[pc]-1 : pc+1;
192 break;
193 // ...
194 }
195 }
196 }
197 \end{lstlisting}