Merge pull request #59 from jtanx/master
authorJeremy Tan <[email protected]>
Sat, 19 Oct 2013 14:54:04 +0000 (07:54 -0700)
committerJeremy Tan <[email protected]>
Sat, 19 Oct 2013 14:54:04 +0000 (07:54 -0700)
Control updates and stuff

15 files changed:
server/actuator.c
server/common.h
server/control.c
server/control.h
server/fastcgi.c
server/fastcgi.h
server/main.c
server/options.h
server/sensor.c
testing/MCTXWeb/public_html/control.html
testing/MCTXWeb/public_html/static/base64.js [deleted file]
testing/MCTXWeb/public_html/static/mctx.control.js
testing/MCTXWeb/public_html/static/mctx.graph.js
testing/MCTXWeb/public_html/static/mctx.gui.js
testing/MCTXWeb/public_html/static/style.css

index 9b9a1a8..2be4194 100644 (file)
@@ -8,9 +8,6 @@
 // Files containing GPIO and PWM definitions
 #include "bbb_pin.h"
 
-
-
-
 /** Number of actuators **/
 int g_num_actuators = 0;
 
@@ -79,12 +76,17 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg)
        {
                case CONTROL_START:
                        {
+                               // Set filename
                                char filename[BUFSIZ];
-                               const char *experiment_name = (const char*) arg;
+                               const char *experiment_path = (const char*) arg;
+                               int ret;
+
+                               ret = snprintf(filename, BUFSIZ, "%s/actuator_%d", experiment_path, a->id);
 
-                               if (snprintf(filename, BUFSIZ, "%s_a%d", experiment_name, a->id) >= BUFSIZ)
+                               if (ret >= BUFSIZ) 
                                {
-                                       Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
+                                       Fatal("Experiment path \"%s\" too long (%d, limit %d)",
+                                                       experiment_path, ret, BUFSIZ);
                                }
 
                                Log(LOGDEBUG, "Actuator %d with DataFile \"%s\"", a->id, filename);
@@ -141,7 +143,7 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg)
  */
 void Actuator_SetModeAll(ControlModes mode, void * arg)
 {
-       for (int i = 0; i < ACTUATORS_MAX; i++)
+       for (int i = 0; i < g_num_actuators; i++)
                Actuator_SetMode(&g_actuators[i], mode, arg);
 }
 
index d4defd9..fa997c5 100644 (file)
@@ -46,6 +46,7 @@
 
 
 extern bool PathExists(const char * path);
+extern bool DirExists(const char * path);
 
 
 
index c6b5823..4342c8c 100644 (file)
@@ -8,16 +8,30 @@
 #include "sensor.h"
 #include "actuator.h"
 #include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 typedef struct ControlData {
        ControlModes current_mode;
        pthread_mutex_t mutex;
        struct timeval start_time;
        char user_name[31]; // The user who owns the currently running experiment
+       char experiment_dir[BUFSIZ]; //Directory for experiment
+       char experiment_name[BUFSIZ];
 } ControlData;
 
 ControlData g_controls = {CONTROL_STOP, PTHREAD_MUTEX_INITIALIZER, {0}};
 
+bool DirExists(const char *path)
+{
+       DIR *dir = opendir(path);
+       if (dir) {
+               closedir(dir);
+               return true;
+       }
+       return false;
+}
+
 bool PathExists(const char *path) 
 {
        FILE *fp = fopen(path, "r");
@@ -28,6 +42,43 @@ bool PathExists(const char *path)
        return false;
 }
 
+/**
+ * Lists all experiments for the current user.
+ * @param The context to work in
+ */
+void ListExperiments(FCGIContext *context) 
+{
+       DIR * dir = opendir(context->user_dir);
+       if (dir == NULL)
+       {
+               FCGI_RejectJSON(context, "Failed to open user directory");
+               return;
+       }
+       struct dirent * ent;
+       FCGI_BeginJSON(context, STATUS_OK);
+       FCGI_JSONKey("experiments");
+       FCGI_PrintRaw("[");
+
+       bool first = true;
+       while ((ent = readdir(dir)) != NULL) {
+               char *ext = strrchr(ent->d_name, '.');
+               if (ext && !strcmp(ext, ".exp")) {
+                       if (!first) {
+                               FCGI_PrintRaw(", ");
+                       }
+
+                       *ext = '\0'; // Ummm... probably not a great idea
+                       FCGI_PrintRaw("\"%s\"", ent->d_name);
+                       first = false;
+               }
+       }
+       FCGI_PrintRaw("]");
+       FCGI_EndJSON();
+       
+       closedir(dir);
+       return;
+}
+
 /**
  * System control handler. This covers high-level control, including
  * admin related functions and starting/stopping experiments..
@@ -40,10 +91,6 @@ void Control_Handler(FCGIContext *context, char *params) {
        bool force = false;
        ControlModes desired_mode;
        
-       
-       
-
-
        // Login/auth now handled entirely in fastcgi.c and login.c
        //TODO: Need to not have the ability for any user to stop someone else' experiment...
        // (achieve by storing the username of the person running the current experiment, even when they log out?)
@@ -54,43 +101,19 @@ void Control_Handler(FCGIContext *context, char *params) {
                {"name", &name, FCGI_STRING_T}
        };
 
-
        if (!FCGI_ParseRequest(context, params, values, 3))
                return;
 
-       //HACKETY HACK HACK (should really be a seperate function)
-       if (strcmp(action, "list") == 0)
-       {
-               DIR * dir = opendir(context->user_name);
-               if (dir == NULL)
-               {
-                       FCGI_RejectJSON(context, "Failed to open user directory");
-                       return;
-               }
-               struct dirent * ent;
+       if (!strcmp(action, "identify")) {
                FCGI_BeginJSON(context, STATUS_OK);
-               FCGI_JSONKey("experiments");
-               FCGI_PrintRaw("[");
-
-               bool first = true;
-               while ((ent = readdir(dir)) != NULL)
-               {
-                       char * c;
-                       for (c = ent->d_name; *c != '\0' && *c != '.'; ++c);
-
-                       if (*c != '\0' && strcmp(c, ".exp") == 0)
-                       {
-                               if (!first)
-                                       FCGI_PrintRaw(",");
-                               *c = '\0'; // Ummm... probably not a great idea
-                               FCGI_PrintRaw(ent->d_name);
-                               first = false;
-                       }
-               }
-               FCGI_PrintRaw("]");
+               FCGI_JSONLong("control_state_id", g_controls.current_mode);
+               FCGI_JSONPair("control_user_name", g_controls.user_name);
+               FCGI_JSONPair("control_experiment_name", g_controls.experiment_name);
                FCGI_EndJSON();
-               
-               return; // Dear god this is terrible
+               return;
+       } else if (!strcmp(action, "list")) {
+               ListExperiments(context);
+               return;
        }
        //TODO: Need a "load" action to set data files (but not run) from a past experiment
 
@@ -111,67 +134,71 @@ void Control_Handler(FCGIContext *context, char *params) {
                return;
        }
 
-       if (*g_controls.user_name != '\0' && strcmp(g_controls.user_name,context->user_name) != 0)
+       if ((*g_controls.user_name) != '\0' && strcmp(g_controls.user_name, context->user_name) != 0)
        {
-               if (context->user_type != USER_ADMIN)
-               {
+               if (context->user_type != USER_ADMIN) {
                        FCGI_RejectJSON(context, "Another user has an experiment in progress.");
                        return;
                }
                
-               if (!force)
-               {
+               if (!force) {
                        Log(LOGERR, "User %s is currently running an experiment!", g_controls.user_name);
                        FCGI_RejectJSON(context, "Pass \"force\" to take control over another user's experiment");
                        return;
                }
        }
 
-       
-       
-       //HACK
-       chdir(context->user_name);
-
        void *arg = NULL;
+       char experiment_dir[BUFSIZ] = {0};
        if (desired_mode == CONTROL_START) {
-               if (PathExists(name) && !force) {
-                       FCGI_RejectJSON(context, "An experiment with that name already exists.");
-                       chdir(g_options.root_dir); // REVERSE HACK
+               int ret;
+
+               if (*name == '\0') {
+                       FCGI_RejectJSON(context, "An experiment name must be specified.");
+                       return;
+               } if (strpbrk(name, INVALID_CHARACTERS)) {
+                       FCGI_RejectJSON(context, 
+                               "An experiment name must not contain: " INVALID_CHARACTERS_JSON);
                        return;
                }
-               char * c = (char*)name;
-               for (c = (char*)name; *c != '\0' && *c != '.'; ++c);
-               if (*c == '.')
-               {
-                       FCGI_RejectJSON(context, "Can't include '.' characters in experiment names (at this point we don't care anymore, go find someone who does).");
-                       chdir(g_options.root_dir); // REVERSE HACK
+
+               if (*(context->user_dir) == '\0') {
+                       FCGI_RejectJSON(context, "Not creating experiment in root dir.");
                        return;
                }
-               arg = (void*)name;
-       }
 
-       
+               ret = snprintf(experiment_dir, BUFSIZ, "%s/%s.exp", 
+                                               context->user_dir, name);
+               if (ret >= BUFSIZ) {
+                       FCGI_RejectJSON(context, "The experiment name is too long.");
+                       return;
+               } else if (DirExists(experiment_dir) && !force) {
+                       FCGI_RejectJSON(context, "An experiment with that name already exists.");
+                       return;
+               }
+
+               arg = (void*) experiment_dir;
+       }
 
        const char *ret;
-       if ((ret = Control_SetMode(desired_mode, arg)) != NULL) 
-       {
+       if ((ret = Control_SetMode(desired_mode, arg)) != NULL) {
                FCGI_RejectJSON(context, ret);
-       } 
-       else 
-       {
-               if (desired_mode == CONTROL_STOP)
+       } else {
+               if (desired_mode == CONTROL_STOP) {
                        g_controls.user_name[0] = '\0';
-               else
-               {
-                       snprintf(g_controls.user_name, sizeof(g_controls.user_name), context->user_name);
+                       g_controls.experiment_dir[0] = '\0';
+                       g_controls.experiment_name[0] = '\0';
+               } else if (desired_mode == CONTROL_START) {
+                       snprintf(g_controls.user_name, sizeof(g_controls.user_name), 
+                                               "%s", context->user_name);
+                       snprintf(g_controls.experiment_dir, sizeof(g_controls.experiment_dir),
+                                               "%s", experiment_dir);
+                       snprintf(g_controls.experiment_name, sizeof(g_controls.experiment_name),
+                                               "%s", name);
                }
-               FCGI_BeginJSON(context, STATUS_OK);
-               FCGI_JSONPair("description", "ok");
-               FCGI_EndJSON();
-       }
 
-       //REVERSE HACK
-       chdir(g_options.root_dir);
+               FCGI_AcceptJSON(context, "Ok", NULL);
+       }
 }
 
 /**
@@ -192,18 +219,13 @@ const char* Control_SetMode(ControlModes desired_mode, void * arg)
        else switch (desired_mode) {
                case CONTROL_START:
                        if (g_controls.current_mode == CONTROL_STOP) {
-                               const char * name = arg;
-                               if (!*name)
-                                       ret = "An experiment name must be specified";
-                               else if (strpbrk(name, INVALID_CHARACTERS))
-                                       ret = "The experiment name must not contain: " INVALID_CHARACTERS_JSON;
-                               else {
-                                       FILE *fp = fopen((const char*) arg, "a");
-                                       if (fp) {
-                                               fclose(fp);
-                                               gettimeofday(&(g_controls.start_time), NULL);
-                                       } else
-                                               ret = "Cannot open experiment name marker";
+                               const char * path = arg;
+                               if (mkdir(path, 0777) != 0 && errno != EEXIST) {
+                                       Log(LOGERR, "Couldn't create experiment directory %s - %s", 
+                                               path, strerror(errno));
+                                       ret = "Couldn't create experiment directory.";
+                               } else {
+                                       gettimeofday(&(g_controls.start_time), NULL);
                                }
                        } else 
                                ret = "Cannot start when not in a stopped state.";
index 7b3511a..3ceea9f 100644 (file)
@@ -14,9 +14,9 @@ typedef enum ControlModes {
 } ControlModes;
 
 /** Invalid filename characters **/
-#define INVALID_CHARACTERS "\"*/:<>?\\|"
+#define INVALID_CHARACTERS "\"*/:<>?\\|"
 /** The same as INVALID_CHARACTERS, except escaped for use in JSON strings **/
-#define INVALID_CHARACTERS_JSON "\\\"*/:<>?\\\\|"
+#define INVALID_CHARACTERS_JSON "\\\"*/:<>?\\\\|"
 
 extern void Control_Handler(FCGIContext *context, char *params);
 extern const char* Control_SetMode(ControlModes desired_mode, void * arg);
index a77a3a4..fd5839e 100644 (file)
  */ 
 static void IdentifyHandler(FCGIContext *context, char *params) {
        bool ident_sensors = false, ident_actuators = false;
-       bool has_control = FCGI_HasControl(context, getenv("COOKIE_STRING"));
+       char control_key[CONTROL_KEY_BUFSIZ];
+       bool has_control;
        int i;
 
+       snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING"));
+       has_control = FCGI_HasControl(context, control_key);
+       
        FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T},
                                         {"actuators", &ident_actuators, FCGI_BOOL_T}};
        if (!FCGI_ParseRequest(context, params, values, 2))
@@ -89,6 +93,7 @@ bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType use
        // Get current time
        time_t now = time(NULL);
        bool expired = now - context->control_timestamp > CONTROL_TIMEOUT;
+       int i;
 
        // Can't lock control if: User not actually logged in (sanity), or key is still valid and the user is not an admin
        if (user_type == USER_UNAUTH || 
@@ -104,7 +109,7 @@ bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType use
        // Generate a SHA1 hash for the user
        SHA_CTX sha1ctx;
        unsigned char sha1[20];
-       int i = rand();
+       i = rand();
        SHA1_Init(&sha1ctx);
        SHA1_Update(&sha1ctx, &now, sizeof(now));
        SHA1_Update(&sha1ctx, &i, sizeof(i));
@@ -112,25 +117,38 @@ bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType use
        for (i = 0; i < sizeof(sha1); i++)
                sprintf(context->control_key + i * 2, "%02x", sha1[i]);
 
-       // Set the IP address
+       // Set the IPv4 address
        snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR"));
+
        // Set the user name
        int uname_len = strlen(user_name);
-       if (snprintf(context->user_name, sizeof(context->user_name), "%s", user_name) < uname_len)
-       {
-               Log(LOGERR, "Username at %d characters too long (limit %d)", uname_len, sizeof(context->user_name));
+       i = snprintf(context->user_name, sizeof(context->user_name), "%s", user_name);
+       if (i < uname_len) {
+               Log(LOGERR, "Username at %d characters too long (limit %d)", 
+                       uname_len, sizeof(context->user_name));
                return false; // :-(
        }
        // Set the user type
        context->user_type = user_type;
+
+       // Build the user directory
+       i = snprintf(context->user_dir, sizeof(context->user_dir), "%s/%s", 
+                                       g_options.experiment_dir, context->user_name);
+       if (i >= sizeof(context->user_dir)) {
+               Log(LOGERR, "Experiment dir too long (required %d, limit %d)",
+                       i, sizeof(context->user_dir));
+               return false;
+       }
+
+       Log(LOGDEBUG, "User dir: %s", context->user_dir);
        // Create directory
-       if (mkdir(user_name, 0777) != 0 && errno != EEXIST)
+       if (mkdir(context->user_dir, 0777) != 0 && errno != EEXIST)
        {
-               Log(LOGERR, "Couldn't create user directory %s/%s - %s", g_options.root_dir, user_name, strerror(errno));
+               Log(LOGERR, "Couldn't create user directory %s - %s", 
+                       context->user_dir, strerror(errno));
                return false; // :-(
        }
 
-
        return true; // :-)
 }
 
@@ -491,16 +509,17 @@ void * FCGI_RequestLoop (void *data)
        while (FCGI_Accept() >= 0) {
                
                ModuleHandler module_handler = NULL;
-               char module[BUFSIZ], params[BUFSIZ];
-               //Don't need to copy if we're not modifying string contents
-               const char *cookie = getenv("COOKIE_STRING");
+               char module[BUFSIZ], params[BUFSIZ], control_key[CONTROL_KEY_BUFSIZ];
                
                //strncpy doesn't zero-truncate properly
                snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL"));
                snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING"));
 
+               //Hack to get the nameless cookie only
+               snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING"));
+
                Log(LOGDEBUG, "Got request #%d - Module %s, params %s", context.response_number, module, params);
-               Log(LOGDEBUG, "Cookie: %s", cookie);
+               Log(LOGDEBUG, "Control key: %s", control_key);
 
                
                //Remove trailing slashes (if present) from module query
@@ -538,7 +557,7 @@ void * FCGI_RequestLoop (void *data)
                        if (module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler)
                        //if (false) // Testing
                        {
-                               if (!FCGI_HasControl(&context, cookie))
+                               if (!FCGI_HasControl(&context, control_key))
                                {
                                        FCGI_RejectJSON(&context, "Please login. Invalid control key.");
                                        continue;       
index 85dab57..de02501 100644 (file)
@@ -33,6 +33,8 @@ typedef enum StatusCodes {
 #define FCGI_RECEIVED(x) ((x) & FCGI_PARAM_RECEIVED)
 #define FCGI_TYPE(x) ((x) & ~(FCGI_PARAM_REQUIRED | FCGI_PARAM_RECEIVED))
 
+#define CONTROL_KEY_BUFSIZ 41
+
 typedef struct FCGIValue {
        const char *key;
        void *value;
@@ -47,13 +49,15 @@ typedef struct
        /**The time of last valid user access possessing the control key**/
        time_t control_timestamp;
        /**A SHA-1 hash that is the control key, determining who is logged in**/
-       char control_key[41]; 
+       char control_key[CONTROL_KEY_BUFSIZ]; 
        /**The IPv4 address of the logged-in user**/
        char control_ip[16];
        /**Determines if the user is an admin or not**/
        UserType user_type;
        /**Name of the logged in user**/
        char user_name[31];
+       /**User directory for the logged in user**/
+       char user_dir[BUFSIZ];
        /**The name of the current module**/
        const char *current_module;
        /**For debugging purposes?**/
index 7933bab..e7edc7e 100644 (file)
@@ -29,13 +29,11 @@ Options g_options; // options passed to program through command line arguments
  */
 void ParseArguments(int argc, char ** argv)
 {
-
-
        g_options.program = argv[0]; // program name
        g_options.verbosity = LOGDEBUG; // default log level
        // Set the main directory
-       if (getcwd(g_options.root_dir, sizeof(g_options.root_dir)) == NULL)
-               Fatal("Couldn't get current working directory - %s", strerror(errno));
+       //if (getcwd(g_options.root_dir, sizeof(g_options.root_dir)) == NULL)
+       //      Fatal("Couldn't get current working directory - %s", strerror(errno));
 
        gettimeofday(&(g_options.start_time), NULL); // Start time
 
@@ -43,9 +41,8 @@ void ParseArguments(int argc, char ** argv)
        g_options.auth_method = AUTH_NONE;  // Don't use authentication
        g_options.auth_uri = ""; // 
        g_options.ldap_base_dn = "";
+       g_options.experiment_dir = ".";
        
-
-
        for (int i = 1; i < argc; ++i)
        {
                if (argv[i][0] != '-')
@@ -76,6 +73,10 @@ void ParseArguments(int argc, char ** argv)
                        case 'd':
                                g_options.ldap_base_dn = argv[++i];
                                break;
+                       case 'e':
+                       // Experiments directory
+                               g_options.experiment_dir = argv[++i];
+                               break;
                        default:
                                Fatal("Unrecognised switch %s", argv[i]);
                                break;
@@ -89,7 +90,13 @@ void ParseArguments(int argc, char ** argv)
        Log(LOGDEBUG, "Pin Module Enabled: %d", g_options.enable_pin);
        Log(LOGDEBUG, "Auth URI: %s", g_options.auth_uri);
        Log(LOGDEBUG, "LDAP Base DN: %s", g_options.ldap_base_dn);
-       Log(LOGDEBUG, "Root directory: %s", g_options.root_dir);
+       //Log(LOGDEBUG, "Root directory: %s", g_options.root_dir);
+       Log(LOGDEBUG, "Experiment directory: %s", g_options.experiment_dir);
+
+       if (!DirExists(g_options.experiment_dir))
+       {
+               Fatal("Experiment directory '%s' does not exist.", g_options.experiment_dir);
+       }
 
        if (g_options.auth_uri[0] != '\0')
        {
index bf668b3..385a05b 100644 (file)
@@ -31,11 +31,8 @@ typedef struct
        /** Authentication method **/
        enum {AUTH_NONE, AUTH_LDAP, AUTH_SHADOW} auth_method;
 
-       /** Starting directory **/
-       char root_dir[BUFSIZ];
-
-       
-
+       /** Experiments directory **/
+       const char *experiment_dir;
 } Options;
 
 /** The only instance of the Options struct **/
index e136988..c41d39e 100644 (file)
@@ -120,11 +120,15 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg)
                        {
                                // Set filename
                                char filename[BUFSIZ];
-                               const char *experiment_name = (const char*) arg;
+                               const char *experiment_path = (const char*) arg;
+                               int ret;
+
+                               ret = snprintf(filename, BUFSIZ, "%s/sensor_%d", experiment_path, s->id);
 
-                               if (snprintf(filename, BUFSIZ, "%s_%d", experiment_name, s->id) >= BUFSIZ)
+                               if (ret >= BUFSIZ) 
                                {
-                                       Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
+                                       Fatal("Experiment path \"%s\" too long (%d, limit %d)",
+                                                       experiment_path, ret, BUFSIZ);
                                }
 
                                Log(LOGDEBUG, "Sensor %d with DataFile \"%s\"", s->id, filename);
@@ -291,7 +295,6 @@ void Sensor_Handler(FCGIContext *context, char * params)
        struct timeval now;
        gettimeofday(&now, NULL);
        double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime());
-
        int id = 0;
        const char * name = "";
        double start_time = 0;
index 61157b6..60817cf 100644 (file)
     <script type="text/javascript">
       runBeforeLoad().done(function () {
         $(document).ready(function () {
+          $("form").submit(function () { //Prevent form submit globally
+            return false;
+          })
+          
+          //Set the status updated
+          $("#state-exp").setStatusUpdater();
+          
+          //Set the logic for the start controls
+          $("#start-controls").submit(function () {
+            var start = $("#start-controls input[type='button']");
+            var force = $("#start-controls input[name='start_force']");
+            
+            $(this).startExperiment(start, $("#experiment_name").val(), 
+                      force.is(":checked"), $("#start-result"));
+            force.prop("checked", false);
+          });
+          
+          //Set the logic for the stop button
+          $("#experiment-stop").click(function () {
+            $(this).stopExperiment($("#stop-status"));
+          });
+          
+          //Set the logic for the pressure controls
+          $("#pressure-controls").submit(function () {
+            var pressure = {
+              set : $("#pressure-set").val(),
+              step : $("#pressure-stepsize").val(),
+              wait : $("#pressure-stepwait").val(),
+              count : $("#pressure-stepcount").val()
+            };   
+            $(this).setPressure(pressure, $("#pressure-result"));
+          });
        });       
-      })
+      }).fail(function () {
+        $(document).ready(function () {
+         $("#state-exp").text("Connection failure").parent().addClass("fail");
+       });  
+      });
     </script>
   </head>
   
             <table class="horizontal medium">
               <tr id="state-exp-r">
                 <th>Experiment state</th>
-                <td id="state-exp">sdgfsdsses</td>
+                <td id="state-exp"></td>
                 <td style="text-align: right;">
                   <input id="experiment-stop" type="button" value="Stop">
                 </td>
               </tr>
             </table>
+            <div id="stop-status">
+              &nbsp;
+            </div>
             
             <div class="sub-title">Error and warning messages</div>
             <textarea id="errorlog" wrap="off" rows="4" cols="30" readonly>
               <p>
                 <label for="experiment_name">Experiment name</label>
                 <input id="experiment_name" type="text">
+                
+                <label for="start_force">Overwrite existing</label>
+                <input type="checkbox" name="start_force" id="start_force">
               </p>
               <p id="start-result">
                 &nbsp;
               </p>
               <p class="centre">
-                <input type="button" name="start_strain" value="Strain test">
-                <input type="button" name="start_strain" value="Explode test">
+                <input type="submit" name="start_strain" value="Strain test">
+                <input type="submit" name="start_explode" value="Explode test">                
               </p>
             </form>
           </div>
diff --git a/testing/MCTXWeb/public_html/static/base64.js b/testing/MCTXWeb/public_html/static/base64.js
deleted file mode 100644 (file)
index 72bced4..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (c) 2010 Nick Galbreath
- * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-/* base64 encode/decode compatible with window.btoa/atob
- *
- * window.atob/btoa is a Firefox extension to convert binary data (the "b")
- * to base64 (ascii, the "a").
- *
- * It is also found in Safari and Chrome.  It is not available in IE.
- *
- * if (!window.btoa) window.btoa = base64.encode
- * if (!window.atob) window.atob = base64.decode
- *
- * The original spec's for atob/btoa are a bit lacking
- * https://developer.mozilla.org/en/DOM/window.atob
- * https://developer.mozilla.org/en/DOM/window.btoa
- *
- * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
- * If any character is not [0,255], then an DOMException(5) is thrown.
- *
- * window.atob and base64.decode take a base64-encoded string
- * If the input length is not a multiple of 4, or contains invalid characters
- *   then an DOMException(5) is thrown.
- */
-var base64 = {};
-base64.PADCHAR = '=';
-base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-
-base64.makeDOMException = function() {
-    // sadly in FF,Safari,Chrome you can't make a DOMException
-    var e, tmp;
-
-    try {
-        return new DOMException(DOMException.INVALID_CHARACTER_ERR);
-    } catch (tmp) {
-        // not available, just passback a duck-typed equiv
-        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
-        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
-        var ex = new Error("DOM Exception 5");
-
-        // ex.number and ex.description is IE-specific.
-        ex.code = ex.number = 5;
-        ex.name = ex.description = "INVALID_CHARACTER_ERR";
-
-        // Safari/Chrome output format
-        ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
-        return ex;
-    }
-}
-
-base64.getbyte64 = function(s,i) {
-    // This is oddly fast, except on Chrome/V8.
-    //  Minimal or no improvement in performance by using a
-    //   object with properties mapping chars to value (eg. 'A': 0)
-    var idx = base64.ALPHA.indexOf(s.charAt(i));
-    if (idx === -1) {
-        throw base64.makeDOMException();
-    }
-    return idx;
-}
-
-base64.decode = function(s) {
-    // convert to string
-    s = '' + s;
-    var getbyte64 = base64.getbyte64;
-    var pads, i, b10;
-    var imax = s.length
-    if (imax === 0) {
-        return s;
-    }
-
-    if (imax % 4 !== 0) {
-        throw base64.makeDOMException();
-    }
-
-    pads = 0
-    if (s.charAt(imax - 1) === base64.PADCHAR) {
-        pads = 1;
-        if (s.charAt(imax - 2) === base64.PADCHAR) {
-            pads = 2;
-        }
-        // either way, we want to ignore this last block
-        imax -= 4;
-    }
-
-    var x = [];
-    for (i = 0; i < imax; i += 4) {
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
-            (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
-        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
-    }
-
-    switch (pads) {
-    case 1:
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
-        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
-        break;
-    case 2:
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
-        x.push(String.fromCharCode(b10 >> 16));
-        break;
-    }
-    return x.join('');
-}
-
-base64.getbyte = function(s,i) {
-    var x = s.charCodeAt(i);
-    if (x > 255) {
-        throw base64.makeDOMException();
-    }
-    return x;
-}
-
-base64.encode = function(s) {
-    if (arguments.length !== 1) {
-        throw new SyntaxError("Not enough arguments");
-    }
-    var padchar = base64.PADCHAR;
-    var alpha   = base64.ALPHA;
-    var getbyte = base64.getbyte;
-
-    var i, b10;
-    var x = [];
-
-    // convert to string
-    s = '' + s;
-
-    var imax = s.length - s.length % 3;
-
-    if (s.length === 0) {
-        return s;
-    }
-    for (i = 0; i < imax; i += 3) {
-        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
-        x.push(alpha.charAt(b10 >> 18));
-        x.push(alpha.charAt((b10 >> 12) & 0x3F));
-        x.push(alpha.charAt((b10 >> 6) & 0x3f));
-        x.push(alpha.charAt(b10 & 0x3f));
-    }
-    switch (s.length - imax) {
-    case 1:
-        b10 = getbyte(s,i) << 16;
-        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
-               padchar + padchar);
-        break;
-    case 2:
-        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
-        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
-               alpha.charAt((b10 >> 6) & 0x3f) + padchar);
-        break;
-    }
-    return x.join('');
-}
-
-
index cf67629..24b0448 100644 (file)
 
 mctx.control = {};
 mctx.control.api = mctx.api + 'control'
+mctx.control.states = {
+  start : 0,
+  pause : 1,
+  resume : 2,
+  stop : 3,
+  emergency : 4
+};
+mctx.control.state = null;
 
-$(document).ready(function () {
+function toggleControls(running) {
+  if (running) {
+    $("#experiment-stop").show();
+    $("#pressure-widget").show();
+    $("#start-widget").hide();
+  } else {
+    $("#start-widget").show();
+    $("#experiment-stop").hide();
+    $("#pressure-widget").hide();
+  }
+}
+
+$.fn.setStatusUpdater = function () {
+  var result = this;
+  
+  var updater = function () {
+    $.ajax({
+      url : mctx.control.api,
+      data : {'action' : 'identify'}
+    }).done(function (data) {
+      if (!result.checkStatus(data)) {
+        $(result).parent().addClass("fail");
+        setTimeout(updater, 4000);
+        return;
+      }
+
+      var text;
+      var running = false;
+      var fail = false;
+      switch (data.control_state_id) {
+        case mctx.control.states.start:
+          text = "Experiment started - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+        break;
+        case mctx.control.states.pause:
+          text = "Experiment paused - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+        break;
+        case mctx.control.states.stop:
+          text = "No experiment running.";
+        break;
+        case mctx.control.states.emergency:
+          text = "Emergency mode - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+          fail = true;
+        default:
+          text = "Unknown mode: " + data.control_state_id;
+          fail = true;
+      }
+      
+      if (data.control_state_id !== mctx.control.state) {      
+        toggleControls(running);
+        $(result).text(text);
+        if (fail) {
+          $(result).parent().addClass("fail");
+        } else {
+          $(result).parent().addClass("pass");
+        }
+        
+        mctx.control.state = data.control_state_id;
+      }
+      
+      setTimeout(updater, 2000);
+    })
+   .fail(function () {
+     $(result).text("Connection failed.").parent().addClass("fail");
+     setTimeout(updater, 4000);
+   });
+  };
+  
+  updater();
+};
+
+
+$.fn.startExperiment = function (group, experiment, force, result) {
+ $(group).attr('disabled', 'disabled');
+ if (!experiment || !experiment.match(/^[a-zA-Z0-9_-]+$/)) {
+   result.text("Experiment names must be composed of alphanumeric characters" + 
+               " or the characters -_-").addClass("fail");
+   $(group).removeAttr('disabled');
+   return;
+ } 
+ var data = {action : "start", name : experiment};
+ if (force) {
+   data.force = 1;
+ }
+ $.ajax({
+   url : mctx.control.api,
+   data : data
+ }).done(function (data) {
+   if (!result.checkStatus(data)) {
+     return;
+   }
+   result.html("&nbsp;");
+   toggleControls(true);
+ }).always(function () {
+   $(group).removeAttr('disabled');
+ });
+};
+
+$.fn.stopExperiment = function (result) {
+  var stop = this;
+  stop.attr('disabled', 'disabled');
+  result.text("Stopping the experiment...");
+  
   $.ajax({
     url : mctx.control.api,
-    data : {'action' : 'identify'}
-  }).done(function () {
-    
+    data : {action : "stop"}
+  }).done(function (data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    result.html("&nbsp;");
+    toggleControls(false);
+  }).always(function () {
+    stop.removeAttr('disabled');
   });
-});
+};
 
+$.fn.setPressure = function(pressure, result) {
+  result.html("&nbsp;");
+  
+  for (var k in pressure) {
+    var n = Number(pressure[k]);
+    if (isNaN(n) || n < 0) {
+      result.text("You must give positive numeric values.").addClass("fail");
+      return;
+    }
+    pressure[k] = n;
+  }
+  
+  var set = pressure['set'] + "," + pressure['wait'] + ","
+            pressure['size'] + "," + pressure['count'];
+  $.ajax({
+    url : mctx.api + "actuators",
+    data : {id : mctx.actuator.pressure_regulator, set : set}
+  }).done(function (data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    
+    result.text("Set ok!").removeClass("fail").addClass("pass");
+  });
+};
\ No newline at end of file
index 4bb3e06..22d14d7 100644 (file)
@@ -106,19 +106,19 @@ $.fn.setDevices = function() {
   });
 };
 
-function setGraphStatus(on, failText) {
+function setGraphStatus(on, failText, keep) {
   if (on) {
     mctx.graph.running = true;
     $("#status-text").html("&nbsp;");
-    $("#graph-run").text("Pause");
+    $("#graph-run").prop("value", "Pause");
   } else {
     mctx.graph.running = false;
     if (failText) {
       $("#status-text").text(failText).addClass("fail");
-    } else {
+    } else if (!keep) {
       $("#status-text").text("Graph stopped").removeClass("fail");
     }
-    $("#graph-run").text("Run");
+    $("#graph-run").prop("value", "Run");
   }
 }
 
@@ -155,6 +155,11 @@ function graphUpdater() {
         responses.push($.ajax({url : urls[val.urltype], data : parameters})
         .done(function(json) {
           //alert("Hi from " + json.name);
+          if (!$("#status-text").checkStatus(json)) {
+            setGraphStatus(false, null, true); //Don't reset text, checkstatus just set it.
+            return;
+          }
+          
           var dev = val.data;
           for (var i = 0; i < json.data.length; ++i) {
             if (dev.length <= 0 || json.data[i][0] > dev[dev.length-1][0]) {
@@ -169,29 +174,29 @@ function graphUpdater() {
 
     //... When the response is received, then() will happen (I think?)
     $.when.apply(this, responses).then(function () {
-      var plot_data = [];
-      yaxis.each(function() {
-        //alert("Add " + $(this).val() + " to plot");
-        if (xaxis.attr("alt") === "time") {
-          //alert("Against time");
-          plot_data.push(devices[$(this).attr("alt")].data);
+      if (mctx.graph.running) {
+        var plot_data = [];
+        
+        yaxis.each(function() {
+          //alert("Add " + $(this).val() + " to plot");
+          if (xaxis.attr("alt") === "time") {
+            //alert("Against time");
+            plot_data.push(devices[$(this).attr("alt")].data);
+          } else {
+            var result = []
+            dataMerge(devices[xaxis.attr("alt")].data, 
+                      devices[$(this).attr("alt")].data, result);
+            plot_data.push(result);
+          }
+        });
+        
+        if (mctx.graph.chart !== null) {
+          mctx.graph.chart.setData(plot_data);
+          mctx.graph.chart.setupGrid(); 
+          mctx.graph.chart.draw();
         } else {
-          var result = []
-          dataMerge(devices[xaxis.attr("alt")].data, 
-                    devices[$(this).attr("alt")].data, result);
-          plot_data.push(result);
+          mctx.graph.chart = $.plot("#graph", plot_data);
         }
-      });
-      
-      //$.plot("#graph", plot_data);
-      if (mctx.graph.chart !== null) {
-        mctx.graph.chart.setData(plot_data);
-        mctx.graph.chart.setupGrid(); 
-        mctx.graph.chart.draw();
-      } else {
-        mctx.graph.chart = $.plot("#graph", plot_data);
-      }
-      if (mctx.graph.running) {
         mctx.graph.timer = setTimeout(updater, 1000);
       }
     }, function () {
index 3198055..3df2bac 100644 (file)
@@ -25,8 +25,12 @@ mctx.menu = [
     {'text' : 'Help', href : mctx.location + 'help.html'}
 ];
 
-mctx.statusCodes = {
-    STATUS_OK : 1
+mctx.status = {
+    OK : 1,
+    ERROR : -1,
+    UNAUTHORIZED : -2,
+    NOTRUNNING : -3,
+    ALREADYEXISTS : -4
 };
 
 mctx.statusCodesDescription = {
@@ -54,6 +58,9 @@ mctx.actuators = {
     3 : {name : "Pressure regulator"}
 };
 
+mctx.actuator = {};
+mctx.actuator.pressure_regulator = 0;
+
 mctx.strain_gauges = {};
 mctx.strain_gauges.ids = [0, 1, 2, 3];
 mctx.strain_gauges.time_limit = 20;
@@ -296,6 +303,14 @@ $.fn.setErrorLog = function () {
     return this;
 };
 
+$.fn.checkStatus = function(data) {
+  if (data.status !== mctx.status.OK) {
+    $(this).text(data.description).removeClass("pass").addClass("fail");
+    return false;
+  }
+  return true;
+};
+
 $(document).ready(function () {
   //Show the content!
   $("#content").css("display", "block");
index a3f0c09..f282aba 100644 (file)
@@ -118,10 +118,10 @@ tr.pass {
   font-weight: inherit;
 }
 
-tr.fail {
+tr.fail, tr.fail .fail {
   color: #000;
   background-color: #E30909;
-  font-weight: bold
+  font-weight: inherit;
 }
 
 table.centre {
@@ -434,4 +434,13 @@ form.controls {
 
 #experiment-stop {
   padding: 0.2em 0.4em;
+  display: none;
+}
+
+#start-widget {
+  display: none;
+}
+
+#pressure-widget {
+  display: none;
 }
\ No newline at end of file

UCC git Repository :: git.ucc.asn.au