Created
August 9, 2016 06:07
-
-
Save elliotwoods/d550d6a8e0e9da7f20d74b091b40959d to your computer and use it in GitHub Desktop.
Etherdream cross-platform (C++11)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Ether Dream interface library | |
* | |
* Copyright 2011-2012 Jacob Potter | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of either the GNU General Public License version 2 | |
* or 3, or the GNU Lesser General Public License version 3, as published | |
* by the Free Software Foundation, at your option. | |
* | |
* 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/>. | |
*/ | |
#define _POSIX_C_SOURCE 199309L | |
#define _DARWIN_C_SOURCE 1 | |
#ifdef _MSC_VER | |
#define _CRT_SECURE_NO_WARNINGS | |
#define _WINSOCK_DEPRECATED_NO_WARNINGS | |
#include <winsock2.h> | |
#else | |
#include <arpa/inet.h> | |
#include <netinet/in.h> | |
#include <netinet/tcp.h> | |
#include <sys/ioctl.h> | |
#include <sys/socket.h> | |
#endif | |
#include <thread> | |
#include <mutex> | |
#include <stdarg.h> | |
#include <sys/types.h> | |
#include <string.h> | |
#include "../common/protocol.h" | |
#include "etherdream.h" | |
#define BUFFER_POINTS_PER_FRAME 16000 | |
#define BUFFER_NFRAMES 2 | |
#define MAX_LATE_ACKS 64 | |
#define MIN_SEND_POINTS 40 | |
#define DEFAULT_TIMEOUT 2000000 | |
#define DEBUG_THRESHOLD_POINTS 800 | |
struct etherdream_conn { | |
int dc_sock; | |
char dc_read_buf[1024]; | |
int dc_read_buf_size; | |
dac_response resp; | |
long long dc_last_ack_time; | |
struct { | |
struct queue_command queue; | |
struct data_command_header header; | |
struct dac_point data[1000]; | |
} __attribute__((packed)) dc_local_buffer; | |
int dc_begin_sent; | |
int ackbuf[MAX_LATE_ACKS]; | |
int ackbuf_prod; | |
int ackbuf_cons; | |
int unacked_points; | |
int pending_meta_acks; | |
}; | |
struct buffer_item { | |
struct dac_point data[BUFFER_POINTS_PER_FRAME]; | |
int points; | |
int pps; | |
int repeatcount; | |
int idx; | |
}; | |
enum dac_state { | |
ST_DISCONNECTED, | |
ST_READY, | |
ST_RUNNING, | |
ST_BROKEN, | |
ST_SHUTDOWN | |
}; | |
struct etherdream { | |
std::mutex mutex; | |
std::condition_variable loop_cond; | |
struct buffer_item buffer[BUFFER_NFRAMES]; | |
int frame_buffer_read; | |
int frame_buffer_fullness; | |
int bounce_count; | |
std::thread workerthread; | |
struct in_addr addr; | |
struct etherdream_conn conn; | |
unsigned long dac_id; | |
int sw_revision; | |
char mac_address[6]; | |
char version[32]; | |
enum dac_state state; | |
etherdream * next; | |
}; | |
static FILE *trace_fp = NULL; | |
static std::mutex dac_list_lock; | |
static struct etherdream *dac_list = NULL; | |
static std::chrono::high_resolution_clock::time_point startTime = std::chrono::high_resolution_clock::now(); | |
static std::thread watcherThread; | |
/* microseconds() | |
* | |
* Return the number of microseconds since library initialization. | |
*/ | |
static long long microseconds(void) { | |
const auto upTime = std::chrono::high_resolution_clock::now() - startTime; | |
return std::chrono::duration_cast<std::chrono::microseconds>(upTime).count(); | |
} | |
/* microsleep(us) | |
* | |
* Like usleep(). | |
*/ | |
static void microsleep(long long us) { | |
std::this_thread::sleep_for(std::chrono::microseconds(us)); | |
} | |
/* trace(d, fmt, ...) | |
* | |
* Utility function for logging. | |
*/ | |
static void trace(struct etherdream *d, const char *fmt, ...) { | |
if (!trace_fp) | |
return; | |
char buf[120]; | |
long long v = microseconds(); | |
int len; | |
if (d) | |
len = snprintf(buf, sizeof buf, "[%d.%06d] %06lx ", | |
(int)(v / 1000000), (int)(v % 1000000), d->dac_id); | |
else | |
len = snprintf(buf, sizeof buf, "[%d.%06d] ", | |
(int)(v / 1000000), (int)(v % 1000000)); | |
va_list args; | |
va_start(args, fmt); | |
vsnprintf(buf + len, sizeof buf - len, fmt, args); | |
va_end(args); | |
fputs(buf, trace_fp); | |
} | |
/* log_socket_error(d, call) | |
* | |
* Log an error in a socket call. | |
*/ | |
static void log_socket_error(struct etherdream *d, const char *call) { | |
trace(d, "!! socket error in %s: %d: %s\n", | |
call, errno, strerror(errno)); | |
} | |
/* wait_for_fd_activity(d, usec, writable) | |
* | |
* Wait for activity (if writable is 0, then readable or error; if writable | |
* is 1, then writable or error) on d's socket. Time out after usec. Returns | |
* 1 if activity happened, 0 on timeout, -1 on error (will also log error). | |
*/ | |
static int wait_for_fd_activity(struct etherdream *d, int usec, int writable) { | |
fd_set set; | |
FD_ZERO(&set); | |
FD_SET(d->conn.dc_sock, &set); | |
struct timeval t; | |
t.tv_sec = usec / 1000000; | |
t.tv_usec = usec % 1000000; | |
int res = select(d->conn.dc_sock + 1, (writable ? NULL : &set), | |
(writable ? &set : NULL), &set, &t); | |
if (res < 0) | |
log_socket_error(d, "select"); | |
return res; | |
} | |
/* read_bytes(d, buf, len) | |
* | |
* Read exactly len bytes from d's connection socket into buf. Returns 0 on | |
* success, -1 on error (will also log error). | |
*/ | |
static int read_bytes(struct etherdream *d, char *buf, int len) { | |
while (d->conn.dc_read_buf_size < len) { | |
int res = wait_for_fd_activity(d, DEFAULT_TIMEOUT, 0); | |
if (res < 0) | |
return res; | |
if (res == 0) { | |
trace(d, "!! Read from DAC timed out.\n"); | |
return -1; | |
} | |
res = recv(d->conn.dc_sock, | |
d->conn.dc_read_buf + d->conn.dc_read_buf_size, | |
len - d->conn.dc_read_buf_size, 0); | |
if (res <= 0) { | |
log_socket_error(d, "recv"); | |
return -1; | |
} | |
d->conn.dc_read_buf_size += res; | |
} | |
memcpy(buf, d->conn.dc_read_buf, len); | |
if (d->conn.dc_read_buf_size > len) { | |
printf("moving %d up by %d\n", d->conn.dc_read_buf_size, len); | |
memmove(d->conn.dc_read_buf, d->conn.dc_read_buf + len, | |
d->conn.dc_read_buf_size - len); | |
} | |
d->conn.dc_read_buf_size -= len; | |
return 0; | |
} | |
/* send_all(d, data, len) | |
* | |
* Send all of data to d's socket. Returns 0 on success, -1 on error or if the | |
* send times out (will also log error). | |
*/ | |
static int send_all(struct etherdream *d, const char *data, int len) { | |
do { | |
int res = wait_for_fd_activity(d, 100000, 1); | |
if (res < 0) | |
return -1; | |
if (res == 0) { | |
trace(d, "write timed out\n"); | |
} | |
res = send(d->conn.dc_sock, data, len, 0); | |
if (res < 0) { | |
log_socket_error(d, "send"); | |
return -1; | |
} | |
len -= res; | |
data += res; | |
} while (len); | |
return 0; | |
} | |
/* read_resp(d) | |
* | |
* Read a response from the DAC into d's conn.resp buffer. Returns 0 on | |
* success, -1 on error (in which case the error will have been logged). | |
*/ | |
static int read_resp(struct etherdream *d) { | |
int res = read_bytes(d, (char *)&d->conn.resp, sizeof(d->conn.resp)); | |
if (res < 0) | |
return res; | |
d->conn.dc_last_ack_time = microseconds(); | |
return 0; | |
} | |
/* dump_resp(d) | |
* | |
* Dump the last response received from d. | |
*/ | |
static void dump_resp(struct etherdream *d) { | |
struct etherdream_conn *conn = &d->conn; | |
struct dac_status *st = &conn->resp.dac_status; | |
trace(d, "-- Protocol %d / LE %d / playback %d / source %d\n", | |
0 /* st->protocol */, st->light_engine_state, | |
st->playback_state, st->source); | |
trace(d, "-- Flags: LE %x, playback %x, source %x\n", | |
st->light_engine_flags, st->playback_flags, | |
st->source_flags); | |
trace(d, "-- Buffer: %d points, %d pps, %d total played\n", | |
st->buffer_fullness, st->point_rate, st->point_count); | |
} | |
/* dac_connect(d, host, port) | |
* | |
* Initialize a dac's connection struct and open up a socket. On success, | |
* return 0; otherwise, return -1. | |
*/ | |
static int dac_connect(struct etherdream *d) { | |
struct etherdream_conn *conn = &d->conn; | |
memset(conn, 0, sizeof *conn); | |
// Open socket | |
conn->dc_sock = socket(AF_INET, SOCK_STREAM, 0); | |
if (conn->dc_sock < 0) { | |
log_socket_error(d, "socket"); | |
return -1; | |
} | |
u_long iMode = 1; | |
#ifdef _MSC_VER | |
ioctlsocket(conn->dc_sock, FIONBIO, &iMode); | |
#else | |
ioctl(conn->dc_sock, FIONBIO, &iMode); | |
#endif | |
sockaddr_in addr; | |
addr.sin_family = AF_INET; | |
addr.sin_addr.s_addr = d->addr.s_addr; | |
addr.sin_port = htons(7765); | |
connect(conn->dc_sock, (struct sockaddr *)&addr, (int)sizeof addr); | |
#ifdef _MSC_VER | |
// Because the socket is nonblocking, this will always error... | |
if (WSAGetLastError() != WSAEWOULDBLOCK) { | |
#else | |
if (errno != EINPROGRESS) { | |
#endif | |
log_socket_error(d, "connect"); | |
goto bail; | |
} | |
// Wait for connection to go through | |
{ | |
int res = wait_for_fd_activity(d, DEFAULT_TIMEOUT, 1); | |
if (res < 0) | |
goto bail; | |
if (res == 0) { | |
trace(d, "Connection to %s timed out.\n", inet_ntoa(d->addr)); | |
goto bail; | |
} | |
} | |
// See if we have *actually* connected | |
{ | |
int error; | |
#ifdef _MSC_VER | |
int len = sizeof error; | |
#else | |
unsigned int len = sizeof error; | |
#endif | |
if (getsockopt(conn->dc_sock, SOL_SOCKET, SO_ERROR, (char *)&error, | |
&len) < 0) { | |
log_socket_error(d, "getsockopt"); | |
goto bail; | |
} | |
if (error) { | |
errno = error; | |
log_socket_error(d, "connect"); | |
goto bail; | |
} | |
} | |
{ | |
int ndelay = 1; | |
if (setsockopt(conn->dc_sock, IPPROTO_TCP, TCP_NODELAY, | |
(char *)&ndelay, sizeof(ndelay)) < 0) { | |
log_socket_error(d, "setsockopt TCP_NODELAY"); | |
goto bail; | |
} | |
} | |
// After we connect, the DAC will send an initial status response | |
if (read_resp(d) < 0) | |
goto bail; | |
{ | |
char c = 'p'; | |
send_all(d, &c, 1); | |
} | |
if (read_resp(d) < 0) | |
goto bail; | |
dump_resp(d); | |
if (d->sw_revision >= 2) { | |
char c = 'v'; | |
if (send_all(d, &c, 1) < 0) | |
goto bail; | |
int res = read_bytes(d, d->version, sizeof(d->version)); | |
if (res < 0) | |
return res; | |
} | |
else { | |
strcpy(d->version, "[old]"); | |
} | |
trace(d, "DAC version %.*s\n", sizeof(d->version), d->version); | |
return 0; | |
bail: | |
#ifdef _MSC_VER | |
closesocket(d->conn.dc_sock); | |
#else | |
close(d->conn.dc_sock); | |
#endif | |
return -1; | |
} | |
/* check_data_response(d) | |
* | |
* Handle a response from d: update our record of the number of sent-but-not- | |
* ACKed points, and error if the response was unexpected. | |
*/ | |
static int check_data_response(struct etherdream *d) { | |
struct etherdream_conn *conn = &d->conn; | |
if (conn->resp.dac_status.playback_state == 0) | |
conn->dc_begin_sent = 0; | |
if (conn->resp.command == 'd') { | |
if (conn->ackbuf_prod == conn->ackbuf_cons) { | |
trace(d, "!! protocol error: unexpected data ack\n"); | |
return -1; | |
} | |
conn->unacked_points -= conn->ackbuf[conn->ackbuf_cons]; | |
conn->ackbuf_cons = (conn->ackbuf_cons + 1) % MAX_LATE_ACKS; | |
} | |
else { | |
conn->pending_meta_acks--; | |
} | |
if (conn->resp.response != 'a' && conn->resp.response != 'I') { | |
trace(d, "!! protocol error: ACK for '%c' got '%c' (%d)\n", | |
conn->resp.command, | |
conn->resp.response, conn->resp.response); | |
return -1; | |
} | |
return 0; | |
} | |
/* dac_get_acks(d, wait) | |
* | |
* Read any ACKs we are owed, waiting up to 'wait' microseconds. | |
*/ | |
static int dac_get_acks(struct etherdream *d, int wait) { | |
while (d->conn.pending_meta_acks | |
|| (d->conn.ackbuf_prod != d->conn.ackbuf_cons)) { | |
int res = wait_for_fd_activity(d, wait, 0); | |
if (res <= 0) | |
return res; | |
if ((res = read_resp(d)) < 0) | |
return res; | |
if ((res = check_data_response(d)) < 0) | |
return res; | |
} | |
return 0; | |
} | |
/* dac_send_data(d, data, npoints, rate) | |
* | |
* Send points to the DAC, including prepare or begin commands and changing | |
* the point rate as necessary. | |
*/ | |
static int dac_send_data(struct etherdream *d, struct dac_point *data, | |
int npoints, int rate) { | |
int res; | |
const struct dac_status *st = &d->conn.resp.dac_status; | |
if (st->playback_state == 0) { | |
trace(d, "L: Sending prepare command...\n"); | |
char c = 'p'; | |
if ((res = send_all(d, &c, sizeof c)) < 0) | |
return res; | |
d->conn.pending_meta_acks++; | |
/* Block here until all ACKs received... XXX timeout */ | |
while (d->conn.pending_meta_acks) | |
dac_get_acks(d, 1500); | |
trace(d, "L: prepare ACKed\n"); | |
} | |
if (st->buffer_fullness > 1600 && st->playback_state == 1 \ | |
&& !d->conn.dc_begin_sent) { | |
trace(d, "L: Sending begin command...\n"); | |
struct begin_command b = { | |
'b', | |
0, | |
(uint32_t)rate | |
}; | |
if ((res = send_all(d, (const char *)&b, sizeof b)) < 0) | |
return res; | |
d->conn.dc_begin_sent = 1; | |
d->conn.pending_meta_acks++; | |
} | |
if ((res = dac_get_acks(d, 0)) < 0) | |
return res; | |
if (npoints <= 0) | |
return 0; | |
d->conn.dc_local_buffer.queue.command = 'q'; | |
d->conn.dc_local_buffer.queue.point_rate = rate; | |
d->conn.dc_local_buffer.header.command = 'd'; | |
d->conn.dc_local_buffer.header.npoints = npoints; | |
memcpy(&d->conn.dc_local_buffer.data[0], data, | |
npoints * sizeof(struct dac_point)); | |
d->conn.dc_local_buffer.data[0].control |= DAC_CTRL_RATE_CHANGE; | |
/* Write the data */ | |
if ((res = send_all(d, (const char *)&d->conn.dc_local_buffer, | |
8 + npoints * sizeof(struct dac_point))) < 0) | |
return res; | |
/* Expect two ACKs */ | |
d->conn.pending_meta_acks++; | |
d->conn.ackbuf[d->conn.ackbuf_prod] = npoints; | |
d->conn.ackbuf_prod = (d->conn.ackbuf_prod + 1) % MAX_LATE_ACKS; | |
d->conn.unacked_points += npoints; | |
return 0; | |
} | |
#define SHOULD_TRACE() (expected_fullness < DEBUG_THRESHOLD_POINTS \ | |
|| d->conn.resp.dac_status.buffer_fullness < DEBUG_THRESHOLD_POINTS) | |
/* dac_loop(dv) | |
* | |
* Main thread function for sending data to the DAC. | |
*/ | |
static void *dac_loop(etherdream *d) { | |
int res = 0; | |
while (1) { | |
/* Wait for us to have data */ | |
int state; | |
while ((state = d->state) == ST_READY) { | |
trace(d, "L: waiting\n"); | |
std::unique_lock<std::mutex> lock(d->mutex); | |
d->loop_cond.wait(lock); | |
} | |
if (state != ST_RUNNING) | |
break; | |
struct buffer_item *b = &d->buffer[d->frame_buffer_read]; | |
int cap; | |
int expected_used, expected_fullness; | |
while (1) { | |
res = 0; | |
/* Estimate how much data has been consumed since the | |
* last time we got an ACK. */ | |
long long time_diff = microseconds() | |
- d->conn.dc_last_ack_time; | |
expected_used = time_diff * b->pps / 1000000; | |
if (d->conn.resp.dac_status.playback_state != 2) | |
expected_used = 0; | |
expected_fullness = | |
d->conn.resp.dac_status.buffer_fullness | |
+ d->conn.unacked_points - expected_used; | |
/* Now, see how much data we should write. */ | |
cap = 1700 - expected_fullness; | |
if (cap > MIN_SEND_POINTS) | |
break; | |
if (d->conn.resp.dac_status.playback_state != 2) { | |
microsleep(1000); | |
break; | |
} | |
/* Wait a little. */ | |
int diff = MIN_SEND_POINTS - cap; | |
int wait_time = 500 + (1000000L * diff / b->pps); | |
if (SHOULD_TRACE()) | |
trace(d, "L: st %d om %d; b %d + %d - %d = %d" | |
" -> c %d, wait %d us\n", | |
d->conn.resp.dac_status.playback_state, | |
d->conn.pending_meta_acks, | |
d->conn.resp.dac_status.buffer_fullness, | |
d->conn.unacked_points, expected_used, | |
expected_fullness, cap, wait_time); | |
microsleep(wait_time); | |
if ((res = dac_get_acks(d, 0)) < 0) | |
break; | |
} | |
if (res < 0) | |
break; | |
/* How many points can we send? */ | |
int b_left = b->points - b->idx; | |
if (cap > b_left) | |
cap = b_left; | |
if (cap > 80) | |
cap = 80; | |
if (SHOULD_TRACE()) | |
trace(d, "L: st %d om %d; b %d + %d - %d = %d" | |
" -> write %d\n", | |
d->conn.resp.dac_status.playback_state, | |
d->conn.pending_meta_acks, | |
d->conn.resp.dac_status.buffer_fullness, | |
d->conn.unacked_points, expected_used, | |
expected_fullness, cap); | |
res = dac_send_data(d, b->data + b->idx, cap, b->pps); | |
if (res < 0) | |
break; | |
{ | |
std::unique_lock<std::mutex> lock(d->mutex); | |
/* What next? */ | |
b->idx += cap; | |
if (b->idx < b->points) { | |
/* There's more in this frame. */ | |
continue; | |
} | |
b->idx = 0; | |
if (b->repeatcount > 1) { | |
/* Play this frame again? */ | |
b->repeatcount--; | |
} | |
else if (d->frame_buffer_fullness > 1) { | |
/* Move to the next frame */ | |
d->frame_buffer_fullness--; | |
d->frame_buffer_read++; | |
if (d->frame_buffer_read >= BUFFER_NFRAMES) | |
d->frame_buffer_read = 0; | |
d->loop_cond.notify_all(); | |
} | |
else if (b->repeatcount >= 0) { | |
/* Stop playing until we get a new frame. */ | |
trace(d, "L: returning to idle\n"); | |
d->state = ST_READY; | |
} | |
else { | |
/* repeatcount is negative and there's no new frame, | |
* so just play this one over again. */ | |
} | |
} | |
} | |
trace(d, "L: Shutting down.\n"); | |
d->state = ST_SHUTDOWN; | |
d->loop_cond.notify_all(); | |
return 0; | |
} | |
int etherdream_connect(struct etherdream *d) { | |
trace(d, "L: Connecting.\n"); | |
// Initialize buffer | |
d->frame_buffer_read = 0; | |
d->frame_buffer_fullness = 0; | |
memset(d->buffer, 0, sizeof(d->buffer)); | |
// Connect to the DAC | |
if (dac_connect(d) < 0) { | |
trace(d, "!! DAC connection failed.\n"); | |
return -1; | |
} | |
d->state = ST_READY; | |
d->workerthread = std::thread([d]() { | |
dac_loop(d); | |
}); | |
trace(d, "Ready.\n"); | |
return 0; | |
} | |
int etherdream_is_connected(struct etherdream *d) { | |
return d->state == ST_READY; | |
} | |
void etherdream_disconnect(struct etherdream *d) { | |
trace(d, "L: Disconnecting.\n"); | |
d->mutex.lock(); | |
if (d->state == ST_READY) | |
d->loop_cond.notify_all(); | |
d->state = ST_SHUTDOWN; | |
d->mutex.unlock(); | |
d->workerthread.join(); | |
#ifdef _MSC_VER | |
closesocket(d->conn.dc_sock); | |
#else | |
close(d->conn.dc_sock); | |
#endif | |
} | |
/* etherdream_get_id(d) | |
* | |
* Documented in etherdream.h. | |
*/ | |
unsigned long etherdream_get_id(struct etherdream *d) { | |
return d->dac_id; | |
} | |
/* etherdream_get_in_addr(d) | |
* | |
* Documented in etherdream.h. | |
*/ | |
const struct in_addr *etherdream_get_in_addr(struct etherdream *d) { | |
return &d->addr; | |
} | |
/* etherdream_write(d, pts, npts, pps, reps) | |
* | |
* Documented in etherdream.h. | |
*/ | |
int etherdream_write(struct etherdream *d, const struct etherdream_point *pts, | |
int npts, int pps, int reps) { | |
/* Limit maximum frame size */ | |
if (npts > BUFFER_POINTS_PER_FRAME) | |
npts = BUFFER_POINTS_PER_FRAME; | |
/* Ignore 0-repeat frames */ | |
if (!reps) | |
return 0; | |
d->mutex.lock(); | |
/* If not ready for a new frame, bail */ | |
if (d->frame_buffer_fullness == BUFFER_NFRAMES) { | |
d->mutex.unlock(); | |
trace(d, "M: NOT READY: %d points, %d reps\n", npts, reps); | |
return -1; | |
} | |
struct buffer_item *next = &d->buffer[(d->frame_buffer_read | |
+ d->frame_buffer_fullness) % BUFFER_NFRAMES]; | |
d->mutex.unlock(); | |
// trace(d, "M: Writing: %d points, %d reps, %d pps\n", npts, reps, pps); | |
/* XXX: automatically pad out small frames */ | |
int i; | |
for (i = 0; i < npts; i++) { | |
next->data[i].x = pts[i].x; | |
next->data[i].y = pts[i].y; | |
next->data[i].r = pts[i].r; | |
next->data[i].g = pts[i].g; | |
next->data[i].b = pts[i].b; | |
next->data[i].i = pts[i].i; | |
next->data[i].u1 = pts[i].u1; | |
next->data[i].u2 = pts[i].u2; | |
next->data[i].control = 0; | |
} | |
next->pps = pps; | |
next->repeatcount = reps; | |
next->points = npts; | |
/* Advance buffer and signal the writing thread if necessary */ | |
d->mutex.lock(); | |
d->frame_buffer_fullness++; | |
if (d->state == ST_READY) | |
d->loop_cond.notify_one(); | |
d->state = ST_RUNNING; | |
d->mutex.unlock(); | |
return 0; | |
} | |
/* etherdream_is_ready(d) | |
* | |
* Documented in etherdream.h. | |
*/ | |
int etherdream_is_ready(struct etherdream *d) { | |
d->mutex.lock(); | |
int ready = (d->frame_buffer_fullness != BUFFER_NFRAMES); | |
d->mutex.unlock(); | |
return ready; | |
} | |
/* etherdream_wait_for_ready(d) | |
* | |
* Documented in etherdream.h. | |
*/ | |
int etherdream_wait_for_ready(struct etherdream *d) { | |
d->mutex.lock(); | |
while (d->frame_buffer_fullness == BUFFER_NFRAMES && d->state != ST_SHUTDOWN) { | |
std::unique_lock<std::mutex> lock(d->mutex); d->loop_cond.wait(lock); | |
} | |
int is_shutdown = (d->state == ST_SHUTDOWN); | |
d->mutex.unlock(); | |
if (is_shutdown) { | |
return -1; | |
} | |
else { | |
return 0; | |
} | |
} | |
/* etherdream_stop(d) | |
* | |
* Documented in etherdream.h. | |
*/ | |
int etherdream_stop(struct etherdream *d) { | |
d->mutex.lock(); | |
if (d->state == ST_RUNNING) | |
d->buffer[d->frame_buffer_read].repeatcount = 0; | |
d->mutex.unlock(); | |
return 0; | |
} | |
/* watch_for_dacs(arg) | |
* | |
* Thread function for the broadcast monitor thread. This listens for UDP | |
* broadcasts from Ether Dream boards on the network and adds them to our list. | |
*/ | |
static void *watch_for_dacs() { | |
int sock = socket(AF_INET, SOCK_DGRAM, 0); | |
if (sock < 0) { | |
log_socket_error(NULL, "socket"); | |
return NULL; | |
} | |
int opt = 1; | |
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, | |
sizeof opt) < 0) { | |
log_socket_error(NULL, "setsockopt SO_REUSEADDR"); | |
return NULL; | |
} | |
struct sockaddr_in addr; | |
addr.sin_family = AF_INET; | |
addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
addr.sin_port = htons(7654); | |
if (bind(sock, (struct sockaddr *)&addr, sizeof addr) < 0) { | |
log_socket_error(NULL, "bind"); | |
return NULL; | |
} | |
trace(NULL, "_: listening for DACs...\n"); | |
while (1) { | |
struct sockaddr_in src; | |
struct dac_broadcast buf; | |
#ifdef _MSC_VER | |
int srclen = sizeof src; | |
#else | |
unsigned int srclen = sizeof src; | |
#endif | |
int len = recvfrom(sock, (char *)&buf, sizeof buf, 0, | |
(struct sockaddr *)&src, &srclen); | |
if (len < 0) { | |
log_socket_error(NULL, "recvfrom"); | |
return NULL; | |
} | |
/* See if this is a DAC we already knew about */ | |
dac_list_lock.lock(); | |
struct etherdream *p = dac_list; | |
while (p) { | |
if (p->addr.s_addr == src.sin_addr.s_addr) | |
break; | |
p = p->next; | |
} | |
if (p && (p->addr.s_addr == src.sin_addr.s_addr)) { | |
dac_list_lock.unlock(); | |
continue; | |
} | |
dac_list_lock.unlock(); | |
/* Make a new DAC entry */ | |
struct etherdream *new_dac; | |
new_dac = new etherdream(); | |
new_dac->addr = src.sin_addr; | |
memcpy(new_dac->mac_address, buf.mac_address, 6); | |
new_dac->dac_id = (buf.mac_address[3] << 16) | |
| (buf.mac_address[4] << 8) | |
| buf.mac_address[5]; | |
new_dac->sw_revision = buf.sw_revision; | |
new_dac->state = ST_DISCONNECTED; | |
trace(NULL, "_: Found new DAC: %s\n", inet_ntoa(src.sin_addr)); | |
dac_list_lock.lock(); | |
new_dac->next = dac_list; | |
dac_list = new_dac; | |
dac_list_lock.unlock(); | |
} | |
trace(NULL, "_: Exiting\n"); | |
return NULL; | |
} | |
/* etherdream_lib_start() | |
* | |
* Documented in etherdream.h. | |
*/ | |
int etherdream_lib_start(void) { | |
// Set up the logging fd (just stderr for now) | |
trace_fp = stderr; | |
fprintf(trace_fp, "----------\n"); | |
fflush(trace_fp); | |
trace(NULL, "== libetherdream started ==\n"); | |
watcherThread = std::thread(watch_for_dacs); | |
#ifdef _MSC_VER | |
{ | |
WORD wVersionRequested; | |
WSADATA wsaData; | |
wVersionRequested = MAKEWORD(2, 2); | |
auto err = WSAStartup(wVersionRequested, &wsaData); | |
if (err != 0) { | |
printf("WSAStartup failed with error: %d\n", err); | |
return err; | |
} | |
} | |
#endif | |
return 0; | |
} | |
/* etherdream_dac_count() | |
* | |
* Documented in etherdream.h. | |
*/ | |
int etherdream_dac_count(void) { | |
dac_list_lock.lock(); | |
int count = 0; | |
struct etherdream *d = dac_list; | |
while (d) { | |
d = d->next; | |
count++; | |
} | |
dac_list_lock.unlock(); | |
trace(NULL, "== etherdream_lib_get_dac_count(): %d\n", count); | |
return count; | |
} | |
/* etherdream_get() | |
* | |
* Documented in etherdream.h. | |
*/ | |
struct etherdream *etherdream_get(unsigned long idx) { | |
struct etherdream *d = dac_list; | |
unsigned long i = 0; | |
while (d) { | |
// Match by either numerical position or ID | |
if (idx == i || idx == d->dac_id) | |
return d; | |
i++; | |
d = d->next; | |
} | |
return NULL; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef ETHERDREAM_H | |
#define ETHERDREAM_H | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
#include <stdint.h> | |
struct etherdream_point { | |
int16_t x; | |
int16_t y; | |
uint16_t r; | |
uint16_t g; | |
uint16_t b; | |
uint16_t i; | |
uint16_t u1; | |
uint16_t u2; | |
}; | |
struct etherdream; | |
/* etherdream_lib_start() | |
* | |
* Initialize the Ether Dream library and start a background thread to listen | |
* for DAC broadcasts. This should be called exactly once at program startup. | |
* | |
* Returns 0 on success, -1 on failure. | |
*/ | |
int etherdream_lib_start(void); | |
/* etherdream_dac_count() | |
* | |
* Return the number of detected DACs since etherdream_lib_start() was called. | |
* Ether Dream DACs broadcast once per second, so calling code should wait a | |
* little over a second after etherdream_lib_start() to ensure that all DACs | |
* on the network are seen. | |
*/ | |
int etherdream_dac_count(void); | |
/* etherdream_get(idx) | |
* | |
* Return the [i]'th detected DAC. This function accepts either an integer index | |
* (0-based) or an ID value as returned by etherdream_get_id(). Returns NULL | |
* if the requested DAC is not available. | |
*/ | |
struct etherdream *etherdream_get(unsigned long idx); | |
/* etherdream_get_id(d) | |
* | |
* Return the ID value (equal to the second half of the MAC address, when | |
* represented in hex) of the given Ether Dream. Does not require that a | |
* connection to d has been established. | |
*/ | |
unsigned long etherdream_get_id(struct etherdream *d); | |
/* etherdream_get_in_addr(d) | |
* | |
* Return the IP address of the given Ether Dream. Does not require that | |
* a connection to d has been established. | |
*/ | |
const struct in_addr *etherdream_get_in_addr(struct etherdream *d); | |
/* etherdream_connect(d) | |
* | |
* Open a connection to d. This must be called before most other etherdream_ | |
* functions can be used. | |
*/ | |
int etherdream_connect(struct etherdream *d); | |
/* etherdream_is_connected(d) | |
* | |
* Returns 1 if the network connection to d is connected, 0 if not. | |
*/ | |
int etherdream_is_connected(struct etherdream *d); | |
/* etherdream_is_ready(d) | |
* | |
* Return 1 if the local buffer for d can accept more frames, 0 if not, -1 on | |
* error (if the connection to d has not been opened or has failed). | |
*/ | |
int etherdream_is_ready(struct etherdream *d); | |
/* etherdream_wait_for_ready(d) | |
* | |
* Block the invoking thread until more data can be written to d. Returns 0 on | |
* success, -1 if the connection to d is not open or has failed. | |
*/ | |
int etherdream_wait_for_ready(struct etherdream *d); | |
/* etherdream_write(d, pts, npts, pps, repeatcount) | |
* | |
* Write a "frame" consisting of pts (length npts) to d. | |
* | |
* If repeatcount is -1, pts will be sent to the laser repeatedly until new | |
* data is received or until etherdream_stop is called. Otherwise, the points | |
* will be sent repeatedly at most npts times, and then the stream will | |
* automatically stop. pps specifies the output rate (30000 is a common value). | |
* repeatcount must not be 0. | |
* | |
* The Ether Dream uses a continuous streaming protocol, so if new frames are | |
* continuously sent, frame boundaries are not visible; however, to reduce | |
* overhead, frames should be reasonably large (at least 50-100 points). | |
*/ | |
int etherdream_write(struct etherdream *d, const struct etherdream_point *pts, | |
int npts, int pps, int repeatcount); | |
/* etherdream_stop(d) | |
* | |
* Stop output from d as soon as the current frame is finished. | |
*/ | |
int etherdream_stop(struct etherdream *d); | |
/* etherdream_disconnect(d) | |
* | |
* Close the TCP connection to d. | |
*/ | |
void etherdream_disconnect(struct etherdream *d); | |
#ifdef __cplusplus | |
} // extern "c" | |
#endif | |
#endif // ETHERDREAM_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment