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