diff --git a/trick_source/web/docs/Adding_a_Web_Server_to_Your_Sim.html b/trick_source/web/docs/Adding_a_Web_Server_to_Your_Sim.html new file mode 100644 index 00000000..1ccf86de --- /dev/null +++ b/trick_source/web/docs/Adding_a_Web_Server_to_Your_Sim.html @@ -0,0 +1,558 @@ + + + +
+ + + +To add a web server to your simulation, simply include the WebServer sim module into your S_define file:
+ +#include "sim_objects/WebServer.sm"
The following (input.py) parameters are available to configure your web server:
+ +Parameter Name | +Default Value | +Description | +
---|---|---|
web.server.enable | +False | +Must be explicitly enabled | +
web.server.port | +"8888" | +Web servers “listen” port | +
web.server.document_root | +"www" | +Web servers document root | +
web.server.debug | +False | +Print Client/Server Communication. | +
For your web server to be active, you must at least specify the following :
+ +web.server.enable = True
+
To have your web server listen on port 8890, rather than 8888, you would specify:
+ +web.server.port = "8890"
To serve files from a directory called my_document_root
, rather than www
:
web.server.document_root = "my_document_root"
To see client/server communication:
+ +web.server.debug = True
The web server, if enabled, will start during sim initialization. When it does, it will look for the specified document root directory. By default that’s “www”. If root directory doesn’t exist, one will be created with a simple index.html file , a style sheet, and a couple of directories.
+ +Assuming that you accepted the default port, connect to http://localhost:8888/
from your web browser. This will display the index.html file in your root directory.
The default document root directory that was initially created for you is minimal.
+ +www/
+ index.html
+ style.css
+ apps/
+ images/
index.html is the file that’s displayed when you connect to http://localhost:8888/.
+ +style.css is a CSS style-sheet that’s included by index.html to give it some pizzazz.
+ +The apps directory contains links to some example html/javascript applications
+ in $TRICK_HOME/trick_source/web/apps
.
The images directory contains trick_icon.png.
+ +You are encouraged to add to, modify, and/or delete these files and directories to best suite the needs of your project.
+ + + + + + + + + + + diff --git a/trick_source/web/docs/Adding_a_Web_Server_to_Your_Sim.md b/trick_source/web/docs/Adding_a_Web_Server_to_Your_Sim.md new file mode 100644 index 00000000..3cb78ee6 --- /dev/null +++ b/trick_source/web/docs/Adding_a_Web_Server_to_Your_Sim.md @@ -0,0 +1,75 @@ +# Adding a Web Server to Your Sim + +To add a web server to your simulation, simply include the WebServer sim module into your **S_define** file: + +``` +#include "sim_objects/WebServer.sm" +``` + +## Configuration of the Web Server + +The following (input.py) parameters are available to configure your web server: + +|Parameter Name | Default Value| Description | +|------------------------|--------------|----------------------------------| +|web.server.enable | False |Must be explicitly enabled | +|web.server.port | "8888" |Web servers “listen” port | +|web.server.document_root| "www" |Web servers document root | +|web.server.debug | False |Print Client/Server Communication.| + +For your web server to be active, you must at least specify the following : + +```python +web.server.enable = True + +``` + +To have your web server listen on port 8890, rather than 8888, you would specify: + +```python +web.server.port = "8890" +``` + +To serve files from a directory called ```my_document_root```, rather than ```www```: + +```python +web.server.document_root = "my_document_root" +``` + +To see client/server communication: + +```python +web.server.debug = True +``` + +## When the Web Server Starts +The web server, if enabled, will start during sim initialization. When it does, it will look for the specified document root directory. By default that’s “www”. If root directory doesn’t exist, one will be created with a simple index.html file , a style sheet, and a couple of directories. + + +## Connecting to Your Web Server +Assuming that you accepted the default port, connect to ```http://localhost:8888/``` from your web browser. This will display the index.html file in your root directory. + + +## The Default Document Root Directory + +The default document root directory that was initially created for you is minimal. + +``` +www/ + index.html + style.css + apps/ + images/ +``` + +**index.html** is the file that’s displayed when you connect to http://localhost:8888/. + +**style.css** is a CSS style-sheet that’s included by index.html to give it some pizzazz. + +The **apps** directory contains links to some example html/javascript applications + in ```$TRICK_HOME/trick_source/web/apps```. + +The **images** directory contains trick_icon.png. + +**You are encouraged to add to, modify, and/or delete these files and directories to best suite the needs of your project.** + diff --git a/trick_source/web/docs/Extending_the_HTTP-API.html b/trick_source/web/docs/Extending_the_HTTP-API.html new file mode 100644 index 00000000..b2bd7d57 --- /dev/null +++ b/trick_source/web/docs/Extending_the_HTTP-API.html @@ -0,0 +1,583 @@ + + + + + + + +The HTTP-API is implemented as a collection of httpMethodHandlers
. An httpMethodHandler
is a pointer to a function that is expected to respond to an HTTP GET request, using the Cesanta Mongoose framework. An httpMethodHandler
is defined (in trick/WebServer.hh
) as follows:
typedef void (*httpMethodHandler)(struct mg_connection*, struct http_message*);
Documentation for the Cesanta Mongoose Networking Library can be found at: +https://cesanta.com/docs/overview/intro.html
+ +Suppose you want your web server to send you a JSON message:
+ +{ "greeting" : "Hello Trick Sim Developer!" }
when you invoke the URL: http://localhost:8888/api/http/hello
.
httpMethodHandler
.The following two files will be our implementation of an httpMethodHandler
. We'll put these in some models directory httpMethods/
.
handle_HTTP_GET_hello.h
#ifndef HANDLE_HTTP_GET_HELLO
+#define HANDLE_HTTP_GET_HELLO
+
+#ifndef SWIG
+void handle_HTTP_GET_hello(struct mg_connection *nc, struct http_message *hm);
+#endif
+
+#endif
handle_HTTP_GET_hello.c
#include "mongoose/mongoose.h"
+
+void handle_HTTP_GET_hello(struct mg_connection *nc, struct http_message *hm) {
+ mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
+ const char* json_text =
+ "{ \"greeting\" : \"Hello Trick Sim Developer!\" }";
+ mg_printf_http_chunk(nc, "%s", json_text);
+ mg_send_http_chunk(nc, "", 0);
+}
httpMethodHandler
.We'll do this from our S_define file:
+ +Add (httpMethods/handle_HTTP_GET_hello.c)
to the LIBRARY DEPENDENCIES
.
Include our header file:
+ +##include "httpMethods/handle_HTTP_GET_hello.h"
create_connections()
add :web.server.installHTTPGEThandler( "hello", &handle_HTTP_GET_hello );
/***********************TRICK HEADER*************************
+PURPOSE:
+ (Cannon Numeric)
+LIBRARY DEPENDENCIES:
+ (
+ (cannon/gravity/src/cannon_init.c)
+ (cannon/gravity/src/cannon_numeric.c)
+ (httpMethods/handle_HTTP_GET_hello.c)
+ )
+*************************************************************/
+
+#include "sim_objects/default_trick_sys.sm"
+#include "sim_objects/WebServer.sm"
+##include "cannon/gravity/include/cannon_numeric.h"
+##include "httpMethods/handle_HTTP_GET_hello.h"
+
+class CannonSimObject : public Trick::SimObject {
+
+ public:
+ CANNON cannon ;
+ int foo;
+ CannonSimObject() {
+ ("default_data") cannon_default_data( &cannon ) ;
+ ("initialization") cannon_init( &cannon ) ;
+ ("derivative") cannon_deriv( &cannon ) ;
+ ("integration") trick_ret = cannon_integ( &cannon ) ;
+ ("dynamic_event") cannon_impact( &cannon) ;
+ }
+} ;
+CannonSimObject dyn ;
+
+IntegLoop dyn_integloop (0.01) dyn;
+
+void create_connections() {
+ dyn_integloop.getIntegrator(Runge_Kutta_4, 5);
+ web.server.installHTTPGEThandler( "hello", &handle_HTTP_GET_hello );
+}
+
Consider the following Javascript, that creates a web socket connection:
+ +var ws = new WebSocket('ws://localhost:8888/api/ws/VariableServer');
In the URL: ws://localhost:8888/api/ws/VariableServer
ws://
specifies the protocol (web-socket in this case.)localhost
specifies the domain,:8888
specifies the port, and/api/ws/VariableServer
specifies the path.In the Trick web server, the path associated with a websocket must begin with
+/api/ws/
. The remaining part of the path, i.e., VariableServer
is the key that specifies the sub-protocol, prescribing what messages will be passed between client and server, and what those messages mean.
When a web-socket connection is established, the key will determine what type (sub-class) of WebSocketSession
object to create, to manage the connection.
A WebSocketSession
is a pure virtual base class meant to represent the state of one of potentially many websocket connections. It provides methods to:
To implement a new websocket sub-protocol, one needs to derive a new class from this base class, and implement the required methods. WebSocketSession.hh
can be found in ${TRICK_HOME}/include/trick/
.
/*************************************************************************
+PURPOSE: (Represent Websocket connection.)
+**************************************************************************/
+#ifndef WEB_SOCKET_SESSION_HH
+#define WEB_SOCKET_SESSION_HH
+
+#include <string>
+#ifndef SWIG
+#include "mongoose/mongoose.h"
+#endif
+
+class WebSocketSession {
+ public:
+ WebSocketSession(struct mg_connection *nc):connection(nc){};
+ virtual ~WebSocketSession() {};
+ virtual void marshallData()=0;
+ virtual void sendMessage()=0;
+ virtual int handleMessage(std::string)=0;
+
+ struct mg_connection* connection;
+};
+#endif
To install your new websocket protocol, you'll need to create a function that
+creates an instance of your new WebSocketSession type. Then you'll need to call
+HTTP_Server::installWebSocketSessionMaker
to install the function, with a
+label.
The function you'll create will take struct mg_connection *
as an argument
+and return WebSocketSession*
.
Let's create a new web socket protocol that sends the time in GMT or local time.
+ +First we'll derive a new type called TimeSession
from WebSocketSession
.
/*************************************************************************
+PURPOSE: (Represent the state of a variable server websocket connection.)
+**************************************************************************/
+#ifndef TIMESESSION_HH
+#define TIMESESSION_HH
+#include <vector>
+#include <string>
+#include "time.h"
+#include "trick/WebSocketSession.hh"
+
+class TimeSession : public WebSocketSession {
+ public:
+ enum Zone { GMT, LOCAL};
+ TimeSession(struct mg_connection *nc);
+ ~TimeSession();
+ void marshallData();
+ void sendMessage();
+ int handleMessage(std::string);
+ private:
+ time_t now;
+ Zone zone;
+};
+
+WebSocketSession* makeTimeSession( struct mg_connection *nc );
+#endif
Below is our implementation. Notice the function makeTimeSession
at the bottom.
#include <stdio.h>
+#include <time.h>
+#include <iostream>
+#include "TimeSession.hh"
+
+// CONSTRUCTOR
+TimeSession::TimeSession( struct mg_connection *nc ) : WebSocketSession(nc) {
+ time(&now);
+}
+
+// DESTRUCTOR
+TimeSession::~TimeSession() {}
+
+void TimeSession::marshallData() {
+ time(&now);
+}
+
+void TimeSession::sendMessage() {
+
+ char message[1024];
+ struct tm *theTime;
+ if (zone == TimeSession::LOCAL) {
+ theTime = localtime(&now);
+ } else {
+ theTime = gmtime(&now);
+ }
+ int hours = theTime->tm_hour;
+ int minutes = theTime->tm_min;
+ int seconds = theTime->tm_sec;
+ int day = theTime->tm_mday;
+ int month = theTime->tm_mon + 1;
+ int year = theTime->tm_year + 1900;
+
+ sprintf(message, "Time: %02d:%02d:%02d Date: %02d/%02d/%d\n", hours, minutes, seconds, month, day, year);
+ mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, message, strlen(message));
+}
+
+int TimeSession::handleMessage(std::string client_msg) {
+
+ if (client_msg.compare("GMT") == 0) {
+ zone = TimeSession::GMT;
+ } else if (client_msg.compare("LOCAL") == 0) {
+ zone = TimeSession::LOCAL;
+ } else {
+ std::cerr << "ERROR: Unknown command \"" << client_msg << "\"." << std::endl;
+ }
+ return 0;
+}
+
+// WebSocketSessionMaker function for a TimeSession.
+WebSocketSession* makeTimeSession( struct mg_connection *nc ) {
+ std::cerr << "DEBUG: Creating new TimeSession." << std::endl;
+ return new TimeSession(nc);
+}
We put TimeSession.cpp
and TimeSession.cpp
into a models directory called httpMethods/
.
httpMethods/TimeSession.cpp
compilation unit.##include "httpMethods/TimeSession.hh"
web.server.installWebSocketSessionMaker("Time", &makeTimeSession);
+The label we use for our protocol here is "Time", but it can be whatever name you choose./***********************TRICK HEADER*************************
+PURPOSE:
+ (Cannon Numeric)
+LIBRARY DEPENDENCIES:
+ (
+ (cannon/gravity/src/cannon_init.c)
+ (cannon/gravity/src/cannon_numeric.c)
+ (httpMethods/TimeSession.cpp) // <--(1)
+ )
+*************************************************************/
+
+#include "sim_objects/default_trick_sys.sm"
+#include "sim_objects/WebServer.sm" // <--(2)
+##include "cannon/gravity/include/cannon_numeric.h"
+##include "httpMethods/TimeSession.hh" // <--(3)
+
+class CannonSimObject : public Trick::SimObject {
+
+ public:
+ CANNON cannon ;
+ int foo;
+ CannonSimObject() {
+ ("default_data") cannon_default_data( &cannon ) ;
+ ("initialization") cannon_init( &cannon ) ;
+ ("derivative") cannon_deriv( &cannon ) ;
+ ("integration") trick_ret = cannon_integ( &cannon ) ;
+ ("dynamic_event") cannon_impact( &cannon) ;
+ }
+} ;
+CannonSimObject dyn ;
+
+IntegLoop dyn_integloop (0.10) dyn;
+
+void create_connections() {
+ dyn_integloop.getIntegrator(Runge_Kutta_4, 5);
+ web.server.installWebSocketSessionMaker("Time", &makeTimeSession); // <--(4)
+}
To test your new web socket interface, put the following time.html
file in $YOUR_SIM_DIRECTORY/www/apps
. Then request http://localhost:8888/apps/time.html
from your browser. You should see the time messages from your sim.
<!DOCTYPE html>
+<html>
+ <head>
+ <title>WS Example</title>
+ </head>
+ <body>
+ <div id="output"></div>
+ <script type="text/javascript">
+
+ function log(s) {
+ var p = document.createElement("p");
+ p.style.wordWrap = "break-word";
+ p.textContent = s;
+ output.appendChild(p);
+ }
+
+ var ws = new WebSocket('ws://localhost:8888/api/ws/Time');
+
+ // WebSocket Event Handlers
+ ws.onopen = function(e) {
+ ws.send("GMT");
+ };
+ ws.onmessage = function(e) {
+ log(e.data);
+ };
+ ws.onerror = function(e) {
+ console.log("WebSocket Error: " , e);
+ handleErrors(e);
+ };
+ ws.onclose = function(e) {
+ console.log("Connection closed", e);
+ };
+
+ </script>
+ </body>
+</html>
http://localhost:8888/api/http/alloc_info
Request a JSON encoded sub-list of allocation descriptors from the Memory Manager’s alloc_info list.
+ +Parameter | +Default | +Description | +
---|---|---|
start |
+0 | +starting index of the sub-list. | +
count |
+20 | +number of allocation descriptors. | +
http://localhost:8888/api/http/alloc_info?start=20&count=2
Returns a JSON object containing four name-value pairs:
+ +Name | +Value Description | +
---|---|
alloc_total |
+Total number allocations in the Memory Manager’s alloc_info list. | +
chunk_size |
+Number of allocation description objects in alloc_list . |
+
chunk_start |
+The Memory Manager alloc_info index of the first alloc_list element below |
+
alloc_list |
+Array of JSON Allocation Description objects (described below). | +
Name | +Value Description | +
---|---|
name |
+Name of the allocation. May be Null |
+
start |
+Starting address of the allocation. | +
end |
+Ending address of the allocation. | +
num |
++ |
size |
+Size of the allocation in bytes. | +
type |
+Type descriptor of the allocation. | +
stcl |
+Storage class of the allocation. Either TRICK_EXTERN or TRICK_LOCAL . |
+
language |
+Language. Either : Language_C or Language_CPP . |
+
index |
+Array dimension sizes of the allocation (if it represents an array). | +
In SIM_cannon_numeric
(one of Trick's example sims) the following query resulted in the following JSON.
http://localhost:8888/api/http/alloc_info?start=20&count=2
{ "alloc_total":43,
+ "chunk_size":2,
+ "chunk_start":20,
+ "alloc_list":[
+ { "name":"dyn",
+ "start":"0x101aa9900",
+ "end":"0x101aa9b27",
+ "num":"1",
+ "size":"552",
+ "type":"CannonSimObject",
+ "stcl":"TRICK_EXTERN",
+ "language":"Language_CPP",
+ "index": []
+ }
+ ,
+ { "name":"web",
+ "start":"0x101aa9610",
+ "end":"0x101aa98ff",
+ "num":"1",
+ "size":"752",
+ "type":"WebServerSimObject",
+ "stcl":"TRICK_EXTERN",
+ "language":"Language_CPP",
+ "index": []
+ }
+ ]
+}
+
ws://localhost:8888/api/ws/VariableServer
JSON Variable Server
+ +Add a Trick Variable to the current session.
+ +{ "cmd" : "var_add",
+ "var_name" : string
+}
Stop sending periodic var_list
messages (see below) from the server.
{ "cmd" : "var_pause" }
Resume sending periodic var_list
response messages from the server.
{ "cmd" : "var_unpause" }
+
Send one var_list
message from the server.
{ "cmd" : "var_send" }
Clear all variables from the current session, that is: undo all of the var_add
commands.
{ "cmd" : "var_clear" }
Disconnect from the variable server.
+ +{ "cmd" : "var_exit" }
Set the period (in milliseconds) at which var_list
messages are sent form the server.
{ "cmd" : "var_cycle",
+ "period" : integer
+}
Execute the given Python code in the host sim.
+ +{ "cmd" : "python",
+ "pycode" : string
+}
Send the sie structure from the server. Response will be the sie
response message (below).
{ "cmd" : "sie" }
Send the units for the given variable. Response will be the units
response message (below).
{ "cmd" : "units",
+ "var_name" : string
+}
Error Response
+ +{ "msg_type" : "error",
+ "error_text" : string
+}
Periodic response containing the values of variables requested by var_add
.
{ "msg_type" : "var_list"
+ "time" : double
+ "values" : []
+}
Response to the sie
command (above).
{ "msg_type" : "sie",
+ "data" : string
+}
Response to the units
command (above).
{ "msg_type" : "units",
+ "var_name" : string,
+ "data" : string
+}
<!DOCTYPE html>
+<html>
+ <head>
+ <title>WS Experiments</title>
+ </head>
+ <body>
+ <style>
+ table { border-collapse: collapse; width: 100%; }
+ th, td { text-align: left; padding: 8px; }
+ tr:nth-child(even){background-color: #f2f2f2}
+ th { background-color: #562399; color: white; }
+ </style>
+ <header>
+ </header>
+
+ <div class="variableDisplay"></div>
+ <table class="variables">
+ <tr>
+ <th>Variable</th>
+ <th>Value</th>
+ </tr>
+ </table>
+
+ <div id="output"></div>
+ <script type="text/javascript">
+ function log(s) {
+ var p = document.createElement("p");
+ p.style.wordWrap = "break-word";
+ p.textContent = s;
+ output.appendChild(p);
+ }
+ function sendMessage(msg) {
+ ws.send(msg);
+ }
+ // Interface to Trick WebSocket Variable Server
+ function setPeriod(period) {
+ sendMessage(`{"cmd":"var_cycle","period":${period}}`);
+ }
+ function addVarTableRow(name, value) {
+ // create a row in the table that contains two <td>s, one for the var_name and one for its value.
+ let tr = document.createElement('tr');
+ let td1 = document.createElement('td');
+ td1.textContent = `${name}`;
+ let td2 = document.createElement('td');
+ td2.textContent = `${value}`;
+ td2.className = "values";
+ tr.appendChild(td1);
+ tr.appendChild(td2);
+ varTable.appendChild(tr);
+ }
+ function addVariable(name, value) {
+ sendMessage(`{"cmd":"var_add","var_name": "${name}"}`);
+ addVarTableRow(name, value);
+ }
+ var varTable = document.querySelector('table.variables');
+
+
+ var ws = new WebSocket('ws://localhost:8888/api/ws/VariableServer');
+ ws.onopen = function(e) {
+ setPeriod(100);
+ addVarTableRow("Time", 0.0);
+ addVariable("dyn.cannon.pos[0]", 0.0);
+ addVariable("dyn.cannon.pos[1]", 0.0);
+ addVariable("dyn.cannon.vel[0]", 0.0);
+ addVariable("dyn.cannon.vel[1]", 0.0);
+ addVariable("dyn.cannon.time", 0.0);
+ addVariable("dyn.cannon.timeRate", 0.0);
+ addVariable("dyn.cannon.impact", 0.0);
+ addVariable("I.dont.exist", 0.0);
+ sendMessage("{\"cmd\":\"var_unpause\"}");
+ };
+ ws.onmessage = function(e) {
+ let msg = JSON.parse(e.data);
+ if (msg.msg_type == "values") {
+ let valueNodes = varTable.getElementsByClassName("values");
+ valueNodes[0].textContent = msg.time;
+ for (let i = 0; i < msg.values.length; i++ ) {
+ valueNodes[i+1].textContent = msg.values[i];
+ }
+ }
+ };
+ ws.onerror = function(e) {
+ console.log("WebSocket Error: " , e);
+ handleErrors(e);
+ };
+ ws.onclose = function(e) {
+ console.log("Connection closed", e);
+ };
+
+ </script>
+ </body>
+</html>
Variable | +Value | +
---|
![]() |
+ Web Server Documentation |
+
---|