Updated German translation
[dasher.git] / Src / DasherCore / SocketInputBase.cpp
blob5338f3cb3bf2028d164d0f0f22aed2c1eb5a7b95
1 // SocketInputBase.cpp
2 //
3 // (C) Copyright Seb Wills 2005
5 #include "../Common/Common.h"
7 #include "SocketInputBase.h"
9 #include "DasherInterfaceBase.h"
11 #include <string.h>
12 #include <errno.h>
13 #include <stdarg.h>
14 #ifdef _WIN32
15 #include <winsock2.h>
16 #define DASHER_SOCKET_CLOSE_FUNCTION closesocket
17 #else
18 #include <sys/socket.h>
19 #include <netinet/in.h>
20 #include <unistd.h>
21 #define DASHER_SOCKET_CLOSE_FUNCTION close
22 #endif
24 using namespace Dasher;
26 static SModuleSettings sSettings[] = {
27 {LP_SOCKET_PORT, T_LONGSPIN, 0, 65535, 1, 10, _("Port:")},
28 {SP_SOCKET_INPUT_X_LABEL, T_STRING, -1, -1, -1, -1, _("X label:")},
29 {LP_SOCKET_INPUT_X_MIN, T_LONGSPIN, -2147480000, 2147480000, 1000, 10000, _("X minimum:")},
30 {LP_SOCKET_INPUT_X_MAX, T_LONGSPIN, -2147480000, 2147480000, 1000, 10000, _("X maximum:")},
31 {SP_SOCKET_INPUT_Y_LABEL, T_STRING, -1, -1, -1, -1, _("Y label:")},
32 {LP_SOCKET_INPUT_Y_MIN, T_LONGSPIN, -2147480000, 2147480000, 1000, 10000, _("Y minimum:")},
33 {LP_SOCKET_INPUT_Y_MAX, T_LONGSPIN, -2147480000, 2147480000, 1000, 10000, _("Y maximum:")},
34 {BP_SOCKET_DEBUG, T_BOOL, -1, -1, -1, -1, _("Print socket-related debugging information to console:")}
37 Dasher::CSocketInputBase::CSocketInputBase(CSettingsUser *pCreator, CMessageDisplay *pMsgs)
38 : CScreenCoordInput(1, _("Socket Input")), CSettingsUserObserver(pCreator), m_pMsgs(pMsgs) {
39 port = -1;
40 debug_socket_input = false;
41 readerRunning = false;
42 sock = -1;
43 SetCoordinateCount(2);
44 for(int i = 0; i < DASHER_SOCKET_INPUT_MAX_COORDINATE_COUNT; i++) {
45 dasherMaxCoordinateValues[i] = 4096; // the real value will come later when SetMaxCoordinates is invoked
46 rawMinValues[i] = 0.0; // suitable defaults for BCI2000
47 rawMaxValues[i] = 512.0;
48 memset(coordinateNames[i], '\0', DASHER_SOCKET_INPUT_MAX_COORDINATE_LABEL_LENGTH + 1);
49 dasherCoordinates[i] = 2048; // initialise to mid-range value
52 // initialise using parameter settings:
53 SetDebug(GetBoolParameter(BP_SOCKET_DEBUG));
54 SetReaderPort(GetLongParameter(LP_SOCKET_PORT));
55 SetRawRange(0, ((double)GetLongParameter(LP_SOCKET_INPUT_X_MIN)) / 1000.0, ((double)GetLongParameter(LP_SOCKET_INPUT_X_MAX)) / 1000.0);
56 SetRawRange(1, ((double)GetLongParameter(LP_SOCKET_INPUT_Y_MIN)) / 1000.0, ((double)GetLongParameter(LP_SOCKET_INPUT_Y_MAX)) / 1000.0);
57 SetCoordinateLabel(0, GetStringParameter(SP_SOCKET_INPUT_X_LABEL).c_str());
58 SetCoordinateLabel(1, GetStringParameter(SP_SOCKET_INPUT_Y_LABEL).c_str());
59 SocketDebugMsg("Socket input is initialised but not yet enabled");
62 Dasher::CSocketInputBase::~CSocketInputBase() {
63 // Would like to call StopListening(); Can't do this here because by the time this (base class) destructor is called,
64 // the derived class instance has been deleted, so we can no longer call it's StopListening.
65 // Instead, you should call StopListening in the derived class's destructor.
68 void Dasher::CSocketInputBase::HandleEvent(int iParameter) {
69 switch (iParameter) {
70 case LP_SOCKET_PORT:
71 SetReaderPort(GetLongParameter(LP_SOCKET_PORT));
72 break;
73 case SP_SOCKET_INPUT_X_LABEL:
74 SetCoordinateLabel(0, GetStringParameter(SP_SOCKET_INPUT_X_LABEL).c_str());
75 break;
76 case SP_SOCKET_INPUT_Y_LABEL:
77 SetCoordinateLabel(1, GetStringParameter(SP_SOCKET_INPUT_Y_LABEL).c_str());
78 break;
79 case LP_SOCKET_INPUT_X_MIN:
80 case LP_SOCKET_INPUT_X_MAX:
81 SetRawRange(0, ((double)GetLongParameter(LP_SOCKET_INPUT_X_MIN)) / 1000.0, ((double)GetLongParameter(LP_SOCKET_INPUT_X_MAX)) / 1000.0);
82 break;
83 case LP_SOCKET_INPUT_Y_MIN:
84 case LP_SOCKET_INPUT_Y_MAX:
85 SetRawRange(1, ((double)GetLongParameter(LP_SOCKET_INPUT_Y_MIN)) / 1000.0, ((double)GetLongParameter(LP_SOCKET_INPUT_Y_MAX)) / 1000.0);
86 break;
87 case BP_SOCKET_DEBUG:
88 SetDebug(GetBoolParameter(BP_SOCKET_DEBUG));
89 break;
90 default:
91 break;
95 bool Dasher::CSocketInputBase::StartListening() {
96 struct sockaddr_in name;
98 // this shouldn't be called if we are already listening, but if it is, let's failsafe
99 // rather than attempt to bind to the same port twice.
100 if(readerRunning) {
101 StopListening();
104 SocketDebugMsg("Socket input: binding to socket and starting to listen.");
106 if((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
107 //TODO This is not a very good error message even in English...???
108 m_pMsgs->Message(_("Error creating socket"),true);
109 return false;
112 name.sin_family = AF_INET;
113 name.sin_port = htons(port);
114 name.sin_addr.s_addr = htonl(INADDR_ANY);
115 if(bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
116 ReportErrnoError(_("Error binding to socket - already in use?"));
117 DASHER_SOCKET_CLOSE_FUNCTION(sock);
118 sock = -1;
119 return false;
122 if(!LaunchReaderThread()) {
123 // LaunchReaderThread will already have displayed an error message
124 DASHER_SOCKET_CLOSE_FUNCTION(sock);
125 sock = -1;
126 return false;
129 readerRunning = true;
130 return true;
134 void Dasher::CSocketInputBase::StopListening() {
136 if(!readerRunning) {
137 return;
140 CancelReaderThread();
142 if(sock >= 0) {
143 DASHER_SOCKET_CLOSE_FUNCTION(sock);
145 readerRunning = false;
146 SocketDebugMsg("Socket input: stopped listening to socket.");
149 void CSocketInputBase::SetReaderPort(int _port) {
150 if(_port == port) {
151 SocketDebugMsg("SetReaderPort called with same value (%d), so ignoring.",port);
152 return;
155 SocketDebugMsg("Setting socket input port to %d.", _port);
156 if(readerRunning) {
157 StopListening();
158 port = _port;
159 StartListening();
161 else {
162 port = _port;
166 void CSocketInputBase::SetCoordinateLabel( int iWhichCoordinate, const char *Label) {
167 DASHER_ASSERT(iWhichCoordinate < DASHER_SOCKET_INPUT_MAX_COORDINATE_COUNT);
168 if(strlen(Label) > DASHER_SOCKET_INPUT_MAX_COORDINATE_LABEL_LENGTH) {
169 const char *msg=_("Warning truncating socket input label '%s' to %i characters.");
170 char *buf(new char[strlen(msg)+strlen(Label)+DASHER_SOCKET_INPUT_MAX_COORDINATE_LABEL_LENGTH]);
171 sprintf(buf,msg,Label,DASHER_SOCKET_INPUT_MAX_COORDINATE_LABEL_LENGTH);
172 m_pMsgs->Message(buf, true);
173 delete[] buf;
175 strncpy(coordinateNames[iWhichCoordinate], Label, DASHER_SOCKET_INPUT_MAX_COORDINATE_LABEL_LENGTH);
176 SocketDebugMsg("Socket input: set coordinate %d label to '%s'.", iWhichCoordinate, coordinateNames[iWhichCoordinate]);
179 void CSocketInputBase::SetRawRange(int iWhich, double dMin, double dMax) {
180 rawMinValues[iWhich] = dMin;
181 rawMaxValues[iWhich] = dMax;
182 SocketDebugMsg("Socket input: set coordinate %d input range to: min: %lf, max: %lf.", iWhich, dMin, dMax);
186 // private methods:
188 void CSocketInputBase::ReadForever() {
189 // this gets called in its own thread. It reads datagrams and updates the coordinate variables
191 int numbytes;
192 while(sock >= 0) {
193 SocketDebugMsg("Reading from socket...");
194 if((numbytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) == -1) {
195 m_pMsgs->Message(_("Socket input: Error reading from socket"),false);
196 continue;
198 buffer[numbytes] = '\0';
200 SocketDebugMsg(" received string: '%s'.", buffer);
202 ParseMessage(buffer);
207 // Parse and act on a message received from the socket
208 // Allowed to modify contents of memory pointed to by message, up to its final '\0'.
209 void CSocketInputBase::ParseMessage(char *message) {
211 char *p;
212 double rawdouble;
213 // myint dasherCoordinateTemp;
214 // parse line by line
215 while((p = strchr(message, '\n')) != NULL) {
216 *p = '\0';
217 // Each line is expected to be of the form "Label <value>"
218 // We run through each coordinate label, checking if this line matches it
219 for(int i = 0; i < coordinateCount; i++) {
220 int len = strlen(coordinateNames[i]);
221 if(strncmp(coordinateNames[i], message, len) == 0) {
222 SocketDebugMsg("Matched label '%s'...", coordinateNames[i]);
223 // First len chars match the label of this coordinate. Value should be at the next non-space char.
224 if(sscanf(message + len, "%lf", &rawdouble) == 1) {
225 SocketDebugMsg("...parsed value as %lf.", rawdouble);
227 #ifdef DASHER_SOCKET_INPUT_BCI2000_OVERFLOW_WORKAROUND
228 // a temporary workaround to undo an integer overflow that occurs in messages sent from BCI2000
229 if(rawdouble > 32000) {
230 rawdouble = 0;
232 if(rawdouble > 768 && rawdouble < 32000) {
233 rawdouble = 768;
235 #endif
237 // Clipping:
238 // for clipping purposes, we want to ignore whether Max < Min (which indicates that
239 // we need to flip the sense of the input)
240 double actualMax = (rawMaxValues[i] > rawMinValues[i]) ? rawMaxValues[i] : rawMinValues[i];
241 double actualMin = (rawMaxValues[i] > rawMinValues[i]) ? rawMinValues[i] : rawMaxValues[i];
242 if(rawdouble < actualMin) {
243 //TODO: Should these be converted to calls to Message() ? On first occurrence only???
244 cerr << "Socket input: clipped " << coordinateNames[i] << " value of " << rawdouble << "to configured minimum of " << actualMin << endl;
245 rawdouble = actualMin;
247 if(rawdouble > actualMax) {
248 //TODO: Should these be converted to calls to Message() ? On first occurrence only???
249 cerr << "Socket input: clipped " << message << " value of " << rawdouble << "to configured maximum of " << actualMax << endl;
250 rawdouble = actualMax;
253 // convert to dasher coordinates:
255 const bool do_lowpass = false;
256 if(do_lowpass) {
257 // initial attempt at putting a low-pass filter in. Not well tested; disabled for now.
258 double timeconst = 100.0; // no of updates
259 double newcoord = ((rawdouble - rawMinValues[i]) / (rawMaxValues[i] - rawMinValues[i]) * dasherMaxCoordinateValues[i]);
260 dasherCoordinates[i] = (myint) ((1 - 1 / timeconst) * (double)dasherCoordinates[i] + (1 / timeconst) * newcoord);
262 else {
263 // straightforward linear mapping to dasher coordinates:
264 // Treat X coordinate specially: reverse sense so it has the more intuitive left-to-right direction
265 double min = (i==0) ? rawMaxValues[i] : rawMinValues[i];
266 double max = (i==0) ? rawMinValues[i] : rawMaxValues[i];
267 if(max != min) { // prevent nasty explosion
268 dasherCoordinates[i] = (myint) ((rawdouble - min) / (max - min) * (double)dasherMaxCoordinateValues[i]);
272 SocketDebugMsg("Socket input: new value for coordinate %d rescales to %u in Dasher's internal coordinates (range 0-%d).", i, (unsigned int) dasherCoordinates[i], (int) dasherMaxCoordinateValues[i]);
274 // don't break out of the for loop in case we get asked to drive two coordinates from same label
275 } else {
276 SocketDebugMsg("... but couldn't parse the text following that label as a number.");
281 message = p + 1; // move on to next line (if there isn't one, we'll point at the terminating '\0')
285 void CSocketInputBase::SetDebug(bool _debug) {
286 if(!_debug) {
287 SocketDebugMsg("Disabling socket debug messages.");
289 debug_socket_input = _debug;
290 if(_debug) {
291 SocketDebugMsg("Enabled socket debug messages.");
295 void CSocketInputBase::ReportErrnoError(const std::string &prefix) {
296 int err = errno; errno=0;
297 const char *msg = _("Dasher Socket Input error: %s: %s");
298 char *e = strerror(err);
299 char *buf(new char[strlen(msg) + prefix.length() + strlen(e)]);
300 sprintf(buf,msg,prefix.c_str(),e);
301 m_pMsgs->Message(buf,true);
302 delete[] buf;
305 void CSocketInputBase::SocketDebugMsg(const char *pszFormat, ...) {
306 if(debug_socket_input) {
307 va_list v;
308 va_start(v, pszFormat);
309 vfprintf(stderr, pszFormat, v);
310 fprintf(stderr, "\n");
311 va_end(v);
315 bool CSocketInputBase::GetSettings(SModuleSettings **pSettings, int *iCount) {
316 *pSettings = sSettings;
317 *iCount = sizeof(sSettings) / sizeof(SModuleSettings);
319 return true;