kopia lustrzana https://github.com/weetmuts/wmbusmeters
1642 wiersze
43 KiB
C++
1642 wiersze
43 KiB
C++
/*
|
|
Copyright (C) 2017-2020 Fredrik Öhrström
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include"util.h"
|
|
#include"rtlsdr.h"
|
|
#include"serial.h"
|
|
#include"shell.h"
|
|
#include"threads.h"
|
|
#include"timings.h"
|
|
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <functional>
|
|
#include <libgen.h>
|
|
#include <memory.h>
|
|
#include <pthread.h>
|
|
#include <sys/file.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#if defined(__linux__)
|
|
#include <linux/serial.h>
|
|
#endif
|
|
|
|
// return a positive integer (file descriptor) on success.
|
|
// return -1 for failure to open. return -2 for already locked.
|
|
static int openSerialTTY(const char *tty, int baud_rate, PARITY parity);
|
|
static string showTTYSettings(int fd);
|
|
|
|
struct SerialDeviceImp;
|
|
struct SerialDeviceTTY;
|
|
struct SerialDeviceCommand;
|
|
struct SerialDeviceFile;
|
|
struct SerialDeviceSimulator;
|
|
struct Timer
|
|
{
|
|
int id;
|
|
int seconds;
|
|
time_t last_call;
|
|
function<void()> callback;
|
|
string name;
|
|
|
|
bool isTime(time_t now)
|
|
{
|
|
return (last_call+seconds) <= now;
|
|
}
|
|
};
|
|
|
|
struct SerialCommunicationManagerImp : public SerialCommunicationManager
|
|
{
|
|
SerialCommunicationManagerImp(time_t exit_after_seconds, bool start_event_loop);
|
|
~SerialCommunicationManagerImp();
|
|
|
|
shared_ptr<SerialDevice> createSerialDeviceTTY(string dev, int baud_rate, PARITY parity, string purpose);
|
|
shared_ptr<SerialDevice> createSerialDeviceCommand(string identifier, string command, vector<string> args,
|
|
vector<string> envs, string purpose);
|
|
shared_ptr<SerialDevice> createSerialDeviceFile(string file, string purpose);
|
|
shared_ptr<SerialDevice> createSerialDeviceSimulator();
|
|
|
|
void listenTo(SerialDevice *sd, function<void()> cb);
|
|
void onDisappear(SerialDevice *sd, function<void()> cb);
|
|
|
|
void expectDevicesToWork();
|
|
void stop();
|
|
void startEventLoop();
|
|
void waitForStop();
|
|
bool isRunning();
|
|
|
|
shared_ptr<SerialDevice> addSerialDeviceForManagement(SerialDevice *sd);
|
|
void tickleEventLoop();
|
|
void removeNonWorkingSerialDevices();
|
|
void closeAllDoNotRemove();
|
|
|
|
int startRegularCallback(string name, int seconds, function<void()> callback);
|
|
void stopRegularCallback(int id);
|
|
|
|
AccessCheck checkAccess(string device,
|
|
shared_ptr<SerialCommunicationManager> manager,
|
|
string extra_info,
|
|
function<AccessCheck(string,shared_ptr<SerialCommunicationManager>)> extra_probe);
|
|
|
|
vector<string> listSerialTTYs();
|
|
shared_ptr<SerialDevice> lookup(std::string device);
|
|
bool removeNonWorking(std::string device);
|
|
|
|
private:
|
|
|
|
void *eventLoop();
|
|
void *timerLoop();
|
|
|
|
void executeTimerCallbacks();
|
|
time_t calculateTimeToNearestTimerCallback(time_t now);
|
|
|
|
bool running_ {};
|
|
bool expect_devices_to_work_ {}; // false during detection phase, true when running.
|
|
time_t start_time_ {};
|
|
time_t exit_after_seconds_ {};
|
|
|
|
vector<shared_ptr<SerialDevice>> serial_devices_;
|
|
RecursiveMutex serial_devices_mutex_ = { "serial_devices_mutex" };
|
|
#define LOCK_SERIAL_DEVICES(where) WITH(serial_devices_mutex_, serial_devices_mutex, where)
|
|
|
|
RecursiveMutex event_loop_mutex_ = {"event_loop_mutex" };
|
|
#define LOCK_EVENT_LOOP(where) WITH(event_loop_mutex_, event_loop_mutex, where)
|
|
|
|
vector<Timer> timers_; // Protected by LOCK_TIMERS
|
|
RecursiveMutex timers_mutex_ = { "timers_mutex" };
|
|
#define LOCK_TIMERS(where) WITH(timers_mutex_, timers_mutex, where)
|
|
};
|
|
|
|
SerialCommunicationManagerImp::~SerialCommunicationManagerImp()
|
|
{
|
|
// Stop the loop.
|
|
stop();
|
|
// Grab the event_loop_lock. This can only be done when the eventLoop has stopped running.
|
|
event_loop_mutex_.lock();
|
|
// Close all managed devices (not yet closed)
|
|
closeAllDoNotRemove();
|
|
// Remove all closed devices.
|
|
removeNonWorkingSerialDevices();
|
|
// Now we can be sure the eventLoop has stopped and it is safe to
|
|
// free this Manager object.
|
|
}
|
|
|
|
struct SerialDeviceImp : public SerialDevice
|
|
{
|
|
void disableCallbacks() { no_callbacks_ = true; }
|
|
void enableCallbacks() { no_callbacks_ = false; }
|
|
bool skippingCallbacks() { return no_callbacks_; }
|
|
void fill(vector<uchar> &data) {};
|
|
int receive(vector<uchar> *data);
|
|
bool waitFor(uchar c);
|
|
bool working() { return resetting_ || fd_ != -1; }
|
|
bool resetting() { return resetting_; }
|
|
bool opened() { return resetting_ || fd_ != -2; }
|
|
bool isClosed() { return fd_ == -1 && !resetting_; }
|
|
bool readonly() { return is_stdin_ || is_file_; }
|
|
|
|
void expectAscii() { expecting_ascii_ = true; }
|
|
void setIsFile() { is_file_ = true; }
|
|
void setIsStdin() { is_stdin_ = true; }
|
|
string device() { return ""; }
|
|
int fd() { return fd_; }
|
|
SerialCommunicationManager *manager() { return manager_; }
|
|
void resetInitiated() { debug("(serial) initiate reset\n"); resetting_ = true; }
|
|
void resetCompleted() { debug("(serial) reset completed\n"); resetting_ = false; }
|
|
bool checkIfDataIsPending()
|
|
{
|
|
if (!opened() || !working()) return false; // No data can be pending if device is not opened nor working.
|
|
int available = -1;
|
|
int rc = ioctl(fd_, FIONREAD, &available);
|
|
if (rc == -1) return false;
|
|
return available > 0;
|
|
}
|
|
|
|
SerialDeviceImp(SerialCommunicationManagerImp *manager, string purpose)
|
|
{
|
|
manager_ = manager;
|
|
purpose_ = purpose;
|
|
}
|
|
~SerialDeviceImp() = default;
|
|
|
|
protected:
|
|
|
|
RecursiveMutex read_mutex_ = { "read_mutex" };
|
|
#define LOCK_READ_SERIAL(where) WITH(read_mutex_, read_mutex, where)
|
|
|
|
RecursiveMutex write_mutex_ = { "write_mutex" };
|
|
#define LOCK_WRITE_SERIAL(where) WITH(write_mutex_, write_mutex, where)
|
|
|
|
function<void()> on_data_;
|
|
function<void()> on_disappear_;
|
|
int fd_ = -2; // -2 not yet opened, -1 not working
|
|
bool expecting_ascii_ {}; // If true, print using safeString instead if bin2hex
|
|
bool is_file_ = false;
|
|
bool is_stdin_ = false;
|
|
// When feeding from stdin, to prevent early exit, we want
|
|
// at least some data before leaving the loop!
|
|
// I.e. do not exit before we have received something!
|
|
bool no_callbacks_ = false;
|
|
SerialCommunicationManagerImp *manager_;
|
|
bool resetting_ {}; // Set to true while resetting.
|
|
string purpose_; // Can be set to identify a serial device purose.
|
|
|
|
friend struct SerialCommunicationManagerImp;
|
|
};
|
|
|
|
bool SerialDeviceImp::waitFor(uchar c)
|
|
{
|
|
vector<uchar> data;
|
|
for (;;)
|
|
{
|
|
int n = receive(&data);
|
|
if (n == 0) break;
|
|
if (data.back() == c) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int SerialDeviceImp::receive(vector<uchar> *data)
|
|
{
|
|
LOCK_READ_SERIAL(receive);
|
|
|
|
bool close_me = false;
|
|
|
|
data->clear();
|
|
int num_read = 0;
|
|
|
|
while (true)
|
|
{
|
|
data->resize(num_read+1024);
|
|
int nr = read(fd_, &((*data)[num_read]), 1024);
|
|
if (nr > 0)
|
|
{
|
|
num_read += nr;
|
|
}
|
|
if (nr == 0)
|
|
{
|
|
if (is_file_)
|
|
{
|
|
debug("(serial) no more data on file fd=%d\n", fd_);
|
|
close_me = true;
|
|
}
|
|
if (is_stdin_)
|
|
{
|
|
if (getchar() == EOF)
|
|
{
|
|
debug("(serial) no more data on stdin fd=%d\n", fd_);
|
|
close_me = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (nr < 0)
|
|
{
|
|
if (errno == EINTR && fd_ != -1) continue; // Interrupted try again.
|
|
if (errno == EAGAIN) break; // No more data available since it would block.
|
|
if (errno == EBADF)
|
|
{
|
|
debug("(serial) got EBADF for fd=%d closing it.\n", fd_);
|
|
close_me = true;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
data->resize(num_read);
|
|
|
|
if (isDebugEnabled())
|
|
{
|
|
if (expecting_ascii_)
|
|
{
|
|
string msg = safeString(*data);
|
|
debug("(serial) received ascii \"%s\"\n", msg.c_str());
|
|
}
|
|
else
|
|
{
|
|
string msg = bin2hex(*data);
|
|
debug("(serial) received binary \"%s\"\n", msg.c_str());
|
|
}
|
|
}
|
|
|
|
if (close_me) close();
|
|
|
|
return num_read;
|
|
}
|
|
|
|
struct SerialDeviceTTY : public SerialDeviceImp
|
|
{
|
|
SerialDeviceTTY(string device, int baud_rate, PARITY parity, SerialCommunicationManagerImp * manager, string purpose);
|
|
~SerialDeviceTTY();
|
|
|
|
bool open(bool fail_if_not_ok);
|
|
void close();
|
|
bool send(vector<uchar> &data);
|
|
bool working();
|
|
string device() { return device_; }
|
|
|
|
private:
|
|
|
|
string device_;
|
|
int baud_rate_ {};
|
|
PARITY parity_ {};
|
|
};
|
|
|
|
SerialDeviceTTY::SerialDeviceTTY(string device, int baud_rate, PARITY parity,
|
|
SerialCommunicationManagerImp *manager,
|
|
string purpose)
|
|
: SerialDeviceImp(manager, purpose)
|
|
{
|
|
device_ = device;
|
|
baud_rate_ = baud_rate;
|
|
parity_ = parity;
|
|
}
|
|
|
|
SerialDeviceTTY::~SerialDeviceTTY()
|
|
{
|
|
close();
|
|
}
|
|
|
|
bool SerialDeviceTTY::open(bool fail_if_not_ok)
|
|
{
|
|
assert(device_ != "");
|
|
bool ok = checkCharacterDeviceExists(device_.c_str(), fail_if_not_ok);
|
|
if (!ok) return false;
|
|
fd_ = openSerialTTY(device_.c_str(), baud_rate_, parity_);
|
|
if (fd_ == -1)
|
|
{
|
|
if (fail_if_not_ok) error("Could not open %s with %d baud N81\n", device_.c_str(), baud_rate_);
|
|
verbose("(serialtty) could not open %s with %d baud N81\n", device_.c_str(), baud_rate_);
|
|
return false;
|
|
}
|
|
if (fd_ == -2)
|
|
{
|
|
if (fail_if_not_ok) error("Device %s is already in use and locked.\n", device_.c_str());
|
|
verbose("(serialtty) device %s is already in use and locked.\n", device_.c_str());
|
|
return false;
|
|
}
|
|
verbose("(serialtty) opened %s fd %d (%s)\n", device_.c_str(), fd_, purpose_.c_str());
|
|
return true;
|
|
}
|
|
|
|
void SerialDeviceTTY::close()
|
|
{
|
|
if (fd_ == -1) return;
|
|
::flock(fd_, LOCK_UN);
|
|
::close(fd_);
|
|
fd_ = -1;
|
|
if (on_disappear_ && !resetting_)
|
|
{
|
|
on_disappear_();
|
|
on_disappear_ = NULL;
|
|
}
|
|
manager_->tickleEventLoop();
|
|
|
|
verbose("(serialtty) closed %s (%s)\n", device_.c_str(), purpose_.c_str());
|
|
}
|
|
|
|
bool SerialDeviceTTY::send(vector<uchar> &data)
|
|
{
|
|
LOCK_WRITE_SERIAL(send);
|
|
|
|
assert(data.size() > 0);
|
|
|
|
bool rc = true;
|
|
int n = data.size();
|
|
int written = 0;
|
|
while (true)
|
|
{
|
|
int nw = write(fd_, &data[written], n-written);
|
|
if (nw > 0) written += nw;
|
|
if (nw < 0)
|
|
{
|
|
if (errno==EINTR) continue;
|
|
rc = false;
|
|
if (isDebugEnabled()) {
|
|
string msg = bin2hex(data);
|
|
debug("(serial %s) failed to send \"%s\"\n", device_.c_str(), msg.c_str());
|
|
}
|
|
goto end;
|
|
}
|
|
if (written == n) break;
|
|
}
|
|
|
|
if (isDebugEnabled()) {
|
|
string msg = bin2hex(data);
|
|
debug("(serial %s) sent \"%s\"\n", device_.c_str(), msg.c_str());
|
|
}
|
|
|
|
if (signalsInstalled())
|
|
{
|
|
if (getEventLoopThread()) pthread_kill(getEventLoopThread(), SIGUSR1);
|
|
}
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
bool SerialDeviceTTY::working()
|
|
{
|
|
if (resetting_) return true;
|
|
if (fd_ == -1) return false;
|
|
|
|
bool working = checkCharacterDeviceExists(device_.c_str(), false);
|
|
|
|
if (!working) {
|
|
debug("(serial) device %s is gone\n", device_.c_str());
|
|
}
|
|
|
|
return working;
|
|
}
|
|
|
|
|
|
struct SerialDeviceCommand : public SerialDeviceImp
|
|
{
|
|
SerialDeviceCommand(string identifier, string command, vector<string> args, vector<string> envs,
|
|
SerialCommunicationManagerImp *manager,
|
|
string purpose);
|
|
~SerialDeviceCommand();
|
|
|
|
bool open(bool fail_if_not_ok);
|
|
void close();
|
|
bool send(vector<uchar> &data);
|
|
int available();
|
|
bool working();
|
|
string device() { return identifier_; }
|
|
string command() { return command_; }
|
|
|
|
private:
|
|
|
|
string identifier_;
|
|
string command_;
|
|
int pid_ {};
|
|
vector<string> args_;
|
|
vector<string> envs_;
|
|
|
|
pthread_mutex_t write_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
|
pthread_mutex_t read_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
|
};
|
|
|
|
SerialDeviceCommand::SerialDeviceCommand(string identifier,
|
|
string command,
|
|
vector<string> args,
|
|
vector<string> envs,
|
|
SerialCommunicationManagerImp *manager,
|
|
string purpose)
|
|
: SerialDeviceImp(manager, purpose)
|
|
{
|
|
identifier_ = identifier;
|
|
command_ = command;
|
|
args_ = args;
|
|
envs_ = envs;
|
|
}
|
|
|
|
SerialDeviceCommand::~SerialDeviceCommand()
|
|
{
|
|
close();
|
|
}
|
|
|
|
bool SerialDeviceCommand::open(bool fail_if_not_ok)
|
|
{
|
|
expectAscii();
|
|
bool ok = invokeBackgroundShell("/bin/sh", args_, envs_, &fd_, &pid_);
|
|
assert(fd_ >= 0);
|
|
if (!ok) return false;
|
|
setIsStdin();
|
|
verbose("(serialcmd) opened %s pid %d fd %d (%s)\n", command_.c_str(), pid_, fd_, purpose_.c_str());
|
|
return true;
|
|
}
|
|
|
|
void SerialDeviceCommand::close()
|
|
{
|
|
int p = pid_, f = fd_;
|
|
if (pid_ == 0 && fd_ == -1) return;
|
|
if (pid_ && stillRunning(pid_))
|
|
{
|
|
stopBackgroundShell(pid_);
|
|
pid_ = 0;
|
|
}
|
|
if (on_disappear_ && !resetting_)
|
|
{
|
|
on_disappear_();
|
|
on_disappear_ = NULL;
|
|
}
|
|
::flock(fd_, LOCK_UN);
|
|
::close(fd_);
|
|
fd_ = -1;
|
|
|
|
manager_->tickleEventLoop();
|
|
|
|
verbose("(serialcmd) closed %s pid=%d fd=%d (%s)\n", command_.c_str(), p, f, purpose_.c_str());
|
|
}
|
|
|
|
bool SerialDeviceCommand::working()
|
|
{
|
|
if (resetting_) return true;
|
|
if (fd_ == -1) return false;
|
|
int n = -1;
|
|
int rc = ioctl(fd_, FIONREAD, &n);
|
|
if (rc != 0) return false;
|
|
// There is still data available for reading,
|
|
// even though the child-process might have ended.
|
|
if (n > 0) return true;
|
|
|
|
// No data and no pid. For sure its not working.
|
|
if (!pid_) return false;
|
|
// Ok check the pid, still running?
|
|
bool r = stillRunning(pid_);
|
|
if (r) return true;
|
|
return false;
|
|
}
|
|
|
|
bool SerialDeviceCommand::send(vector<uchar> &data)
|
|
{
|
|
LOCK_WRITE_SERIAL(sendcmd);
|
|
|
|
assert(data.size() > 0);
|
|
|
|
bool rc = true;
|
|
int n = data.size();
|
|
int written = 0;
|
|
while (true) {
|
|
int nw = write(fd_, &data[written], n-written);
|
|
if (nw > 0) written += nw;
|
|
if (nw < 0) {
|
|
if (errno==EINTR) continue;
|
|
rc = false;
|
|
goto end;
|
|
}
|
|
if (written == n) break;
|
|
}
|
|
|
|
if (isDebugEnabled()) {
|
|
string msg = bin2hex(data);
|
|
debug("(serial %s) sent \"%s\"\n", command_.c_str(), msg.c_str());
|
|
}
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
struct SerialDeviceFile : public SerialDeviceImp
|
|
{
|
|
SerialDeviceFile(string file, SerialCommunicationManagerImp *manager, string purpose);
|
|
~SerialDeviceFile();
|
|
|
|
bool open(bool fail_if_not_ok);
|
|
void close();
|
|
bool working();
|
|
bool send(vector<uchar> &data);
|
|
int available();
|
|
string device() { return file_; }
|
|
|
|
private:
|
|
|
|
string file_;
|
|
};
|
|
|
|
SerialDeviceFile::SerialDeviceFile(string file,
|
|
SerialCommunicationManagerImp *manager,
|
|
string purpose)
|
|
: SerialDeviceImp(manager, purpose)
|
|
{
|
|
file_ = file;
|
|
}
|
|
|
|
SerialDeviceFile::~SerialDeviceFile()
|
|
{
|
|
close();
|
|
}
|
|
|
|
bool SerialDeviceFile::open(bool fail_if_not_ok)
|
|
{
|
|
if (file_ == "stdin")
|
|
{
|
|
fd_ = 0;
|
|
int flags = fcntl(0, F_GETFL);
|
|
flags |= O_NONBLOCK;
|
|
fcntl(0, F_SETFL, flags);
|
|
setIsStdin();
|
|
verbose("(serialfile) reading from stdin (%s)\n", purpose_.c_str());
|
|
}
|
|
else
|
|
{
|
|
bool ok = checkFileExists(file_.c_str());
|
|
if (!ok) return false;
|
|
fd_ = ::open(file_.c_str(), O_RDONLY | O_NONBLOCK);
|
|
if (fd_ == -1)
|
|
{
|
|
if (fail_if_not_ok) error("Could not open file %s for reading.\n", file_.c_str());
|
|
verbose("(serialdevicefile) could not open file %s for reading.\n", file_.c_str());
|
|
return false;
|
|
}
|
|
setIsFile();
|
|
verbose("(serialfile) reading from file %s (%s)\n", file_.c_str(), purpose_.c_str());
|
|
}
|
|
manager_->tickleEventLoop();
|
|
|
|
return true;
|
|
}
|
|
|
|
void SerialDeviceFile::close()
|
|
{
|
|
if (fd_ == -1) return;
|
|
::flock(fd_, LOCK_UN);
|
|
::close(fd_);
|
|
fd_ = -1;
|
|
|
|
manager_->tickleEventLoop();
|
|
|
|
verbose("(serialfile) closed %s %d (%s)\n", file_.c_str(), fd_, purpose_.c_str());
|
|
}
|
|
|
|
bool SerialDeviceFile::working()
|
|
{
|
|
if (resetting_) return true;
|
|
if (fd_ == -1) return false;
|
|
int n = -1;
|
|
int rc = ioctl(fd_, FIONREAD, &n);
|
|
// The file descriptor was bad.
|
|
if (rc != 0) return false;
|
|
|
|
// There is still data available for reading.
|
|
if (n > 0) return true;
|
|
|
|
// Oh it is still open, lets continue use it.
|
|
// This could be stdin for example.
|
|
return true;
|
|
}
|
|
|
|
bool SerialDeviceFile::send(vector<uchar> &data)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
struct SerialDeviceSimulator : public SerialDeviceImp
|
|
{
|
|
SerialDeviceSimulator(SerialCommunicationManagerImp *m, string purpose) :
|
|
SerialDeviceImp(m, purpose)
|
|
{
|
|
verbose("(serialsimulator) opened (%s)\n", purpose_.c_str());
|
|
};
|
|
~SerialDeviceSimulator() { };
|
|
|
|
bool open(bool fail_if_not_ok) { return true; };
|
|
void close() { };
|
|
bool readonly() { return true; }
|
|
bool send(vector<uchar> &data) { return true; };
|
|
void fill(vector<uchar> &data) { data_ = data; on_data_(); }; // Fill buffer and trigger callback.
|
|
|
|
int receive(vector<uchar> *data)
|
|
{
|
|
*data = data_;
|
|
data_.clear();
|
|
return data->size();
|
|
}
|
|
int available() { return data_.size(); }
|
|
int fd() { return -1; }
|
|
bool working() { return false; } // Only one message that has already been handled! So return false here.
|
|
|
|
private:
|
|
|
|
vector<uchar> data_;
|
|
};
|
|
|
|
SerialCommunicationManagerImp::SerialCommunicationManagerImp(time_t exit_after_seconds,
|
|
bool start_event_loop)
|
|
{
|
|
running_ = true;
|
|
// Block the event loop until everything is configured.
|
|
if (start_event_loop)
|
|
{
|
|
event_loop_mutex_.lock();
|
|
startEventLoopThread(call(this, eventLoop));
|
|
startTimerLoopThread(call(this, timerLoop));
|
|
}
|
|
wakeMeUpOnSigChld(getEventLoopThread());
|
|
start_time_ = time(NULL);
|
|
exit_after_seconds_ = exit_after_seconds;
|
|
}
|
|
|
|
shared_ptr<SerialDevice> SerialCommunicationManagerImp::createSerialDeviceTTY(string device,
|
|
int baud_rate,
|
|
PARITY parity,
|
|
string purpose)
|
|
{
|
|
return addSerialDeviceForManagement(new SerialDeviceTTY(device, baud_rate, parity, this, purpose));
|
|
}
|
|
|
|
shared_ptr<SerialDevice> SerialCommunicationManagerImp::createSerialDeviceCommand(string identifier,
|
|
string command,
|
|
vector<string> args,
|
|
vector<string> envs,
|
|
string purpose)
|
|
{
|
|
return addSerialDeviceForManagement(new SerialDeviceCommand(identifier, command, args, envs, this, purpose));
|
|
}
|
|
|
|
shared_ptr<SerialDevice> SerialCommunicationManagerImp::createSerialDeviceFile(string file, string purpose)
|
|
{
|
|
return addSerialDeviceForManagement(new SerialDeviceFile(file, this, purpose));
|
|
}
|
|
|
|
shared_ptr<SerialDevice> SerialCommunicationManagerImp::createSerialDeviceSimulator()
|
|
{
|
|
return addSerialDeviceForManagement(new SerialDeviceSimulator(this, ""));
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::listenTo(SerialDevice *sd, function<void()> cb)
|
|
{
|
|
if (sd == NULL) return;
|
|
SerialDeviceImp *si = dynamic_cast<SerialDeviceImp*>(sd);
|
|
if (!si)
|
|
{
|
|
error("Internal error: Invalid serial device passed to listenTo.\n");
|
|
}
|
|
si->on_data_ = cb;
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::onDisappear(SerialDevice *sd, function<void()> cb)
|
|
{
|
|
if (sd == NULL) return;
|
|
SerialDeviceImp *si = dynamic_cast<SerialDeviceImp*>(sd);
|
|
if (!si)
|
|
{
|
|
error("Internal error: Invalid serial device passed to onDisappear.\n");
|
|
}
|
|
si->on_disappear_ = cb;
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::expectDevicesToWork()
|
|
{
|
|
debug("(serial) expecting devices to work\n");
|
|
expect_devices_to_work_ = true;
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::stop()
|
|
{
|
|
// Notify the main waitForStop thread that we are stopped!
|
|
if (running_ == true)
|
|
{
|
|
debug("(serial) stopping manager\n");
|
|
running_ = false;
|
|
if (getMainThread() != 0)
|
|
{
|
|
if (signalsInstalled())
|
|
{
|
|
if (getMainThread()) pthread_kill(getMainThread(), SIGUSR2);
|
|
if (getEventLoopThread()) pthread_kill(getEventLoopThread(), SIGUSR1);
|
|
if (getTimerLoopThread()) pthread_kill(getTimerLoopThread(), SIGUSR1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::startEventLoop()
|
|
{
|
|
// Release the event loop!
|
|
event_loop_mutex_.unlock();
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::waitForStop()
|
|
{
|
|
debug("(serial) waiting for stop\n");
|
|
|
|
recordMyselfAsMainThread();
|
|
while (running_)
|
|
{
|
|
{
|
|
LOCK_SERIAL_DEVICES(wait_for_stop);
|
|
if (serial_devices_.size() == 0) break;
|
|
}
|
|
int rc = usleep(1000*1000);
|
|
if (rc == -1 && errno == EINTR)
|
|
{
|
|
debug("(serial) MAIN thread interrupted\n");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
closeAllDoNotRemove();
|
|
|
|
if (signalsInstalled())
|
|
{
|
|
if (getEventLoopThread()) pthread_kill(getEventLoopThread(), SIGUSR1);
|
|
if (getTimerLoopThread()) pthread_kill(getTimerLoopThread(), SIGUSR1);
|
|
}
|
|
|
|
pthread_join(getEventLoopThread(), NULL);
|
|
pthread_join(getTimerLoopThread(), NULL);
|
|
}
|
|
|
|
bool SerialCommunicationManagerImp::isRunning()
|
|
{
|
|
return running_;
|
|
}
|
|
|
|
shared_ptr<SerialDevice> SerialCommunicationManagerImp::addSerialDeviceForManagement(SerialDevice *sd)
|
|
{
|
|
LOCK_SERIAL_DEVICES(opened);
|
|
|
|
shared_ptr<SerialDevice> ptr = shared_ptr<SerialDevice>(sd);
|
|
serial_devices_.push_back(ptr);
|
|
|
|
tickleEventLoop();
|
|
return ptr;
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::tickleEventLoop()
|
|
{
|
|
LOCK_SERIAL_DEVICES(tickle);
|
|
|
|
if (signalsInstalled())
|
|
{
|
|
// Tickle the event loop to use the new file descriptor in the select.
|
|
if (getEventLoopThread()) pthread_kill(getEventLoopThread(), SIGUSR1);
|
|
}
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::removeNonWorkingSerialDevices()
|
|
{
|
|
LOCK_SERIAL_DEVICES(remove_non_working_serial_devices);
|
|
|
|
for (auto i = serial_devices_.begin(); i != serial_devices_.end(); )
|
|
{
|
|
if ((*i)->opened() && !(*i)->working())
|
|
{
|
|
i = serial_devices_.erase(i);
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (serial_devices_.size() == 0 && expect_devices_to_work_)
|
|
{
|
|
debug("(serial) no devices working emergency exit!\n");
|
|
stop();
|
|
}
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::closeAllDoNotRemove()
|
|
{
|
|
LOCK_SERIAL_DEVICES(close_all_do_not_remove);
|
|
|
|
if (serial_devices_.size() == 0) return;
|
|
|
|
debug("(serial) closing %d devices\n", serial_devices_.size());
|
|
|
|
for (auto i = serial_devices_.begin(); i != serial_devices_.end(); i++)
|
|
{
|
|
(*i)->close();
|
|
}
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::executeTimerCallbacks()
|
|
{
|
|
time_t curr = time(NULL);
|
|
vector<Timer> to_be_called;
|
|
|
|
{
|
|
LOCK_TIMERS(execute_timer_callbacks);
|
|
|
|
for (Timer &t : timers_)
|
|
{
|
|
if (t.isTime(curr))
|
|
{
|
|
trace("[SERIAL] timer isTime! %d %s\n", t.id, t.name.c_str());
|
|
t.last_call = curr;
|
|
to_be_called.push_back(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Timer &t : to_be_called)
|
|
{
|
|
trace("[SERIAL] invoking callback %s(%d)\n", t.name.c_str(), t.id);
|
|
t.callback();
|
|
}
|
|
}
|
|
|
|
time_t SerialCommunicationManagerImp::calculateTimeToNearestTimerCallback(time_t now)
|
|
{
|
|
time_t r = 1024*1024*1024;
|
|
for (Timer &t : timers_)
|
|
{
|
|
// Expected time when to trigger in the future.
|
|
// Well, could be in the past as well, if we are unlucky.
|
|
time_t done = t.last_call+t.seconds;
|
|
// Now how long time is it left....could be negative
|
|
// if we are late.
|
|
time_t remaining = done-now;
|
|
if (remaining < r) r = remaining;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void *SerialCommunicationManagerImp::timerLoop()
|
|
{
|
|
while (running_)
|
|
{
|
|
int rc = usleep(1000*1000);
|
|
if (rc == -1 && errno == EINTR)
|
|
{
|
|
debug("(serial) TIMER thread interrupted\n");
|
|
continue;
|
|
}
|
|
|
|
time_t curr = time(NULL);
|
|
|
|
if (exit_after_seconds_ > 0)
|
|
{
|
|
time_t diff = curr-start_time_;
|
|
if (diff > exit_after_seconds_)
|
|
{
|
|
// Running time limit hit, now stop.
|
|
verbose("(serial) exit after %ld seconds\n", diff);
|
|
stop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
executeTimerCallbacks();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void *SerialCommunicationManagerImp::eventLoop()
|
|
{
|
|
LOCK_EVENT_LOOP(eventLoop);
|
|
|
|
fd_set readfds;
|
|
|
|
while (running_)
|
|
{
|
|
FD_ZERO(&readfds);
|
|
|
|
bool all_working = true;
|
|
|
|
{
|
|
LOCK_SERIAL_DEVICES(list_file_descriptiors_to_listen_to);
|
|
|
|
for (shared_ptr<SerialDevice> &sd : serial_devices_)
|
|
{
|
|
if (sd->opened() && sd->working() && !sd->skippingCallbacks())
|
|
{
|
|
if (!sd->resetting() && sd->fd() >= 0)
|
|
{
|
|
trace("[SERIAL] select read on fd %d\n", sd->fd());
|
|
FD_SET(sd->fd(), &readfds);
|
|
}
|
|
}
|
|
if (sd->opened() && !sd->working()) all_working = false;
|
|
}
|
|
}
|
|
|
|
if (!all_working && expect_devices_to_work_)
|
|
{
|
|
debug("(serial) not all devices working, emergency exit!\n");
|
|
stop();
|
|
break;
|
|
}
|
|
|
|
// Perform a select call every second.
|
|
struct timeval timeout { 1, 0 };
|
|
|
|
trace("[SERIAL] select timeout %d s\n", timeout.tv_sec);
|
|
|
|
int max_fd = 0;
|
|
for (shared_ptr<SerialDevice> &sp : serial_devices_)
|
|
{
|
|
if (sp->fd() > max_fd)
|
|
{
|
|
max_fd = sp->fd();
|
|
}
|
|
}
|
|
|
|
int activity = select(max_fd+1 , &readfds, NULL , NULL, &timeout);
|
|
|
|
if (activity == -1 && errno == EINTR)
|
|
{
|
|
debug("(serial) EVENT thread interrupted\n");
|
|
}
|
|
if (!running_) break;
|
|
if (activity < 0 && errno!=EINTR)
|
|
{
|
|
warning("(serial) internal error after select! errno=%s\n", strerror(errno));
|
|
}
|
|
|
|
if (activity > 0)
|
|
{
|
|
// Something has happened that caused the sleeping select to wake up.
|
|
vector<shared_ptr<SerialDevice>> to_be_notified;
|
|
{
|
|
LOCK_SERIAL_DEVICES(find_triggering_file_descriptions);
|
|
|
|
for (shared_ptr<SerialDevice> &sd : serial_devices_)
|
|
{
|
|
if (sd->opened() && sd->working() && !sd->resetting() && sd->fd() >= 0)
|
|
{
|
|
if (FD_ISSET(sd->fd(), &readfds))
|
|
{
|
|
trace("[SERIAL] select detected data available for reading on fd %d\n", sd->fd());
|
|
to_be_notified.push_back(sd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (shared_ptr<SerialDevice> &sd : to_be_notified)
|
|
{
|
|
SerialDeviceImp *si = dynamic_cast<SerialDeviceImp*>(sd.get());
|
|
if (si->on_data_)
|
|
{
|
|
si->on_data_();
|
|
}
|
|
}
|
|
}
|
|
|
|
vector<shared_ptr<SerialDevice>> non_working;
|
|
{
|
|
LOCK_SERIAL_DEVICES(find_non_working_serial_devices);
|
|
|
|
for (shared_ptr<SerialDevice> &sd : serial_devices_)
|
|
{
|
|
if (sd->opened() && !sd->working() && !sd->isClosed()) non_working.push_back(sd);
|
|
}
|
|
}
|
|
|
|
for (shared_ptr<SerialDevice> &sd : non_working)
|
|
{
|
|
debug("(serial) closing non working fd=%d \"%s\"\n", sd->fd(), sd->device().c_str());
|
|
sd->close();
|
|
}
|
|
|
|
removeNonWorkingSerialDevices();
|
|
|
|
if (non_working.size() > 0 && expect_devices_to_work_)
|
|
{
|
|
debug("(serial) non working devices found, exiting.\n");
|
|
stop();
|
|
break;
|
|
}
|
|
}
|
|
verbose("(serial) event loop stopped!\n");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
shared_ptr<SerialCommunicationManager> createSerialCommunicationManager(time_t exit_after_seconds,
|
|
bool start_event_loop)
|
|
{
|
|
return shared_ptr<SerialCommunicationManager>(new SerialCommunicationManagerImp(exit_after_seconds,
|
|
start_event_loop));
|
|
}
|
|
|
|
static int openSerialTTY(const char *tty, int baud_rate, PARITY parity)
|
|
{
|
|
int rc = 0;
|
|
speed_t speed = 0;
|
|
struct termios tios;
|
|
string tty_info;
|
|
//int DTR_flag = TIOCM_DTR;
|
|
|
|
int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
|
if (fd == -1) {
|
|
usleep(1000*1000);
|
|
fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
|
if (fd == -1) goto err;
|
|
}
|
|
rc = flock(fd, LOCK_EX | LOCK_NB);
|
|
if (rc == -1)
|
|
{
|
|
// It is already locked by another wmbusmeter process.
|
|
fd = -2;
|
|
goto err;
|
|
}
|
|
|
|
tty_info = showTTYSettings(fd);
|
|
debug("(serial) before config: %s %s\n", tty, tty_info.c_str());
|
|
|
|
switch (baud_rate)
|
|
{
|
|
case 300: speed = B300; break;
|
|
case 600: speed = B600; break;
|
|
case 1200: speed = B1200; break;
|
|
case 2400: speed = B2400; break;
|
|
case 4800: speed = B4800; break;
|
|
case 9600: speed = B9600; break;
|
|
case 19200: speed = B19200; break;
|
|
case 38400: speed = B38400; break;
|
|
case 57600: speed = B57600; break;
|
|
case 115200: speed = B115200;break;
|
|
default:
|
|
goto err;
|
|
}
|
|
|
|
memset(&tios, 0, sizeof(tios));
|
|
|
|
rc = cfsetispeed(&tios, speed);
|
|
if (rc < 0) goto err;
|
|
rc = cfsetospeed(&tios, speed);
|
|
if (rc < 0) goto err;
|
|
|
|
|
|
// CREAD=Enable receive CLOCAL=Ignore any Carrier Detect signal.
|
|
tios.c_cflag |= (CREAD | CLOCAL);
|
|
tios.c_cflag &= ~CSIZE;
|
|
tios.c_cflag |= CS8;
|
|
tios.c_cflag &=~ CSTOPB;
|
|
if (parity == PARITY::NONE)
|
|
{
|
|
// Disable parity bit.
|
|
tios.c_cflag &=~ PARENB;
|
|
}
|
|
else if (parity == PARITY::EVEN)
|
|
{
|
|
// Enable parity even, ie not odd.
|
|
tios.c_cflag |= PARENB;
|
|
tios.c_cflag &=~ PARODD;
|
|
}
|
|
else if (parity == PARITY::ODD)
|
|
{
|
|
// Enable parity odd.
|
|
tios.c_cflag |= PARENB;
|
|
tios.c_cflag |= PARODD;
|
|
}
|
|
else
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
|
|
tios.c_iflag &= ~INPCK;
|
|
|
|
tios.c_iflag &= ~(IXON | IXOFF | IXANY);
|
|
|
|
tios.c_oflag &=~ OPOST;
|
|
tios.c_cc[VMIN] = 0;
|
|
tios.c_cc[VTIME] = 0;
|
|
|
|
rc = tcsetattr(fd, TCSANOW, &tios);
|
|
if (rc < 0) goto err;
|
|
|
|
|
|
// This code can toggle DTR... maybe necessary
|
|
// for the pl2303 usb2serial driver/device.
|
|
//rc = ioctl(fd, TIOCMBIC, &DTR_flag);
|
|
//if (rc != 0) goto err;
|
|
|
|
tty_info = showTTYSettings(fd);
|
|
debug("(serial) after config: %s %s\n", tty, tty_info.c_str());
|
|
|
|
return fd;
|
|
|
|
err:
|
|
if (fd > 0)
|
|
{
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
SerialCommunicationManager::~SerialCommunicationManager()
|
|
{
|
|
}
|
|
|
|
int SerialCommunicationManagerImp::startRegularCallback(string name, int seconds, function<void()> callback)
|
|
{
|
|
LOCK_TIMERS(start_regular_callback);
|
|
|
|
Timer t = { (int)timers_.size(), seconds, time(NULL), callback, name };
|
|
timers_.push_back(t);
|
|
debug("(serial) registered regular callback %s(%d) every %d seconds\n", name.c_str(), t.id, seconds);
|
|
|
|
return t.id;
|
|
}
|
|
|
|
void SerialCommunicationManagerImp::stopRegularCallback(int id)
|
|
{
|
|
LOCK_TIMERS(stop_regular_callback);
|
|
|
|
debug("(serial) stopping regular callback %d\n", id);
|
|
for (auto i = timers_.begin(); i != timers_.end(); ++i)
|
|
{
|
|
if ((*i).id == id)
|
|
{
|
|
timers_.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
shared_ptr<SerialDevice> SerialCommunicationManagerImp::lookup(string device)
|
|
{
|
|
LOCK_SERIAL_DEVICES(lookup);
|
|
|
|
for (auto sd : serial_devices_)
|
|
{
|
|
if (sd->device() == device) return shared_ptr<SerialDevice>(sd);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool SerialCommunicationManagerImp::removeNonWorking(string device)
|
|
{
|
|
LOCK_SERIAL_DEVICES(remove_non_working);
|
|
|
|
bool found_and_removed = false;
|
|
for (auto i = serial_devices_.begin(); i != serial_devices_.end(); )
|
|
{
|
|
if ((*i)->opened() && !(*i)->working() && (*i)->device() == device)
|
|
{
|
|
i = serial_devices_.erase(i);
|
|
found_and_removed = true;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return found_and_removed;
|
|
}
|
|
|
|
|
|
#if defined(__APPLE__) || defined(__FreeBSD__)
|
|
vector<string> SerialCommunicationManagerImp::listSerialTTYs()
|
|
{
|
|
vector<string> list;
|
|
list.push_back("Please add code here!");
|
|
return list;
|
|
}
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
|
|
static string lookup_device_driver(string tty)
|
|
{
|
|
struct stat st;
|
|
tty += "/device";
|
|
int rc = lstat(tty.c_str(), &st);
|
|
if (rc==0 && S_ISLNK(st.st_mode))
|
|
{
|
|
tty += "/driver";
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
int rc = readlink(tty.c_str(), buffer, sizeof(buffer));
|
|
if (rc > 0)
|
|
{
|
|
return basename(buffer);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static void check_if_serial(string tty, vector<string> *found_serials, vector<string> *found_8250s)
|
|
{
|
|
string driver = lookup_device_driver(tty);
|
|
|
|
if (driver.size() > 0)
|
|
{
|
|
char buffer[1024];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
strncpy(buffer, tty.c_str(), sizeof(buffer)-1);
|
|
|
|
string dev = buffer;
|
|
|
|
if (driver == "serial8250")
|
|
{
|
|
found_8250s->push_back(dev);
|
|
}
|
|
else
|
|
{
|
|
// The dev is now something like: /sys/class/tty/ttyUSB0
|
|
// Drop the /sys/class/tty/ prefix and replace with /dev/
|
|
if (dev.rfind("/sys/class/tty/", 0) == 0) {
|
|
dev = string("/dev/")+dev.substr(15);
|
|
}
|
|
found_serials->push_back(dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void check_serial8250s(vector<string> *found_serials, vector<string> &found_8250s)
|
|
{
|
|
struct serial_struct serinfo;
|
|
|
|
for (string dev : found_8250s)
|
|
{
|
|
int fd = open(dev.c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY);
|
|
|
|
if (fd >= 0)
|
|
{
|
|
int rc = ioctl(fd, TIOCGSERIAL, &serinfo);
|
|
if (rc == 0)
|
|
{
|
|
if (serinfo.type != PORT_UNKNOWN)
|
|
{
|
|
found_serials->push_back(dev);
|
|
}
|
|
close(fd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int sorty(const struct dirent **a, const struct dirent **b)
|
|
{
|
|
return strcmp((*a)->d_name, (*b)->d_name);
|
|
}
|
|
|
|
vector<string> SerialCommunicationManagerImp::listSerialTTYs()
|
|
{
|
|
struct dirent **entries;
|
|
vector<string> found_serials;
|
|
vector<string> found_8250s;
|
|
string sysdir = "/sys/class/tty/";
|
|
|
|
int n = scandir(sysdir.c_str(), &entries, NULL, sorty);
|
|
if (n < 0)
|
|
{
|
|
perror("scandir");
|
|
return found_serials;
|
|
}
|
|
|
|
for (int i=0; i<n; ++i)
|
|
{
|
|
string name = entries[i]->d_name;
|
|
|
|
if (name == ".." || name == ".")
|
|
{
|
|
free(entries[i]);
|
|
continue;
|
|
}
|
|
|
|
string tty = sysdir+name;
|
|
check_if_serial(tty, &found_serials, &found_8250s);
|
|
free(entries[i]);
|
|
}
|
|
free(entries);
|
|
|
|
check_serial8250s(&found_serials, found_8250s);
|
|
|
|
return found_serials;
|
|
}
|
|
|
|
#endif
|
|
|
|
#define CHECK_SPEED(x) { if (speed == x) return #x; }
|
|
|
|
string translateSpeed(speed_t speed)
|
|
{
|
|
string flags;
|
|
|
|
CHECK_SPEED(B50)
|
|
CHECK_SPEED(B75)
|
|
CHECK_SPEED(B110)
|
|
CHECK_SPEED(B134)
|
|
CHECK_SPEED(B150)
|
|
CHECK_SPEED(B200)
|
|
CHECK_SPEED(B300)
|
|
CHECK_SPEED(B600)
|
|
CHECK_SPEED(B1200)
|
|
CHECK_SPEED(B1800)
|
|
CHECK_SPEED(B2400)
|
|
CHECK_SPEED(B4800)
|
|
CHECK_SPEED(B9600)
|
|
#ifdef B57600
|
|
CHECK_SPEED(B57600)
|
|
#endif
|
|
#ifdef B115200
|
|
CHECK_SPEED(B115200)
|
|
#endif
|
|
CHECK_SPEED(B19200)
|
|
#ifdef B230400
|
|
CHECK_SPEED(B230400)
|
|
#endif
|
|
CHECK_SPEED(B38400)
|
|
#ifdef B460800
|
|
CHECK_SPEED(B460800)
|
|
#endif
|
|
#ifdef B500000
|
|
CHECK_SPEED(B500000)
|
|
#endif
|
|
#ifdef B57600
|
|
CHECK_SPEED(B57600)
|
|
#endif
|
|
#ifdef B921600
|
|
CHECK_SPEED(B921600)
|
|
#endif
|
|
#ifdef B1000000
|
|
CHECK_SPEED(B1000000)
|
|
#endif
|
|
#ifdef B1152000
|
|
CHECK_SPEED(B1152000)
|
|
#endif
|
|
#ifdef B1500000
|
|
CHECK_SPEED(B1500000)
|
|
#endif
|
|
#ifdef B2000000
|
|
CHECK_SPEED(B2000000)
|
|
#endif
|
|
#ifdef B2500000
|
|
CHECK_SPEED(B2500000)
|
|
#endif
|
|
#ifdef B3000000
|
|
CHECK_SPEED(B3000000)
|
|
#endif
|
|
#ifdef B3500000
|
|
CHECK_SPEED(B3500000)
|
|
#endif
|
|
#ifdef B4000000
|
|
CHECK_SPEED(B4000000)
|
|
#endif
|
|
|
|
return "UnknownSpeed";
|
|
};
|
|
|
|
#undef CHECK_SPEED
|
|
|
|
string lookupSpeed(struct termios *tios)
|
|
{
|
|
speed_t in = cfgetispeed(tios);
|
|
speed_t out = cfgetispeed(tios);
|
|
|
|
if (in == out)
|
|
{
|
|
return translateSpeed(in);
|
|
}
|
|
|
|
return translateSpeed(in)+","+translateSpeed(out);
|
|
}
|
|
|
|
#define CHECK_FLAG(x) { if (bits & x) flags += #x "|"; }
|
|
|
|
string iflags(tcflag_t bits)
|
|
{
|
|
string flags;
|
|
|
|
CHECK_FLAG(BRKINT)
|
|
CHECK_FLAG(ICRNL)
|
|
CHECK_FLAG(IGNBRK)
|
|
CHECK_FLAG(IGNCR)
|
|
CHECK_FLAG(IGNPAR)
|
|
#ifdef IMAXBEL
|
|
CHECK_FLAG(IMAXBEL)
|
|
#endif
|
|
CHECK_FLAG(INLCR)
|
|
CHECK_FLAG(ISTRIP)
|
|
#ifdef IUTF8
|
|
CHECK_FLAG(IUTF8)
|
|
#endif
|
|
CHECK_FLAG(IXANY)
|
|
CHECK_FLAG(IXOFF)
|
|
CHECK_FLAG(IXON)
|
|
CHECK_FLAG(PARMRK)
|
|
|
|
if (flags.length() > 0) flags.pop_back();
|
|
return flags;
|
|
};
|
|
|
|
string oflags(tcflag_t bits)
|
|
{
|
|
string flags;
|
|
|
|
#ifdef BS1
|
|
CHECK_FLAG(BS1)
|
|
#endif
|
|
#ifdef NL1
|
|
CHECK_FLAG(NL1)
|
|
#endif
|
|
CHECK_FLAG(ONLCR)
|
|
#ifdef ONOEOT
|
|
CHECK_FLAG(ONOEOT)
|
|
#endif
|
|
CHECK_FLAG(OPOST)
|
|
#ifdef OXTABS
|
|
CHECK_FLAG(OXTABS)
|
|
#endif
|
|
|
|
if (flags.length() > 0) flags.pop_back();
|
|
return flags;
|
|
};
|
|
|
|
string cflags(tcflag_t bits)
|
|
{
|
|
string flags;
|
|
|
|
CHECK_FLAG(CLOCAL)
|
|
CHECK_FLAG(CREAD)
|
|
CHECK_FLAG(CSIZE)
|
|
CHECK_FLAG(CSTOPB)
|
|
CHECK_FLAG(HUPCL)
|
|
CHECK_FLAG(PARENB)
|
|
CHECK_FLAG(PARODD)
|
|
|
|
if (flags.length() > 0) flags.pop_back();
|
|
return flags;
|
|
};
|
|
|
|
string lflags(tcflag_t bits)
|
|
{
|
|
string flags;
|
|
|
|
CHECK_FLAG(ECHO)
|
|
CHECK_FLAG(ECHOCTL)
|
|
CHECK_FLAG(ECHOE)
|
|
CHECK_FLAG(ECHOK)
|
|
CHECK_FLAG(ECHOKE)
|
|
CHECK_FLAG(ECHONL)
|
|
CHECK_FLAG(ECHOPRT)
|
|
CHECK_FLAG(FLUSHO)
|
|
CHECK_FLAG(ICANON)
|
|
CHECK_FLAG(IEXTEN)
|
|
CHECK_FLAG(ISIG)
|
|
CHECK_FLAG(NOFLSH)
|
|
CHECK_FLAG(PENDIN)
|
|
CHECK_FLAG(TOSTOP)
|
|
#ifdef XCASE
|
|
CHECK_FLAG(XCASE)
|
|
#endif
|
|
|
|
if (flags.length() > 0) flags.pop_back();
|
|
return flags;
|
|
};
|
|
|
|
#undef CHECK_FLAG
|
|
|
|
string showSpecialChars(struct termios *tios)
|
|
{
|
|
string s;
|
|
|
|
int n = sizeof(tios->c_cc)/sizeof(cc_t);
|
|
for (int i=0; i<n; ++i)
|
|
{
|
|
cc_t c = tios->c_cc[i];
|
|
if (c != 0)
|
|
{
|
|
string cc;
|
|
strprintf(cc, "%u", c);
|
|
s += cc+",";
|
|
}
|
|
}
|
|
if (s.length() > 0) s.pop_back();
|
|
return s;
|
|
}
|
|
|
|
static string showTTYSettings(int fd)
|
|
{
|
|
string info;
|
|
string bits;
|
|
|
|
struct termios tios;
|
|
int modem_bits;
|
|
|
|
int rc = tcgetattr(fd, &tios);
|
|
if (rc != 0) goto err;
|
|
|
|
info += "speed("+lookupSpeed(&tios)+") ";
|
|
info += "input("+iflags(tios.c_iflag) + ") ";
|
|
info += "output("+oflags(tios.c_oflag) + ") ";
|
|
info += "control("+cflags(tios.c_cflag) + ") ";
|
|
info += "local("+lflags(tios.c_lflag) + ") ";
|
|
info += "special_chars("+showSpecialChars(&tios)+") ";
|
|
|
|
rc = ioctl(fd, TIOCMGET, &modem_bits);
|
|
if (rc != 0) goto err;
|
|
|
|
if (modem_bits & TIOCM_LE) bits += "LE|"; // Line Enabled (same as DSR below?)
|
|
if (modem_bits & TIOCM_DTR) bits += "DTR|"; // Data Terminal Ready (Computer is ready.)
|
|
if (modem_bits & TIOCM_RTS) bits += "RTS|"; // Request to send (hardware flow control)
|
|
if (modem_bits & TIOCM_ST) bits += "ST|";
|
|
if (modem_bits & TIOCM_SR) bits += "SR|";
|
|
if (modem_bits & TIOCM_CTS) bits += "CTS|"; // Clear to Send (hardware flow control)
|
|
if (modem_bits & TIOCM_CD) bits += "CD|"; // Data Carrier Detected (Modem is connected to another modem)
|
|
if (modem_bits & TIOCM_RI) bits += "RING|"; // Ring Indicator
|
|
if (modem_bits & TIOCM_DSR) bits += "DSR|"; // Data Set Ready (Modem is ready.)
|
|
|
|
if (bits.length() > 0) bits.pop_back();
|
|
info += "modem("+bits+")";
|
|
return info;
|
|
|
|
err:
|
|
|
|
return "error";
|
|
}
|
|
|
|
AccessCheck SerialCommunicationManagerImp::checkAccess(string device,
|
|
shared_ptr<SerialCommunicationManager> manager,
|
|
string extra_info,
|
|
function<AccessCheck(string,shared_ptr<SerialCommunicationManager>)> extra_probe)
|
|
{
|
|
assert(device != "");
|
|
|
|
if (extra_info == "")
|
|
{
|
|
extra_info = "serial";
|
|
}
|
|
|
|
debug("(%s) check if %s can be accessed\n", extra_info.c_str(), device.c_str());
|
|
|
|
AccessCheck ac = checkIfExistsAndHasAccess(device);
|
|
|
|
if (ac == AccessCheck::AccessOK)
|
|
{
|
|
if (!extra_probe)
|
|
{
|
|
verbose("(%s) tty %s can be accessed\n", extra_info.c_str(), device.c_str());
|
|
return AccessCheck::AccessOK;
|
|
}
|
|
verbose("(%s) tty %s can be accessed now probing...\n", extra_info.c_str(), device.c_str());
|
|
ac = extra_probe(device, manager);
|
|
verbose("(%s) probe returns %s\n", extra_info.c_str(), toString(ac));
|
|
return ac;
|
|
}
|
|
|
|
if (ac == AccessCheck::NoPermission)
|
|
{
|
|
verbose("(serial) you do not have the correct permissions to open the tty %s, but at least you share the same access group.\n",
|
|
device.c_str());
|
|
return AccessCheck::NoPermission;
|
|
}
|
|
|
|
if (ac == AccessCheck::NotSameGroup)
|
|
{
|
|
verbose("(serial) you do not have the correct permissions to open the tty %s and you do not share the same access group.\n",
|
|
device.c_str());
|
|
return AccessCheck::NotSameGroup;
|
|
}
|
|
|
|
verbose("(serial) cannot open/find tty %s.\n",
|
|
device.c_str());
|
|
|
|
return AccessCheck::NoSuchDevice;
|
|
}
|