From 6edff4bd1d7db321f5d2c38a20c1ef20c52390c0 Mon Sep 17 00:00:00 2001 From: apvrille <ludovic.apvrille@eurecom.fr> Date: Mon, 19 Jun 2017 17:38:15 +0200 Subject: [PATCH] Update on code generation --- .../codegeneration_documentation.tex | 123 ++++++++++++++++++ doc/codegeneration/figures/controlpanel.pdf | Bin 0 -> 7175 bytes 2 files changed, 123 insertions(+) create mode 100644 doc/codegeneration/figures/controlpanel.pdf diff --git a/doc/codegeneration/codegeneration_documentation.tex b/doc/codegeneration/codegeneration_documentation.tex index 7ca8d92de6..4b3c7782d5 100644 --- a/doc/codegeneration/codegeneration_documentation.tex +++ b/doc/codegeneration/codegeneration_documentation.tex @@ -379,8 +379,54 @@ const char* hostname="localhost"; const char* portname="8374"; int fd; struct addrinfo* res; +#define MAX_DGRAM_SIZE 549 \end{lstlisting} +\item It defines the global constants and variables used to be able to interact from the GUI to MS, and the functions to send datagrams to the MS. For example, when you click on the "start" button, a datagram is sent from GUI to MS. This part is explained in more details in section {\ref{sec:GUIActions}} +\begin{lstlisting} +pthread_t thread__Datagram; + +// Handling start datagrams +int start = 0; +pthread_mutex_t startMutex ; +pthread_cond_t noStart; + + + +void startDatagram() { + pthread_mutex_lock(&startMutex); + start = 1; + pthread_cond_signal(&noStart); + pthread_mutex_unlock(&startMutex); +} + +// Assumes fd is valid +void* receiveDatagram(void *arg) { + printf("Thread receive datagram started\n"); + + char buffer[MAX_DGRAM_SIZE]; + struct sockaddr_storage src_addr; + socklen_t src_addr_len=sizeof(src_addr); + + while(1) { + printf("Waiting for datagram packet\n"); + ssize_t count=recvfrom(fd,buffer,sizeof(buffer),0,(struct sockaddr*)&src_addr,&src_addr_len); + if (count==-1) { + perror("recv failed"); + } else if (count==sizeof(buffer)) { + perror("datagram too large for buffer: truncated"); + } else { + //printf("Datagram size: %d.\n", (int)(count)); + if (strncmp(buffer, "START", 5) == 0) { + //printf("+++++++++++++++++++++++ START\n"); + startDatagram(); + } + } + } +} +\end{lstlisting} + + \item It defines a function to send a datagram. \begin{lstlisting} void sendDatagram(char * data, int size) { @@ -418,6 +464,8 @@ void __user_init() { exit(-1); } + // Start a thread to receive datagrams + pthread_create(&thread__Datagram, NULL, receiveDatagram, NULL); } \end{lstlisting} \end{itemize} @@ -456,6 +504,8 @@ The corresponding packets are also defined in the GUI application e.g. see \text \subsection{GUI animation} Generate the C code from TTool (be sure to check the "Include user code" option). Start the GUI from a terminal, and then, start the MS application from TTool (you can also start MS from a terminal). You should see the animations of the GUI while the generated application executes. For example, Figure \ref{fig:animopen} shows the microwave when the door opened, and Figure \ref{fig:animcooking} shows the microwave in heating mode. + + \begin{figure}[htbp] \centering \includegraphics[width=0.5\textwidth]{figures/animopen} @@ -468,6 +518,79 @@ Generate the C code from TTool (be sure to check the "Include user code" option) \caption{GUI when the microwave is cooking} \label{fig:animcooking} \end{figure} +\subsection{GUI actions}\label{sec:GUIActions} +\subsubsection{GUI side} +The GUI contains a "start" button. When the user clicks on this button, the GUI sends a "START" datagram packet to the MS. The GUI is indeed programmed as follows: +\begin{itemize} +\item In MainMicrowave.java: +\begin{lstlisting} +public void mouseClicked(MouseEvent e){ + int x = e.getX(); + int y = e.getY(); + + System.out.println("Mouse clicked!!!"); + + // START? + if ((x>630)&&(x<720)&&(y>335)&&(y<365)) { + System.out.println("Mouse clicked on start"); + if (ds != null) { + ds.sendDatagramTo("START"); + } + System.out.println("Action on start sent"); + } +} +\end{lstlisting} +\textit{ds.sendDatagram(..)} calls a DatagramServer object that sends a datagram to the destination from which it got its first packet. +\end{itemize} +\subsubsection{MS side} +On MS side, the global code starts in \textit{user\_init()} a thread that handles datagram receiving : +\begin{lstlisting} +pthread_create(&thread__Datagram, NULL, receiveDatagram, NULL); +\end{lstlisting} +\textit{receiveDatagram()} waits for datagram packets. When it gets a packet, it checks if it contains the "START" string. If so, it calls \textit{startDatagram()}. This function works as follows: +\begin{enumerate} +\item A lock is put on a mutex ("startMutex") +\item The "start" variable is set to 1 +\item A call is made on the condition variable "noStart" +\item The mutex is unlock +\end{enumerate} +The \textit{ControlPanel} block defines a \textit{start()} method called before it sends the "startButton" signal, see Figure \ref{fig:controlpanel}. + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.25\textwidth]{figures/controlpanel} +\caption{Window of the Graphical User Interface} \label{fig:controlpanel} +\end{figure} + +Thus, when calling start(), the MS wants to wait for the "START" datagram. To do so, the \textit{ControlPanel} block implements "start()" as follows. First, it refers to externally defined elements: the start variable ("start"), the mutex (startMutex) and the condition variable ("startMutex"). +\begin{lstlisting} +extern int start; +extern pthread_mutex_t startMutex ; +extern pthread_cond_t noStart; +\end{lstlisting} +The method itself works as follows: +\begin{enumerate} +\item It puts a lock on the mutex. +\item It waits untils "start" is equal to at least 1. Meanwhile, it waits on the "noStart" condition variable. +\item When "start" is finally equal to 1 or more, it sets "start" to 0. +\item It unlocks the mutex +\end{enumerate} + +Then , the execution of the \textit{ControlPanel} block can continue with the sending of the "startButton" signal. Note that this is thanks to the mutex facility that the datagram receinving facility and the \textit{ControlPanel} block cannot modify "start" at the same time.\\ + +\begin{lstlisting} +void _userImplemented_ControlPanel__start() { + pthread_mutex_lock(&startMutex); + printf("Waiting for next start"); + while(start < 1) { + pthread_cond_wait(&noStart, &startMutex); + } + start = 0; + pthread_mutex_unlock(&startMutex); + printf("****** MW can start cooking\n"); +} +\end{lstlisting} + \newpage \section{Customizing the code generator}\label{sec:customgenerator} diff --git a/doc/codegeneration/figures/controlpanel.pdf b/doc/codegeneration/figures/controlpanel.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0874fd64670188cf7047231c5bd0abe110dc5e99 GIT binary patch literal 7175 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 GH!c7yjuF%V literal 0 HcmV?d00001 -- GitLab