diff --git a/.gitignore b/.gitignore index b740213..80cab54 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ bin/ # Kate temporary files *~ *.kate-swp +.vscode diff --git a/debug.lldb b/debug.lldb new file mode 100644 index 0000000..a42e2c0 --- /dev/null +++ b/debug.lldb @@ -0,0 +1,2 @@ +b opc_server.c:57 +run -l layouts/freespace.json -t UDP diff --git a/python/opc.py b/python/opc.py index 36a3bbd..8875bc3 100755 --- a/python/opc.py +++ b/python/opc.py @@ -41,7 +41,7 @@ class Client(object): - def __init__(self, server_ip_port, long_connection=True, verbose=False): + def __init__(self, server_ip_port, long_connection=True, verbose=False, socket_type="UDP"): """Create an OPC client object which sends pixels to an OPC server. server_ip_port should be an ip:port or hostname:port as a single string. @@ -72,6 +72,8 @@ def __init__(self, server_ip_port, long_connection=True, verbose=False): self._socket = None # will be None when we're not connected + self.socket_type=socket_type + def _debug(self, m): if self.verbose: print(' %s' % str(m)) @@ -82,18 +84,37 @@ def _ensure_connected(self): Return True on success or False on failure. """ + if self._socket: self._debug('_ensure_connected: already connected, doing nothing') return True try: self._debug('_ensure_connected: trying to connect...') - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + + + # print("forcing UDP") + # self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP + + if self.socket_type == "TCP": + print("USING TCP") + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP + elif self.socket_type == "UDP": + print("USING UDP") + self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP + self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + + else: + print("Invalid socket string.") + self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP + self._socket.settimeout(1) self._socket.connect((self._ip, self._port)) self._debug('_ensure_connected: ...success') return True except socket.error: + print("gotSocketError") self._debug('_ensure_connected: ...failure') self._socket = None return False @@ -127,7 +148,7 @@ def put_pixels(self, pixels, channel=0): 0 is a special value which means "all channels". pixels: A list of 3-tuples representing rgb colors. - Each value in the tuple should be in the range 0-255 inclusive. + Each value in the tuple should be in the range 0-255 inclusive. For example: [(255, 255, 255), (0, 0, 0), (127, 0, 0)] Floats will be rounded down to integers. Values outside the legal range will be clamped. diff --git a/python/raver_plaid_parse.py b/python/raver_plaid_parse.py new file mode 100644 index 0000000..0a4e6ab --- /dev/null +++ b/python/raver_plaid_parse.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python + +"""A demo client for Open Pixel Control +http://github.com/zestyping/openpixelcontrol + +Creates a shifting rainbow plaid pattern by overlaying different sine waves +in the red, green, and blue channels. + +To run: +First start the gl simulator using the included "wall" layout + + make + bin/gl_server layouts/wall.json + +Then run this script in another shell to send colors to the simulator + + python_clients/raver_plaid.py + +""" + +from __future__ import division +import time +import math +import sys + +import opc +import color_utils + +import optparse + + +#------------------------------------------------------------------------------- +# handle command line + +# The parser... +parser = optparse.OptionParser() +parser.add_option('-l', '--layout', dest='layout', + action='store', type='string', + help='layout file') +parser.add_option('-s', '--server', dest='server', default='127.0.0.1:7890', + action='store', type='string', + help='ip and port of server') +parser.add_option('-f', '--fps', dest='fps', default=20, + action='store', type='int', + help='frames per second') + +parser.add_option('-t', '--transport', dest='transport', default='UDP', + action='store', type='string', + help='specify transport, UDP or TCP') + +parser.add_option('-b', '--broadcast', dest='broadcast', + action='store_true', + help='hit broadcast address') + +options, args = parser.parse_args() + +print(len(sys.argv)) +if len(sys.argv) == 1: + IP_PORT = '127.0.0.1:7890' +else: + IP_PORT = options.server + +if options.broadcast: + print("Using broadcast address.") + IP_PORT = '10.200.1.255:7890' + +# elif len(sys.argv) == 2 and ':' in sys.argv[1] and not sys.argv[1].startswith('-'): +# IP_PORT = sys.argv[1] +# else: +# print(''' +# Usage: raver_plaid.py [ip:port] +# +# If not set, ip:port defauls to 127.0.0.1:7890 +# ''') +# sys.exit(0) + + +#------------------------------------------------------------------------------- +# connect to server + +client = opc.Client(IP_PORT, socket_type=options.transport) +if client.can_connect(): + print(' connected to %s' % IP_PORT) +else: + # can't connect, but keep running in case the server appears later + print(' WARNING: could not connect to %s' % IP_PORT) +print('') + + +#------------------------------------------------------------------------------- +# send pixels + +print(' sending pixels forever (control-c to exit)...') +print('') + +n_pixels = 300 # number of pixels in the included "wall" layout +fps = 60 # frames per second + +# how many sine wave cycles are squeezed into our n_pixels +# 24 happens to create nice diagonal stripes on the wall layout +freq_r = 24 +freq_g = 24 +freq_b = 24 + +# how many seconds the color sine waves take to shift through a complete cycle +speed_r = 7 +speed_g = -13 +speed_b = 19 + +start_time = time.time() +while True: + t = (time.time() - start_time) * 5 + pixels = [] + for ii in range(n_pixels): + pct = (ii / n_pixels) + # diagonal black stripes + pct_jittered = (pct * 77) % 37 + blackstripes = color_utils.cos(pct_jittered, offset=t*0.05, period=1, minn=-1.5, maxx=1.5) + blackstripes_offset = color_utils.cos(t, offset=0.9, period=60, minn=-0.5, maxx=3) + blackstripes = color_utils.clamp(blackstripes + blackstripes_offset, 0, 1) + # 3 sine waves for r, g, b which are out of sync with each other + r = blackstripes * color_utils.remap(math.cos((t/speed_r + pct*freq_r)*math.pi*2), -1, 1, 0, 256) + g = blackstripes * color_utils.remap(math.cos((t/speed_g + pct*freq_g)*math.pi*2), -1, 1, 0, 256) + b = blackstripes * color_utils.remap(math.cos((t/speed_b + pct*freq_b)*math.pi*2), -1, 1, 0, 256) + pixels.append((r, g, b)) + client.put_pixels(pixels, channel=0) + time.sleep(1 / fps) diff --git a/src/gl_server.c b/src/gl_server.c index 41ac131..635ddc1 100644 --- a/src/gl_server.c +++ b/src/gl_server.c @@ -31,17 +31,18 @@ opc_source source = -1; int verbose = 0; // Camera parameters -#define FOV_DEGREES 20 +#define FOV_DEGREES 40 int orbiting = 0, dollying = 0; double start_angle, start_elevation, start_distance; int start_x, start_y; double orbit_angle = 192.0; // camera orbit angle, degrees double camera_elevation = -15; // camera elevation angle, degrees -double camera_distance = 16.0; // distance from origin, metres +// double camera_distance = 16.0; // distance from origin, metres +double camera_distance = 100.0; // distance from origin, metres double camera_aspect = 1.0; // will be updated to match window aspect ratio // Shape parameters -#define SHAPE_THICKNESS 0.06 // thickness of points and lines, metres +#define SHAPE_THICKNESS 0.6 // thickness of points and lines, metres #define MAX_CHANNELS 10 int channel_offsets[MAX_CHANNELS]; @@ -338,7 +339,7 @@ void load_layout(char* filename, int channel) { cJSON* start; cJSON* x2; int i = 0; - + buffer = read_file(filename); if (buffer == NULL) { fprintf(stderr, "Unable to open '%s'\n", filename); @@ -425,7 +426,7 @@ int main(int argc, char** argv) { int opt; char* layouts[MAX_CHANNELS]; - while ((opt = getopt(argc, argv, ":hl:p:")) != -1) + while ((opt = getopt(argc, argv, ":hl:p:t:")) != -1) { switch (opt) { @@ -440,6 +441,15 @@ int main(int argc, char** argv) { case 'p': port = strtol(optarg, NULL, 10); break; + case 't': + if ( (strcmp(optarg, "UDP") == 0) || (strcmp(optarg, "TCP") == 0) ){ + strcpy(transport, optarg); + } + else { + fprintf(stderr, "Transport can be UDP or TCP\n"); + exit(1); + } + break; case ':': fprintf(stderr, "Missing argument to option: '%c'\n", optopt); usage(argv[0]); diff --git a/src/opc.h b/src/opc.h index db1b3df..abce98b 100644 --- a/src/opc.h +++ b/src/opc.h @@ -30,6 +30,9 @@ specific language governing permissions and limitations under the License. */ /* Maximum number of pixels in one message */ #define OPC_MAX_PIXELS_PER_MESSAGE ((1 << 16) / 3) +// OPC global vars for options +char transport[32]; // either "UDP" or "TCP" + // OPC client functions ---------------------------------------------------- /* Handle for an OPC sink created by opc_new_sink. */ diff --git a/src/opc_server.c b/src/opc_server.c index c9d76d5..45d0064 100644 --- a/src/opc_server.c +++ b/src/opc_server.c @@ -39,18 +39,34 @@ int opc_listen(u16 port) { int sock; int one = 1; - sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + // use TCP + if ( strcmp(transport, "TCP") == 0 ){ + fprintf(stderr, "Using transport: %s\n", transport); + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + } + // use UDP + else if ( strcmp(transport, "UDP") == 0 ){ + fprintf(stderr, "Using transport: %s\n", transport); + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + } + else { + fprintf(stderr, "Defaulting to transport: TCP\n", transport); + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + strcpy(transport, "TCP"); + } + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); address.sin_family = AF_INET; address.sin_port = htons(port); - bzero(&address.sin_addr, sizeof(address.sin_addr)); + // bzero(&address.sin_addr, sizeof(address.sin_addr)); // zeros.. fill address later? + address.sin_addr.s_addr = inet_addr("127.0.0.1"); // for now just set explicitly if (bind(sock, (struct sockaddr*) &address, sizeof(address)) != 0) { fprintf(stderr, "OPC: Could not bind to port %d: ", port); perror(NULL); return -1; } - if (listen(sock, 0) != 0) { + if (listen(sock, 0) != 0 && (strcmp(transport, "TCP") == 0) ) { // only relevant for TCP fprintf(stderr, "OPC: Could not listen on port %d: ", port); perror(NULL); return -1; @@ -110,14 +126,29 @@ u8 opc_receive(opc_source source, opc_handler* handler, u32 timeout_ms) { select(nfds, &readfds, NULL, NULL, &timeout); if (info->listen_sock >= 0 && FD_ISSET(info->listen_sock, &readfds)) { /* Handle an inbound connection. */ - info->sock = accept( - info->listen_sock, (struct sockaddr*) &(address), &address_len); - inet_ntop(AF_INET, &(address.sin_addr), buffer, 64); - fprintf(stderr, "OPC: Client connected from %s\n", buffer); - close(info->listen_sock); - info->listen_sock = -1; - info->header_length = 0; - info->payload_length = 0; + if (strcmp(transport, "TCP") == 0){ + fprintf(stderr, "Accept TCP connection\n"); + info->sock = accept( + info->listen_sock, (struct sockaddr *)&(address), &address_len); + inet_ntop(AF_INET, &(address.sin_addr), buffer, 64); + fprintf(stderr, "OPC: Client connected from %s\n", buffer); + close(info->listen_sock); + info->listen_sock = -1; + info->header_length = 0; + info->payload_length = 0; + } + // Logic for handling data inbound on UDP connection + else if (strcmp(transport, "UDP") == 0){ + fprintf(stderr, "UDP is connectionless. %s\n", buffer); + info->sock = info->listen_sock; + info->listen_sock = -1; + info->header_length = 0; + info->payload_length = 0; + } + else { + fprintf(stderr, "Invalid Transport\n"); + exit(1); + } } else if (info->sock >= 0 && FD_ISSET(info->sock, &readfds)) { /* Handle inbound data on an existing connection. */ if (info->header_length < 4) { /* need header */ @@ -139,8 +170,14 @@ u8 opc_receive(opc_source source, opc_handler* handler, u32 timeout_ms) { if (info->header_length == 4 && info->payload_length == payload_expected) { /* payload complete */ if (info->header[1] == OPC_SET_PIXELS) { + if (strcmp(transport, "UDP") == 0){ + handler(info->header[0], payload_expected/3, + (pixel*) ((void*)info->payload + 4) ); // a hack... + } + else if ( strcmp(transport, "TCP") == 0 ){ handler(info->header[0], payload_expected/3, (pixel*) info->payload); + } } info->header_length = 0; info->payload_length = 0;