3 * @brief Implementation of sensor thread
4 * TODO: Finalise implementation
13 /** Array of sensors, initialised by Sensor_Init **/
14 static Sensor g_sensors[NUMSENSORS]; //global to this file
16 /** Array of sensor threshold structures defining the safety values of each sensor**/
17 const SensorThreshold thresholds[NUMSENSORS]= {
18 //Max Safety, Min safety, Max warning, Min warning
30 /** Human readable names for the sensors **/
31 const char * g_sensor_names[NUMSENSORS] = {
44 * One off initialisation of *all* sensors
48 for (int i = 0; i < NUMSENSORS; ++i)
51 Data_Init(&(g_sensors[i].data_file));
56 // Get the required ADCs
57 ADC_Export(ADC0); // Strain gauges x 4
58 ADC_Export(ADC1); // Pressure sensor 1
59 ADC_Export(ADC2); // Pressure sensor 2
60 // ADC3 still unused (!?)
61 ADC_Export(ADC4); // Pressure regulator feedback(?) signal
62 ADC_Export(ADC5); // Microphone
64 // Get GPIO pins //TODO: Confirm pins used with Electronics Team
65 GPIO_Export(GPIO0_30); // Mux A (strain 1)
66 GPIO_Set(GPIO0_30, false);
67 GPIO_Export(GPIO1_28); // Mux B (strain 2)
68 GPIO_Set(GPIO1_28, false);
69 GPIO_Export(GPIO0_31); // Mux C (strain 3)
70 GPIO_Set(GPIO0_31, false);
71 GPIO_Export(GPIO1_16); // Mux D (strain 4)
72 GPIO_Set(GPIO1_16, false);
74 GPIO_Export(GPIO0_31); // Enclosure switch
78 * Sets the sensor to the desired control mode. No checks are
79 * done to see if setting to the desired mode will conflict with
80 * the current mode - the caller must guarantee this itself.
81 * @param s The sensor whose mode is to be changed
82 * @param mode The mode to be changed to
83 * @param arg An argument specific to the mode to be set.
84 * e.g for CONTROL_START it represents the experiment name.
86 void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg)
93 char filename[BUFSIZ];
94 const char *experiment_name = (const char*) arg;
96 if (snprintf(filename, BUFSIZ, "%s_s%d", experiment_name, s->id) >= BUFSIZ)
98 Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
101 Log(LOGDEBUG, "Sensor %d with DataFile \"%s\"", s->id, filename);
103 Data_Open(&(s->data_file), filename);
105 case CONTROL_RESUME: //Case fallthrough, no break before
108 s->activated = true; // Don't forget this!
111 ret = pthread_create(&(s->thread), NULL, Sensor_Loop, (void*)(s));
114 Fatal("Failed to create Sensor_Loop for Sensor %d", s->id);
117 Log(LOGDEBUG, "Resuming sensor %d", s->id);
121 case CONTROL_EMERGENCY:
123 s->activated = false;
124 pthread_join(s->thread, NULL);
125 Log(LOGDEBUG, "Paused sensor %d", s->id);
129 if (s->activated) //May have been paused before
131 s->activated = false;
132 pthread_join(s->thread, NULL);
135 Data_Close(&(s->data_file)); // Close DataFile
136 s->newest_data.time_stamp = 0;
137 s->newest_data.value = 0;
138 Log(LOGDEBUG, "Stopped sensor %d", s->id);
141 Fatal("Unknown control mode: %d", mode);
146 * Sets all sensors to the desired mode.
147 * @see Sensor_SetMode for more information.
148 * @param mode The mode to be changed to
149 * @param arg An argument specific to the mode to be set.
151 void Sensor_SetModeAll(ControlModes mode, void * arg)
153 for (int i = 0; i < NUMSENSORS; i++)
154 Sensor_SetMode(&g_sensors[i], mode, arg);
159 * Checks the sensor data for unsafe or unexpected results
160 * @param sensor_id - The ID of the sensor
161 * @param value - The value from the sensor to test
163 void Sensor_CheckData(SensorId id, double value)
165 if( value > thresholds[id].max_error || value < thresholds[id].min_error)
167 Log(LOGERR, "Sensor %s at %f is above or below its safety value of %f or %f\n", g_sensor_names[id],value, thresholds[id].max_error, thresholds[id].min_error);
168 //new function that stops actuators?
169 //Control_SetMode(CONTROL_EMERGENCY, NULL)
171 else if( value > thresholds[id].max_warn || value < thresholds[id].min_warn)
173 Log(LOGWARN, "Sensor %s at %f is above or below its warning value of %f or %f\n", g_sensor_names[id],value,thresholds[id].max_warn, thresholds[id].min_warn);
179 * Read a DataPoint from a Sensor; block until value is read
180 * @param id - The ID of the sensor
181 * @param d - DataPoint to set
182 * @returns True if the DataPoint was different from the most recently recorded.
184 bool Sensor_Read(Sensor * s, DataPoint * d)
189 static bool result = true;
191 //TODO: Remove this, code should be refactored to not use so many threads
192 // Although... if it works, it works...
193 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
195 pthread_mutex_lock(&mutex); //TODO: Reduce the critical section
201 gettimeofday(&t, NULL);
202 d->time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime());
204 // Read value based on Sensor Id
205 int value; bool success = true;
206 //TODO: Can probably do this nicer than a switch (define a function pointer for each sensor)
207 // Can probably make the whole sensor thing a lot nicer with a linked list of sensors...
208 // (Then to add more sensors to the software, someone just writes an appropriate read function and calls Sensor_Add(...) at init)
209 // (I will do this. Don't do it before I get a chance, I don't trust you :P)
212 //TODO: Strain gauges should have their own critical section, rest of sensors probably don't need to be in a critical section
214 success &= GPIO_Set(GPIO0_30, true);
215 success &= ADC_Read(ADC0);
216 success &= GPIO_Set(GPIO0_30, false);
218 Fatal("Error reading strain gauge 0");
221 success &= GPIO_Set(GPIO1_28, true);
222 success &= ADC_Read(ADC0);
223 success &= GPIO_Set(GPIO1_28, false);
225 Fatal("Error reading strain gauge 1");
228 success &= GPIO_Set(GPIO0_31, true);
229 success &= ADC_Read(ADC0);
230 success &= GPIO_Set(GPIO0_31, false);
232 success &= GPIO_Set(GPIO1_16, true);
233 success &= ADC_Read(ADC0);
234 success &= GPIO_Set(GPIO1_16, false);
236 Fatal("Error reading strain gauge 2");
239 success &= ADC_Read(ADC1, &value);
242 success &= ADC_Read(ADC5, &value);
244 case PRESSURE_FEEDBACK:
245 success &= ADC_Read(ADC4, &value);
248 success &= ADC_Read(ADC2, &value);
252 bool why_do_i_need_to_do_this = false;
253 success &= GPIO_Read(GPIO0_31, &why_do_i_need_to_do_this);
254 value = (int)why_do_i_need_to_do_this;
259 // Will definitely cause issues included in the same critical section as ADC reads
260 // (since it will be the longest sensor to sample, everything else will have to keep waiting on it)
267 d->value = (double)(value); //TODO: Calibration? Or do calibration in GUI
269 pthread_mutex_unlock(&mutex); //TODO: Reduce the critical section
272 // Perform sanity check based on Sensor's ID and the DataPoint
273 Sensor_CheckData(s->id, d->value);
275 // Update latest DataPoint if necessary
279 s->newest_data.time_stamp = d->time_stamp;
280 s->newest_data.value = d->value;
284 //Not all cases have usleep, easiest here.
285 //TODO: May want to add a control option to adjust the sampling rate for each sensor?
286 // Also, we can get a more accurate sampling rate if instead of a fixed sleep, we calculate how long to sleep each time.
292 Log(LOGDEBUG, "Successfully read sensor %d (for once)", s->id);
294 Log(LOGDEBUG, "Failed to read sensor %d (again)", s->id);
296 return result && success;
300 * Record data from a single Sensor; to be run in a seperate thread
301 * @param arg - Cast to Sensor* - Sensor that the thread will handle
302 * @returns NULL (void* required to use the function with pthreads)
304 void * Sensor_Loop(void * arg)
306 Sensor * s = (Sensor*)(arg);
307 Log(LOGDEBUG, "Sensor %d starts", s->id);
309 // Until the sensor is stopped, record data points
313 //Log(LOGDEBUG, "Sensor %d reads data [%f,%f]", s->id, d.time_stamp, d.value);
314 if (Sensor_Read(s, &d)) // If new DataPoint is read:
316 //Log(LOGDEBUG, "Sensor %d saves data [%f,%f]", s->id, d.time_stamp, d.value);
317 Data_Save(&(s->data_file), &d, 1); // Record it
321 // Needed to keep pthreads happy
323 Log(LOGDEBUG, "Sensor %d finished", s->id);
328 * Get a Sensor given an ID string
329 * @param id_str ID string
330 * @returns Sensor* identified by the string; NULL on error
332 Sensor * Sensor_Identify(const char * id_str)
335 // Parse string as integer
336 int id = strtol(id_str, &end, 10);
342 if (id < 0 || id >= NUMSENSORS)
346 Log(LOGDEBUG, "Sensor \"%s\" identified", g_sensor_names[id]);
351 * Helper: Begin sensor response in a given format
352 * @param context - the FCGIContext
353 * @param id - ID of sensor
354 * @param format - Format
356 void Sensor_BeginResponse(FCGIContext * context, SensorId id, DataFormat format)
362 FCGI_BeginJSON(context, STATUS_OK);
363 FCGI_JSONLong("id", id);
364 FCGI_JSONPair("name", g_sensor_names[id]);
367 FCGI_PrintRaw("Content-type: text/plain\r\n\r\n");
373 * Helper: End sensor response in a given format
374 * @param context - the FCGIContext
375 * @param id - ID of the sensor
376 * @param format - Format
378 void Sensor_EndResponse(FCGIContext * context, SensorId id, DataFormat format)
392 * Handle a request to the sensor module
393 * @param context - The context to work in
394 * @param params - Parameters passed
396 void Sensor_Handler(FCGIContext *context, char * params)
399 gettimeofday(&now, NULL);
400 double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime());
403 double start_time = 0;
404 double end_time = current_time;
405 const char * fmt_str;
408 FCGIValue values[] = {
409 {"id", &id, FCGI_REQUIRED(FCGI_INT_T)},
410 {"format", &fmt_str, FCGI_STRING_T},
411 {"start_time", &start_time, FCGI_DOUBLE_T},
412 {"end_time", &end_time, FCGI_DOUBLE_T},
415 // enum to avoid the use of magic numbers
423 // Fill values appropriately
424 if (!FCGI_ParseRequest(context, params, values, sizeof(values)/sizeof(FCGIValue)))
426 // Error occured; FCGI_RejectJSON already called
430 // Error checking on sensor id
431 if (id < 0 || id >= NUMSENSORS)
433 FCGI_RejectJSON(context, "Invalid sensor id");
436 Sensor * s = g_sensors+id;
438 DataFormat format = Data_GetFormat(&(values[FORMAT]));
441 Sensor_BeginResponse(context, id, format);
444 Data_Handler(&(s->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time);
447 Sensor_EndResponse(context, id, format);