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

303 lines
9.4 KiB
C++
Raw Blame History

/* **************************************************************************
Author: Holger Stork, Achim Gaedke
Created: January 2005
****************************************************************************/
#include "Eurotherm-2000Series.h"
#include <cstdio>
#include <cmath>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <locale.h>
#include <string>
Eurotherm2000Series::Eurotherm2000Series(const std::string& dev_name, int dev_address, int failure_status_mask): tempcont() {
/*
initialization of serial device
*/
device_name=dev_name;
failure_mask=failure_status_mask;
serial_dev=open(dev_name.c_str(),O_RDWR|O_NONBLOCK);
if (serial_dev<0)
throw Eurotherm2000Series_error("could not open serial device");
// set attributes now
termios the_attributes;
the_attributes.c_iflag=0;
the_attributes.c_oflag=0;
// 7 bit per byte, even parity, reciever enabled
the_attributes.c_cflag=CS7|PARENB|CREAD;
the_attributes.c_lflag=0;
// baud rate
cfsetospeed(&the_attributes,B19200);
cfsetispeed(&the_attributes,B19200);
if(tcsetattr(serial_dev,TCSANOW,&the_attributes)!=0)
throw Eurotherm2000Series_error("could not configure serial line");
address=dev_address;
(void)setlocale(LC_NUMERIC,"C");
fp_format="%05.1f";
int_format="%05d.";
hex_format=">%04x";
std::map<std::string,std::string> config;
// configure for <20>K output
config["Q1"]="2.";
// configure for nnn.n format output/precision
config["QD"]="1.";
configure(config);
set_history_stepsize(1);
device_failed=0;
}
void Eurotherm2000Series::configure(const std::map<std::string,std::string>& config) {
std::string error_message;
try {
std::string mode;
const char config_mode[]="0002.";
read_value("IM",mode);
for (std::map<std::string,std::string>::const_iterator i=config.begin();i!=config.end();++i) {
try {
std::string value;
read_value(i->first, value);
fprintf(stderr, "%s: %s (%s)", i->first.c_str(), value.c_str(), i->second.c_str());
if (value!=i->second) {
if (mode!=config_mode) set_value("IM",config_mode);
set_value(i->first,i->second);
mode=config_mode;
}
}
catch (Eurotherm2000Series_error &e){error_message+=i->first+"->"+i->second+", ";}
}
if (mode==config_mode) {
set_value("IM","0000.");
// read superficial zero byte
char buffer[1];
while (read(serial_dev,buffer,1)<=0) sleep(1);
// wait for restarted communication
int timeout=10;
while (1) {
if (timeout<0) throw Eurotherm2000Series_error("could not establish connection after configuring");
try {get_summary_status();} catch(Eurotherm2000Series_error){--timeout;sleep(1); continue;}
break;
}
}
}
catch (const Eurotherm2000Series_error& e) {
throw Eurotherm2000Series_error("got error while configuration: "+std::string(e.what()));
}
if (error_message.size()!=0) {
throw Eurotherm2000Series_error("Could not set "+error_message.substr(0,error_message.size()-1));
}
}
void Eurotherm2000Series::reset() {
set_value("IM","0000.");
// read superficial zero byte
char buffer[1];
while (read(serial_dev,buffer,1)<=0) sleep(1);
// wait for restarted communication
int timeout=10;
while (1) {
if (timeout<0) throw Eurotherm2000Series_error("could not establish connection after reset");
try {get_summary_status();} catch(Eurotherm2000Series_error){--timeout; sleep(1); continue;}
break;
}
}
Eurotherm2000Series::~Eurotherm2000Series() {
/**
close serial device, clean up
*/
close(serial_dev);
}
void Eurotherm2000Series::read_value(const std::string& param_name, std::string& return_value) const{
// assemble message without channel
std::string message;
message+=4;
message+='0'+(address/10);
message+='0'+(address/10);
message+='0'+(address%10);
message+='0'+(address%10);
message+=param_name;
message+=5;
// write message to device
pthread_mutex_lock((pthread_mutex_t*)&device_lock);
write(serial_dev,message.c_str(),message.size());
// read answer
unsigned char got_byte=0;
return_value.clear();
// sleep minimum latency time for parameter reading
timespec read_latency;
read_latency.tv_sec=0; read_latency.tv_nsec=2000000;
nanosleep(&read_latency,NULL);
// now use shorter steps
int timeout=100;
read_latency.tv_nsec=1000000;
while (return_value.size()<2 || return_value[return_value.size()-2]!=3) {
int result=read(serial_dev,&got_byte,1);
if (result<=0) {
if (result<0 && errno!=EAGAIN) {
return_value.clear();
pthread_mutex_unlock((pthread_mutex_t*)&device_lock);
throw Eurotherm2000Series_error("read_value: error while reading");
}
if (timeout<0) {
return_value.clear();
pthread_mutex_unlock((pthread_mutex_t*)&device_lock);
throw Eurotherm2000Series_error("read_value: timeout while reading");
}
// go on sleeping for a while
nanosleep(&read_latency,NULL);
timeout--;
continue;
}
if (return_value.size()==0 && got_byte==4) throw Eurotherm2000Series_error("read_value: invalid register");
return_value+=got_byte;
}
pthread_mutex_unlock((pthread_mutex_t*)&device_lock);
if (0) {
for (std::string::const_iterator i=return_value.begin();i!=return_value.end();++i)
fprintf(stderr,"%02x",*i);
fprintf(stderr,"\n");
}
// test bcc
unsigned char bcc=return_value[1];
for (size_t i=2;i<return_value.size()-1;++i) bcc^=return_value[i];
if (bcc!=got_byte) {
return_value.clear();
throw Eurotherm2000Series_error("read_value: bcc missmatch");
}
// compare C1 and C2
if (return_value.substr(1,param_name.size())!=param_name) {
return_value.clear();
throw Eurotherm2000Series_error("read_value: register name echo mismatch");
}
// cut protocol bytes
return_value.erase(0,3);
return_value.resize(return_value.size()-2);
return;
}
int Eurotherm2000Series::set_value(const std::string& param_name, const std::string& value) {
// comunicate with device
std::string message;
message+=4; //EOT
message+='0'+(address/10);
message+='0'+(address/10);
message+='0'+(address%10);
message+='0'+(address%10);
message+=2; //STX
message+=param_name.substr(0,2);
message+=value;
message+=3; //ETX
// calculate bcc byte
unsigned char bcc=message[6];
for (size_t i=7;i<message.size();++i) bcc^=message[i];
message+=(unsigned char)bcc; //BCC
#if 0
printf("param: %s, value: %s: ",param_name.c_str(),value.c_str());
for (size_t i=0; i<message.size();i++)
printf("%02x ",(unsigned char)message[i]);
printf("\n");
#endif
pthread_mutex_lock(&device_lock);
write(serial_dev,message.c_str(),message.size());
// sleep minimum latency time for value setting
timespec write_latency;
write_latency.tv_sec=0; write_latency.tv_nsec=5000000;
nanosleep(&write_latency,NULL);
// wait shorter while polling
write_latency.tv_nsec=1000000;
int timeout=1000;
unsigned char got_byte;
int result;
while ((result=read(serial_dev,&got_byte,1))<=0) {
if (result<0 && errno!=EAGAIN) {
pthread_mutex_unlock(&device_lock);
throw Eurotherm2000Series_error("set_value: read error");
}
if (timeout<0) {
pthread_mutex_unlock(&device_lock);
throw Eurotherm2000Series_error("set_value: timeout occurred");
}
nanosleep(&write_latency,NULL);
timeout--;
}
pthread_mutex_unlock(&device_lock);
if (got_byte==0x15) {
char message_buffer[256];
std::string ee_answer;
read_value("EE",ee_answer);
snprintf(message_buffer, 256, "set_value: negative acknoledge (ee=0x%02x)",ee_answer.c_str()[0]);
throw Eurotherm2000Series_error(message_buffer);
}
if (got_byte!=6) {
char message_buffer[256];
snprintf(message_buffer,256,"(0x%02x)",got_byte);
throw Eurotherm2000Series_error(std::string("set_value: no acknoledge ")+message_buffer);
}
return 0;
}
double Eurotherm2000Series::get_temperature() const {
int summary_status=get_summary_status();
if (failure_mask&summary_status) {
char message_buffer[10];
snprintf(message_buffer,10,"0x%04x",summary_status);
throw Eurotherm2000Series_error(std::string("sensor/heater/temperature range fault: ")+message_buffer);
}
std::string answer;
read_value("PV",answer);
return strtod(answer.c_str(),NULL);
}
double Eurotherm2000Series::set_setpoint(double ct) {
char buffer[8];
snprintf(buffer,8,fp_format.c_str(),ct);
set_value("SL",buffer);
// extra wait, until the get_setpoint function returns the same value
timespec write_latency;
write_latency.tv_sec=0; write_latency.tv_nsec=50000000;
nanosleep(&write_latency,NULL);
write_latency.tv_nsec=100000000;
int i=100;
while(fabs(get_setpoint()-ct)>1e-4) {
if (i<0) throw Eurotherm2000Series_error("could not confirm setpoint value settings, timeout");
nanosleep(&write_latency,NULL);
--i;
}
return ct;
}
double Eurotherm2000Series::get_setpoint() const {
std::string answer;
read_value("SL",answer);
return strtod(answer.c_str(),NULL);
}
int Eurotherm2000Series::get_summary_status() const {
std::string answer;
read_value("SO",answer);
if (answer[0]!='>') throw Eurotherm2000Series_error("could not handle status data format \'"+answer+"\'");
answer.erase(0,1);
return strtol(answer.c_str(),NULL,16);
}
void Eurotherm2000Series::get_setpoint_limits(double& min, double& max) const {
std::string answer;
read_value("LS",answer);
min=strtod(answer.c_str(),NULL);
read_value("HS",answer);
max=strtod(answer.c_str(),NULL);
}