/*----------------------------------------------------------------------------*/ | |
/* Copyright (c) FIRST 2008. All Rights Reserved. */ | |
/* Open Source Software - may be modified and shared by FRC teams. The code */ | |
/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */ | |
/*----------------------------------------------------------------------------*/ | |
#include <vxWorks.h> | |
#include "PCVideoServer.h" | |
#include <errnoLib.h> | |
#include <hostLib.h> | |
#include <inetLib.h> | |
#include <sockLib.h> | |
#include "NetworkCommunication/UsageReporting.h" | |
#include "Task.h" | |
#include "Timer.h" | |
#include "Vision/AxisCamera.h" | |
#include "WPIErrors.h" | |
/** | |
* @brief Implements an object that automatically does a close on a | |
* camera socket on destruction. | |
*/ | |
class ScopedSocket { | |
public: | |
ScopedSocket(int camSock) | |
: m_camSock(camSock) | |
{ | |
} | |
~ScopedSocket() { | |
if (m_camSock != ERROR) { | |
close(m_camSock); | |
} | |
} | |
// Cast to int allows you to pass this to any function that | |
// takes the socket as an int. | |
operator int() const { | |
return m_camSock; | |
} | |
private: | |
int m_camSock; | |
}; | |
//============================================================================ | |
// PCVideoServer | |
//============================================================================ | |
/** | |
* @brief Constructor. | |
*/ | |
PCVideoServer::PCVideoServer() | |
: m_serverTask("PCVideoServer", (FUNCPTR)s_ServerTask) | |
, m_newImageSem (NULL) | |
, m_stopServer (false) | |
{ | |
AxisCamera &cam = AxisCamera::GetInstance(); | |
m_newImageSem = cam.GetNewImageSem(); | |
if (!cam.StatusIsFatal()) | |
{ | |
StartServerTask(); | |
} | |
else | |
{ | |
CloneError(&cam); | |
} | |
} | |
/** | |
* @brief Destructor. | |
* Stop serving images and destroy this class. | |
*/ | |
PCVideoServer::~PCVideoServer() | |
{ | |
// Stop the images to PC server. | |
Stop(); | |
// Clear the error so that you can use this object to make a connection to | |
// the VIDEO_TO_PC_PORT to stop the ImageToPCServer if it is waiting to | |
// accept connections from a PC. | |
ClearError(); | |
// Open a socket. | |
int camSock = socket(AF_INET, SOCK_STREAM, 0); | |
if (camSock == ERROR) | |
{ | |
wpi_setErrnoError(); | |
return; | |
} | |
ScopedSocket scopedCamSock(camSock); | |
// If successful | |
if (!StatusIsFatal()) | |
{ | |
// Create a connection to the localhost. | |
struct sockaddr_in selfAddr; | |
int sockAddrSize = sizeof(selfAddr); | |
bzero ((char *) &selfAddr, sockAddrSize); | |
selfAddr.sin_family = AF_INET; | |
selfAddr.sin_len = (u_char) sockAddrSize; | |
selfAddr.sin_port = htons (VIDEO_TO_PC_PORT); | |
if (( (int)(selfAddr.sin_addr.s_addr = inet_addr (const_cast<char*>("localhost")) ) != ERROR) || | |
( (int)(selfAddr.sin_addr.s_addr = hostGetByName (const_cast<char*>("localhost")) ) != ERROR)) | |
{ | |
struct timeval connectTimeout; | |
connectTimeout.tv_sec = 1; | |
connectTimeout.tv_usec = 0; | |
connectWithTimeout(camSock, (struct sockaddr *) &selfAddr, sockAddrSize, &connectTimeout); | |
} | |
} | |
} | |
/** | |
* Start the task that is responsible for sending images to the PC. | |
*/ | |
int PCVideoServer::StartServerTask() | |
{ | |
if (StatusIsFatal()) | |
{ | |
return -1; | |
} | |
int id = 0; | |
m_stopServer = false; | |
// Check for prior copy of running task | |
int oldId = taskNameToId((char*)m_serverTask.GetName()); | |
if(oldId != ERROR) | |
{ | |
// TODO: Report error. You are in a bad state. | |
taskDelete(oldId); | |
} | |
// spawn video server task | |
// this is done to ensure that the task is spawned with the | |
// floating point context save parameter. | |
bool started = m_serverTask.Start((int)this); | |
id = m_serverTask.GetID(); | |
if (!started) | |
{ | |
wpi_setWPIError(TaskError); | |
return id; | |
} | |
taskDelay(1); | |
return id; | |
} | |
/** | |
* @brief Start sending images to the PC. | |
*/ | |
void PCVideoServer::Start() | |
{ | |
StartServerTask(); | |
} | |
/** | |
* @brief Stop sending images to the PC. | |
*/ | |
void PCVideoServer::Stop() | |
{ | |
m_stopServer = true; | |
} | |
/** | |
* Static stub for kicking off the server task | |
*/ | |
int PCVideoServer::s_ServerTask(PCVideoServer *thisPtr) | |
{ | |
return thisPtr->ServerTask(); | |
} | |
/** | |
* @brief Initialize the socket and serve images to the PC. | |
* This is the task that serves images to the PC in a loop. This runs | |
* as a separate task. | |
*/ | |
int PCVideoServer::ServerTask() | |
{ | |
/* Setup to PC sockets */ | |
struct sockaddr_in serverAddr; | |
int sockAddrSize = sizeof(serverAddr); | |
int pcSock = ERROR; | |
bzero ((char *) &serverAddr, sockAddrSize); | |
serverAddr.sin_len = (u_char) sockAddrSize; | |
serverAddr.sin_family = AF_INET; | |
serverAddr.sin_port = htons (VIDEO_TO_PC_PORT); | |
serverAddr.sin_addr.s_addr = htonl (INADDR_ANY); | |
int success; | |
while (true) | |
{ | |
taskSafe(); | |
// Create the socket. | |
if ((pcSock = socket (AF_INET, SOCK_STREAM, 0)) == ERROR) | |
{ | |
wpi_setErrnoErrorWithContext("Failed to create the PCVideoServer socket"); | |
continue; | |
} | |
// Set the TCP socket so that it can be reused if it is in the wait state. | |
int reuseAddr = 1; | |
setsockopt(pcSock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&reuseAddr), sizeof(reuseAddr)); | |
// Bind socket to local address. | |
if (bind (pcSock, (struct sockaddr *) &serverAddr, sockAddrSize) == ERROR) | |
{ | |
wpi_setErrnoErrorWithContext("Failed to bind the PCVideoServer port"); | |
close (pcSock); | |
continue; | |
} | |
// Create queue for client connection requests. | |
if (listen (pcSock, 1) == ERROR) | |
{ | |
wpi_setErrnoErrorWithContext("Failed to listen on the PCVideoServer port"); | |
close (pcSock); | |
continue; | |
} | |
struct sockaddr_in clientAddr; | |
int clientAddrSize; | |
int newPCSock = accept (pcSock, reinterpret_cast<sockaddr*>(&clientAddr), &clientAddrSize); | |
if (newPCSock == ERROR) | |
{ | |
close(pcSock); | |
continue; | |
} | |
//TODO: check camera error | |
// Report usage when there is actually a connection. | |
nUsageReporting::report(nUsageReporting::kResourceType_PCVideoServer, 0); | |
int numBytes = 0; | |
int imageDataSize = 0; | |
char* imageData = NULL; | |
while(!m_stopServer) | |
{ | |
success = semTake(m_newImageSem, 1000); | |
if (success == ERROR) | |
{ | |
// If the semTake timed out, there are no new images from the camera. | |
continue; | |
} | |
success = AxisCamera::GetInstance().CopyJPEG(&imageData, numBytes, imageDataSize); | |
if (!success) | |
{ | |
// No point in running too fast - | |
Wait(1.0); | |
// If camera is not initialzed you will get failure and | |
// the timestamp is invalid. Reset this value and try again. | |
continue; | |
} | |
// Write header to PC | |
static const char header[4]={1,0,0,0}; | |
int headerSend = write(newPCSock, const_cast<char*>(header), 4); | |
// Write image length to PC | |
int lengthSend = write(newPCSock, reinterpret_cast<char*>(&numBytes), 4); | |
// Write image to PC | |
int sent = write (newPCSock, imageData, numBytes); | |
// The PC probably closed connection. Get out of here | |
// and try listening again. | |
if (headerSend == ERROR || lengthSend == ERROR || sent == ERROR) | |
{ | |
break; | |
} | |
} | |
// Clean up | |
delete [] imageData; | |
close (newPCSock); | |
newPCSock = ERROR; | |
close (pcSock); | |
pcSock = ERROR; | |
taskUnsafe(); | |
Wait(0.1); | |
} | |
return (OK); | |
} | |