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