damaris-backends/drivers/tempcont.cpp
2022-11-06 21:27:27 +01:00

251 lines
8.8 KiB
C++

#include "tempcont.h"
#include <cmath>
#include "errno.h"
#include <xercesc/util/XMLString.hpp>
#include <xercesc/dom/DOMElement.hpp>
void temp_history::print_xml(FILE* f) const {
if (step==0) {fprintf(f,"<temperature/>\n");}
char timebuffer[30];
ctime_r(&latest,timebuffer);
timebuffer[strlen(timebuffer)-1]=0;
fprintf(f,"<temperature time=\"%s\" step=\"%zu\">",timebuffer,step);
if (!empty()) {
fprintf(f,"\n");
for (const_iterator i=begin(); i!=end();++i) fprintf(f,"%g ",*i);
fprintf(f,"\n");
}
fprintf(f,"</temperature>\n");
}
configuration_result* temp_history::as_result() const {
configuration_result* res=new configuration_result(0);
XMLCh* myname=XERCES_CPP_NAMESPACE_QUALIFIER XMLString::transcode("temphistory");
XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* newelement=res->tag->createElement(myname);
res->tag->insertBefore(newelement,NULL);
XERCES_CPP_NAMESPACE_QUALIFIER XMLString::release(&myname);
// print step [s]
XMLCh* step_name=XERCES_CPP_NAMESPACE_QUALIFIER XMLString::transcode("step");
char step_value_char_buf[100];
snprintf(step_value_char_buf,sizeof(step_value_char_buf),"%zu",step);
XMLCh* step_value=XERCES_CPP_NAMESPACE_QUALIFIER XMLString::transcode(step_value_char_buf);
newelement->setAttribute(step_name,step_value);
XERCES_CPP_NAMESPACE_QUALIFIER XMLString::release(&step_name);
XERCES_CPP_NAMESPACE_QUALIFIER XMLString::release(&step_value);
// print latest timestamp
XMLCh* latest_name=XERCES_CPP_NAMESPACE_QUALIFIER XMLString::transcode("latest");
char latest_value_char_buf[200];
struct tm latest_broken_timeinfo;
localtime_r(&latest, &latest_broken_timeinfo);
strftime(latest_value_char_buf,sizeof(latest_value_char_buf),"%Y%m%d %H:%M:%S",&latest_broken_timeinfo);
XMLCh* latest_value=XERCES_CPP_NAMESPACE_QUALIFIER XMLString::transcode(latest_value_char_buf);
newelement->setAttribute(latest_name,latest_value);
XERCES_CPP_NAMESPACE_QUALIFIER XMLString::release(&latest_name);
XERCES_CPP_NAMESPACE_QUALIFIER XMLString::release(&latest_value);
// append values to buffer
if (!empty()) {
for (const_iterator i=begin(); i!=end(); ++i){
char temp_char_buf[256];
snprintf(temp_char_buf,sizeof(temp_char_buf),"%g\n",*i);
XMLCh* temp_line=XERCES_CPP_NAMESPACE_QUALIFIER XMLString::transcode(temp_char_buf);
XERCES_CPP_NAMESPACE_QUALIFIER DOMText* textnode=res->tag->createTextNode(temp_line);
XERCES_CPP_NAMESPACE_QUALIFIER XMLString::release(&temp_line);
newelement->insertBefore(textnode,NULL);
}
}
return res;
}
void* history_maintainance_thread(void* temperature_sensor) {
tempcont* ts=(tempcont*)temperature_sensor;
// wait for completely initialised structure
ts->maintain_history();
return NULL;
}
temp_history* tempcont::get_history(size_t seconds_back) const {
temp_history* result_history=new temp_history();
if (result_history==NULL) throw tempcont_error("failed to create new temp_history object");
if (history_step!=0) {
pthread_mutex_lock((pthread_mutex_t*)&history_lock);
for (size_t i=0; ((seconds_back==0 || i*history_step<seconds_back) && i<history_used); ++i)
result_history->push_back(history_buffer[(history_latest_index+history_length-i)%history_length]);
pthread_mutex_unlock((pthread_mutex_t*)&history_lock);
result_history->step=history_step;
result_history->latest=history_latest_time;
}
return result_history;
}
void tempcont::set_history_stepsize(size_t step) {
if (history_step==step) return;
pthread_mutex_lock((pthread_mutex_t*)&history_lock);
history_step=step;
if (step==0) {
// reinitalise history
history_used=0;
}
else {
history_latest_index=0;
history_used=1;
history_buffer[0]=get_temperature();
history_latest_time=time(NULL);
}
pthread_mutex_unlock((pthread_mutex_t*)&history_lock);
}
double tempcont::wait_setpoint_reached(double delta, size_t timeout) const {
// poll temperature every second
time_t end_polling=time(NULL)+timeout;
double setpoint=get_setpoint();
double temperature=get_temperature();
while (fabs(temperature-setpoint)>delta && (timeout==0 || end_polling>=time(NULL))) {
sleep(1);
temperature=get_temperature();
}
return temperature;
}
void tempcont::maintain_history() {
// wait for initialisation of derived class or failure
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
// sleep 0.2 seconds, that assures a check every second
timespec sleeptime;
sleeptime.tv_sec=0;
sleeptime.tv_nsec=200*1000*1000;
while (1) {
// do not maintain history until step is set to a reasonable value
if (history_step!=0) {
pthread_mutex_lock(&history_lock);
time_t now=time(NULL);
if (history_step<=difftime(now, history_latest_time)) {
double new_temp;
try {
new_temp=get_temperature();
}
catch (tempcont_error &e) {
pthread_mutex_unlock(&history_lock);
fprintf(stderr,"history maintainance thread caught exception: %s, terminating\n",e.what());
device_failed=1;
pthread_exit(NULL);
}
// append the new temperature value
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
history_latest_time=now;
if (history_used==0) {
history_latest_index=0;
history_buffer[0]=new_temp;
history_used=1;
}
else {
history_latest_index++;
if (history_latest_index==history_length) history_latest_index=0;
history_buffer[history_latest_index]=new_temp;
if (history_used<history_length) history_used++;
}
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
pthread_mutex_unlock(&history_lock);
// continue directly with next check
continue;
}
else {
pthread_mutex_unlock(&history_lock);
}
}
nanosleep(&sleeptime,NULL);
}
return;
}
configuration_result* tempcont::configure(const configuration_device_section& conf, int run) {
std::map<const std::string,std::string>::const_iterator get=conf.attributes.find("get");
if (get!=conf.attributes.end()) {
temp_history* h=get_history(0);
configuration_result* res=h->as_result();
delete h;
return res;
}
std::map<const std::string,std::string>::const_iterator set=conf.attributes.find("set");
if (set!=conf.attributes.end()) {
// read new temperature setpoint
const char* temp_str=set->second.c_str();
char* temp_str_end=NULL;
double new_temperature=strtod(temp_str,&temp_str_end);
if (temp_str_end!=set->second.size()+temp_str) {
throw tempcont_error("could not read new temperature setpoint from attributes");
}
// set the temperature
double new_temp_return=set_setpoint(new_temperature);
if (new_temp_return!=new_temperature) {
char error_message[256];
snprintf(error_message, sizeof(error_message), "could not set new temperature setpoint %f!=%f",
new_temp_return,
new_temperature);
throw tempcont_error(error_message);
}
// report success
configuration_result* res=new configuration_result(0);
XMLCh* myname=XERCES_CPP_NAMESPACE_QUALIFIER XMLString::transcode(conf.name.c_str());
XERCES_CPP_NAMESPACE_QUALIFIER DOMElement* newelement=res->tag->createElement(myname);
res->tag->insertBefore(newelement,NULL);
XERCES_CPP_NAMESPACE_QUALIFIER XMLString::release(&myname);
return res;
}
return NULL;
}
tempcont::tempcont() {
// initialise history
history_step=0;
// one day history (1s steps)
history_length=24*60*60;
history_used=0;
history_buffer=(double*)malloc(sizeof(double)*history_length);
if (history_buffer==NULL) throw tempcont_error("failed to allocate history");
// no failures detected
device_failed=0;
// create history lock
pthread_mutexattr_t history_lock_attr;
pthread_mutexattr_init(&history_lock_attr);
pthread_mutex_init(&history_lock,&history_lock_attr);
pthread_mutexattr_destroy(&history_lock_attr);
// create device lock
pthread_mutexattr_t device_lock_attr;
pthread_mutexattr_init(&device_lock_attr);
pthread_mutex_init(&device_lock,&device_lock_attr);
pthread_mutexattr_destroy(&device_lock_attr);
// create history update thread
pthread_attr_t history_thread_attr;
pthread_attr_init(&history_thread_attr);
int result=pthread_create(&history_thread,&history_thread_attr,&history_maintainance_thread,this);
pthread_attr_destroy(&history_thread_attr);
// clean up, it this operation failed
if (result!=0) {
free(history_buffer);
pthread_mutex_destroy(&device_lock);
pthread_mutex_destroy(&history_lock);
throw tempcont_error("could not create history maintainance thread");
}
}
tempcont::~tempcont() {
// send cancelation
if (0!=pthread_cancel(history_thread) && errno!=EIO) {
fprintf(stderr,"thread cancelation failed\n");
}
// wait for terminated thread
if (pthread_join(history_thread,NULL)!=0){
fprintf(stderr,"thread joining failed\n");
};
if (history_buffer!=NULL) free(history_buffer);
pthread_mutex_destroy(&device_lock);
pthread_mutex_destroy(&history_lock);
}