I wrote a C++ class that us used for serial I/O and another class that is a wrapper for a specific device.
I noticed that the output can randomly vary. It's like if it misses lines or something.
For example calling GETTEMP this is common:
Notice how half the time it never actually does get a number. The :: is the prompt, which is normal to get, but there has to be data after that, and often it gets skipped. I tried adding a delay, but it actually causes it to skip them every time, I don't get why it would do this.
I think my problem is I don't have a proper way to check for EOF. What is the proper way to do that?
Here are the classes:
Sorry, it's kinda long
I noticed that the output can randomly vary. It's like if it misses lines or something.
For example calling GETTEMP this is common:
Code:
[Feb-21-2012 02:40:56am] (4) /dev/ttyACM0[00] SendCommand: CH1.GETTEMP
[Feb-21-2012 02:40:57am] (4) /dev/ttyACM0[00] RecvOutput: ::
19.3750
[Feb-21-2012 02:40:58am] (4) /dev/ttyACM0[00] SendCommand: CH2.GETTEMP
[Feb-21-2012 02:40:58am] (4) /dev/ttyACM0[00] RecvOutput: ::
[Feb-21-2012 02:40:59am] (4) /dev/ttyACM0[00] SendCommand: CH3.GETTEMP
[Feb-21-2012 02:40:59am] (4) /dev/ttyACM0[00] RecvOutput: ::
0.0000
[Feb-21-2012 02:41:00am] (4) /dev/ttyACM0[00] SendCommand: CH4.GETTEMP
[Feb-21-2012 02:41:00am] (4) /dev/ttyACM0[00] RecvOutput: ::
[Feb-21-2012 02:41:01am] (4) /dev/ttyACM0[00] SendCommand: CH5.GETTEMP
[Feb-21-2012 02:41:01am] (4) /dev/ttyACM0[00] RecvOutput: ::
[Feb-21-2012 02:41:02am] (4) /dev/ttyACM0[00] SendCommand: CH6.GETTEMP
[Feb-21-2012 02:41:02am] (4) /dev/ttyACM0[00] RecvOutput: ::
Notice how half the time it never actually does get a number. The :: is the prompt, which is normal to get, but there has to be data after that, and often it gets skipped. I tried adding a delay, but it actually causes it to skip them every time, I don't get why it would do this.
I think my problem is I don't have a proper way to check for EOF. What is the proper way to do that?
Here are the classes:
Sorry, it's kinda long
Code:
#ifndef SerialConnector_h
#define SerialConnector_h
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
class SerialConnector
{
private:
string m_Path;
int m_fd;
bool m_conactive;
void ScreenOutput(string output); //output to screen if debug is on
public:
bool DebugMode;
SerialConnector();
SerialConnector(string path);
void SetPath(string path); //set working path (ex: /dev/tty0)
bool Open(); //connect
void Close(); //disconnect
bool ConActive(); //whether connection has been lost
string ReadLine(bool waitindef=true,bool nlonly=false); //get all data until \r\n is hit, or only \n if arg is true
bool WriteLine(string line, bool nlonly=false); //writes line of data followed by \r\n or \n if 2nd arg is true
};
#endif
SerialConnector::SerialConnector()
{
m_Path="";
m_conactive=false;
DebugMode=false;
m_fd=0;
}
SerialConnector::SerialConnector(string path)
{
m_Path=path;
m_conactive=false;
DebugMode=false;
m_fd=0;
}
void SerialConnector::ScreenOutput(string output)
{
if(DebugMode)LOGGER->Log(5, m_Path + ": " + output);
}
void SerialConnector::SetPath(string path)
{
m_Path=path;
}
bool SerialConnector::Open()
{
if(m_Path=="")
{
ScreenOutput("No path specified");
return false;
}
m_fd=open(m_Path.c_str(), O_RDWR | O_NONBLOCK);
fcntl(m_fd, F_SETFL, O_NONBLOCK);
if (m_fd == -1 )
{
ScreenOutput("Unable to connect");
m_conactive=false;
return false;
}
m_conactive=true;
return true;
}
void SerialConnector::Close()
{
m_conactive=false;
close(m_fd);
}
bool SerialConnector::ConActive()
{
return m_conactive;
}
string SerialConnector::ReadLine(bool waitindef,bool nlonly)
{
string ret;
if(!m_conactive)return "";
//read a single line:
char buff[1]; //array (the read takes an array, not a single char)
int bytes=0;
//TODO: Add some kind of safety guard to avoid getting stuck in an infinite "no data" loop
while(1)
{
bytes= read(m_fd,buff,1);
if(bytes<0)
{
if(errno==11)
{
if(waitindef)
{
continue; //keep trying
}
break; //EAGAIN: No more data
}
ScreenOutput("Error while reading. Errno:" + Int2Str(errno));
return "";
}
else if(bytes==0)
{
//lost connection
ScreenOutput("Lost connection");
m_conactive=false;
break;
}
if(buff[0]=='\r')
{
if(!nlonly)
{
read(m_fd,buff,1);//read the following char which should be \n
if(buff[0]=='\n')break;
}
else break;
}
else
{
ret.append(1,buff[0]);
}
if(ret.size()>1024) //hard coded limit to catch issue where device goes haywire and loops forever.
{
break;
}
}
return ret;
}
bool SerialConnector::WriteLine(string line,bool nlonly)
{
if(!m_conactive)return false;
string towrite = line + (nlonly? "\n" : "\r\n");
return write(m_fd,towrite.c_str(),towrite.size());
}
#ifndef uk1104Connector_h
#define uk1104Connector_h
#include <stdlib.h>
#include "SerialConnector.h"
class uk1104Connector
{
private:
SerialConnector * m_serialcon;
string m_ttydevice; //TTY device (ex: /dev/ttyACM) do not specify number,
int m_ttydevicesec; //TTY sequence (Ex: ttyACM1, 2, 3 etc)
string m_lastcommand; //last command sent
string m_deviceid; //canakit device ID
public:
bool DebugMode; //enable debug
bool HaltAll; //used to halt all activity
uk1104Connector(string ttydevice, string deviceid); //ex: /dev/TTYACM, 10 where deviceid is the ID assigned to the UK1104 device (not the TTY number)
~uk1104Connector();
void ScreenOutput(string output); //output to screen if debug mode is on
bool Connect(int inctries=10); //connects, if it fails, it tries another TTY number, and keeps trying up to specified number
void Disconnect();
void ClearBuffer(); //reads any pending data to ensure buffer is clear
string ClearNonNum(string data); //clears char that is not a number or period
bool SendCommand(string cmd); //sends command to device
string RecvOutput(bool clearcmdstring=false); //get output from last command
//relay control:
bool RelOn(int rel); //turn relay on
bool RelOff(int rel); //turn relay on
//Channel control:
bool SetChMode(int ch, int mode, int tempres=12); //set mode (1=digital output, 2=digital input, 3=analog input*, 4=temp sensor)
//* when setting a channel to analog all channels before it are also set analog
//Channel get:
float GetTemp(int ch); //get temp
string GetRels(); //gets relay status as a string ex: 0110 means relay 2 and 3 are on.
};
#endif
uk1104Connector::uk1104Connector(string tty,string deviceid="00")
{
m_serialcon = NULL;
m_serialcon = new SerialConnector();
m_ttydevice = tty;
m_deviceid = deviceid;
DebugMode = false;
HaltAll = false;
m_ttydevicesec=0;
}
uk1104Connector::~uk1104Connector()
{
if(m_serialcon->ConActive())Disconnect();
delete m_serialcon;
}
void uk1104Connector::ScreenOutput(string output)
{
if(DebugMode)LOGGER->Log(4,m_ttydevice + Int2Str(m_ttydevicesec) + "[" + m_deviceid + "] " + output);
}
bool uk1104Connector::Connect(int inctries)
{
if(m_serialcon==NULL || HaltAll)return false;
int ttynum=0;
//try to find an open serial port
//TODO: Ensure we are connecting to a uk1104 device, and that the ID is the one specified
for(;ttynum<inctries;ttynum++)
{
m_serialcon->DebugMode=DebugMode;
m_serialcon->SetPath(m_ttydevice + Int2Str(ttynum));
m_ttydevicesec=ttynum;
if(m_serialcon->Open())
{
ScreenOutput("Connected to port, verifying device ID...");
//cout<<"[connected]"<<flush; //debug
//we have connected, now ensure ID is correct:
//cout<<"if1"<<endl; //debug
SendCommand(""); //get initial prompt
SendCommand("about"); //get device id
//cout<<"if2"<<endl; //debug
//cout<<"[commandssent]"<<flush; //debug
bool validdevice=false;
//catch data till we see ID:
for(int i=0;i<7;i++)
{
//cout<<"for1"<<endl; //debug
//cout<<"[loopstart]"<<flush; //debug
string aboutdata = RecvOutput(false);
//cout<<"[loopdata: "<<aboutdata<<"]"<<flush; //debug
//cout<<"for2"<<endl;//debug
int pos = aboutdata.find("ID:");
if(pos!=string::npos && aboutdata.size()>=pos+6)
{
//cout<<"[found]"<<flush; //debug
string id = aboutdata.substr(pos+4,2);
ScreenOutput("Connected to device ID " + id);
if(id==m_deviceid)validdevice=true;
break;
}
//cout<<"[loopend]"<<flush; //debug
}
ClearBuffer();
if(!validdevice)
{
m_serialcon->Close();
continue;
}
return true;
}
}
return false;
}
void uk1104Connector::Disconnect()
{
ScreenOutput("Disconnected");
m_serialcon->Close();
}
void uk1104Connector::ClearBuffer()
{
if(HaltAll || !m_serialcon->ConActive())return;
//cout<<"clearbuff1"<<endl; //debug
do
{
//usleep(500);
}while(m_serialcon->ReadLine(false,false)!="");
//cout<<"clearbuff2"<<endl; //debug
}
string uk1104Connector::ClearNonNum(string data)
{
string ret;
for(int i=0;i<data.size();i++)
{
switch(data[i])
{
case 0x30: ret.append(1,data[i]);break;
case 0x31: ret.append(1,data[i]);break;
case 0x32: ret.append(1,data[i]);break;
case 0x33: ret.append(1,data[i]);break;
case 0x34: ret.append(1,data[i]);break;
case 0x35: ret.append(1,data[i]);break;
case 0x36: ret.append(1,data[i]);break;
case 0x37: ret.append(1,data[i]);break;
case 0x38: ret.append(1,data[i]);break;
case 0x39: ret.append(1,data[i]);break;
case 0x2E: ret.append(1,data[i]);break;
}
}
return ret;
}
bool uk1104Connector::SendCommand(string cmd)
{
if(HaltAll || !m_serialcon->ConActive())return false;
sleep(1);
m_serialcon->DebugMode=DebugMode;
ClearBuffer();
m_lastcommand=cmd;
bool ret=true;
ScreenOutput("SendCommand: " + cmd);
ret=m_serialcon->WriteLine(cmd);
//lost con, try to reconnect...
if(!m_serialcon->ConActive())
{
ScreenOutput("Lost connection, attempting to reconnect...");
if(!Connect())return false;
ret=m_serialcon->WriteLine(cmd); //try again...
}
return ret;
}
string uk1104Connector::RecvOutput(bool clearcmdstring)
{
if(!m_serialcon->ConActive() || HaltAll)return "";
m_serialcon->DebugMode=DebugMode;
//cout<<"recvout1"<<endl; //debug
string output = m_serialcon->ReadLine(); //get value
//device may be messing up, need to try to shut the relays off and exit as gracefully as possible
if(output.find("ERROR")!=string::npos)
{
ScreenOutput("FATAL possible runaway error occured. Attempting to shut off all relays and bailing out...");
Disconnect();
//use system to shut relays to bypass everything having to do with this class
system(("echo 'rels.off' >" + m_ttydevice + Int2Str(m_ttydevicesec)).c_str());
return "";
}
//cout<<"recvout2"<<endl; //debug
if(clearcmdstring) //since command string is outputted by default, get rid of it
{
//cout<<"OUT: ["<<output<<"]"<<endl; //debug
int pos = output.find(m_lastcommand);
int size = m_lastcommand.size();
if(pos!=string::npos && (pos+size)<=output.size())
{
output.erase(pos,size);
}
//cout<<"AFT: ["<<output<<"]"<<endl; //debug
}
ScreenOutput("RecvOutput: " + output);
return output;
}
bool uk1104Connector::RelOn(int rel)
{
if(HaltAll || !m_serialcon->ConActive())return false;
if(rel<1)rel=1;
else if(rel>4)rel=4;
return SendCommand("REL" + Int2Str(rel) + ".ON");
}
bool uk1104Connector::RelOff(int rel)
{
if(HaltAll || !m_serialcon->ConActive())return false;
if(rel<1)rel=1;
else if(rel>4)rel=4;
return SendCommand("REL" + Int2Str(rel) + ".OFF");
}
bool uk1104Connector::SetChMode(int ch, int mode,int tempres)
{
if(HaltAll || !m_serialcon->ConActive())return false;
if(ch<1)ch=1;
else if(ch>6)ch=6;
if(tempres<9)tempres=9;
else if(tempres>12)tempres=12;
bool ret=true;
ret = SendCommand("CH" + Int2Str(ch) + ".SETMODE("+Int2Str(mode)+")");
if(ret && mode==4) //set temp precision to max
{
ret = SendCommand("CH" + Int2Str(ch) + ".SETTEMPRES("+Int2Str(tempres)+")");
}
return ret;
}
float uk1104Connector::GetTemp(int ch)
{
if(HaltAll || !m_serialcon->ConActive())return -999;
if(ch<1)ch=1;
if(ch>6)ch=6;
int ret=0;
string tempstr;
SendCommand("CH" + Int2Str(ch) + ".GETTEMP");
tempstr = RecvOutput(true);
//cout<<"preval:["<<tempstr<<"]"<<endl;//debug
tempstr=ClearNonNum(tempstr);
//cout<<"val:["<<tempstr<<"]"<<endl; //debug
return atof(tempstr.c_str());
}
string uk1104Connector::GetRels()
{
if(HaltAll || !m_serialcon->ConActive())return "";
string tempstr;
if(!SendCommand("rels.get"))return "";
tempstr = RecvOutput(true);
string ret;
for(int i=0;i<tempstr.size();i++)
{
if(tempstr[i]=='0')ret.append(1,'0');
else if(tempstr[i]=='1')ret.append(1,'1');
}
return ret;
}
