libcreate/src/serial.cpp

262 lines
7.4 KiB
C++

#include <iostream>
#include "create/serial.h"
#include "create/types.h"
namespace create {
Serial::Serial(boost::shared_ptr<Data> d, const uint8_t& header) :
data(d),
headerByte(header),
port(io),
readState(READ_HEADER),
isReading(false),
dataReady(false),
corruptPackets(0),
totalPackets(0) {
//std::cout << "# Serial Created" << std::endl;
}
Serial::~Serial() {
disconnect();
//std::cout << "# Serial Destroyed" << std::endl;
}
bool Serial::connect(const std::string& portName, const int& baud, boost::function<void()> cb) {
//std::cout << "## Serial connect start" << std::endl;
using namespace boost::asio;
port.open(portName);
port.set_option(serial_port::baud_rate(baud));
port.set_option(serial_port::flow_control(serial_port::flow_control::none));
if (port.is_open()) {
callback = cb;
bool startReadSuccess = startReading();
if (!startReadSuccess)
port.close();
//std::cout << "## Serial connect done" << std::endl;
return startReadSuccess;
}
//std::cout << "## Serial connect failed" << std::endl;
return false;
}
void Serial::disconnect() {
if (isReading) {
stopReading();
}
if (connected()) {
//std::cout << "## Serial disconnect start" << std::endl;
// Ensure not in Safe/Full modes
sendOpcode(OC_START);
// Stop OI
sendOpcode(OC_STOP);
port.close();
//std::cout << "## Serial disconnect done" << std::endl;
}
}
bool Serial::startReading() {
if (!connected()) return false;
if (!data) {
CERR("[create::Serial] ", "data pointer not initialized.");
return false;
}
// Only allow once
if (isReading) return true;
//std::cout << "### Serial start reading" << std::endl;
// Request from Create that we want a stream containing all packets
uint8_t numPackets = data->getNumPackets();
std::vector<uint8_t> packetIDs = data->getPacketIDs();
uint8_t streamReq[2 + numPackets];
streamReq[0] = OC_STREAM;
streamReq[1] = numPackets;
int i = 2;
for (std::vector<uint8_t>::iterator it = packetIDs.begin(); it != packetIDs.end(); ++it) {
streamReq[i] = *it;
i++;
}
// Start OI
sendOpcode(OC_START);
// Start streaming data
send(streamReq, 2 + numPackets);
expectedNumBytes = data->getTotalDataBytes() + numPackets;
//TODO: handle boost exceptions
io.reset();
// Start continuously reading one byte at a time
boost::asio::async_read(port,
boost::asio::buffer(&byteRead, 1),
boost::bind(&Serial::onData, this, _1, _2));
ioThread = boost::thread(boost::bind(&boost::asio::io_service::run, &io));
// Wait for first complete read to finish
boost::unique_lock<boost::mutex> lock(dataReadyMut);
//std::cout << "#### Waiting for dataReady" << std::endl;
int attempts = 1;
int maxAttempts = 10;
while (!dataReady) {
if (!dataReadyCond.timed_wait(lock, boost::get_system_time() + boost::posix_time::milliseconds(500))) {
if (attempts >= maxAttempts) {
CERR("[create::Serial] ", "failed to receive data from Create. Check if robot is powered!");
io.stop();
ioThread.join();
return false;
}
attempts++;
//std::cout << "Requesting data from Create. Attempt " << attempts << std::endl;
// Request data again
sendOpcode(OC_START);
send(streamReq, 2 + numPackets);
}
}
//std::cout << "#### Data is ready." << std::endl;
isReading = true;
//std::cout << "### Serial start reading DONE" << std::endl;
return true;
}
void Serial::stopReading() {
if (isReading) {
//std::cout << "### Start stopReading" << std::endl;
io.stop();
ioThread.join();
isReading = false;
{
boost::lock_guard<boost::mutex> lock(dataReadyMut);
dataReady = false;
}
//std::cout << "### End stopReading" << std::endl;
}
}
void Serial::onData(const boost::system::error_code& e, const std::size_t& size) {
//std::cout << "#### onData" << std::endl;
if (e) {
CERR("[create::Serial] ", "serial error - " << e.message());
return;
}
// Should have read exactly one byte
if (size == 1) {
numBytesRead++;
byteSum += byteRead;
switch (readState) {
case READ_HEADER:
if (byteRead == headerByte) {
readState = READ_NBYTES;
byteSum = byteRead;
}
break;
case READ_NBYTES:
if (byteRead == expectedNumBytes) {
readState = READ_PACKET_ID;
numBytesRead = 0;
}
else
readState = READ_HEADER;
break;
case READ_PACKET_ID:
packetID = byteRead;
if (data->isValidPacketID(packetID)) {
expectedNumDataBytes = data->getPacket(packetID)->nbytes;
assert(expectedNumDataBytes == 1 || expectedNumDataBytes == 2);
numDataBytesRead = 0;
packetBytes = 0;
readState = READ_PACKET_BYTES;
}
else {
readState = READ_HEADER;
}
break;
case READ_PACKET_BYTES:
numDataBytesRead++;
if (expectedNumDataBytes == 2 && numDataBytesRead == 1) {
// high byte first
packetBytes = (byteRead << 8) & 0xff00;
}
else {
// low byte
packetBytes += byteRead;
}
if (numDataBytesRead >= expectedNumDataBytes) {
data->getPacket(packetID)->setTempData(packetBytes);
if (numBytesRead >= expectedNumBytes)
readState = READ_CHECKSUM;
else
readState = READ_PACKET_ID;
}
break;
case READ_CHECKSUM:
if ((byteSum & 0xFF) == 0) {
// Validate all packets
data->validateAll();
// Notify first data packets ready
{
boost::lock_guard<boost::mutex> lock(dataReadyMut);
// std::cout << "locking." << std::endl;
if (!dataReady) {
dataReady = true;
dataReadyCond.notify_one();
//std::cout << "##### Notified." << std::endl;
}
}
// Callback to notify data is ready
if (callback)
callback();
}
else {
// Corrupt data
corruptPackets++;
}
totalPackets++;
// Start again
readState = READ_HEADER;
break;
} // end switch (readState)
} // end if (size == 1)
// Read the next byte
boost::asio::async_read(port,
boost::asio::buffer(&byteRead, 1),
boost::bind(&Serial::onData, this, _1, _2));
}
bool Serial::send(const uint8_t* bytes, unsigned int numBytes) {
if (!connected()) {
CERR("[create::Serial] ", "send failed, not connected.");
return false;
}
// TODO: catch boost exceptions
boost::asio::write(port, boost::asio::buffer(bytes, numBytes));
return true;
}
bool Serial::sendOpcode(const Opcode& code) {
uint8_t oc = (uint8_t) code;
return send(&oc, 1);
}
uint64_t Serial::getNumCorruptPackets() {
return corruptPackets;
}
uint64_t Serial::getNumTotalPackets() {
return totalPackets;
}
} // namespace create