/* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * artnet.c * Implementes the external functions for libartnet * Copyright (C) 2004-2007 Simon Newton */ #include "private.h" // various constants used everywhere int ARTNET_ADDRESS_NO_CHANGE = 0x7f; int ARTNET_PORT = 6454; int ARTNET_STRING_SIZE = 8; char ARTNET_STRING[] = "Art-Net"; uint8_t ARTNET_VERSION = 14; uint8_t OEM_HI = 0x04; uint8_t OEM_LO = 0x30; char ESTA_HI = 'z'; char ESTA_LO = 'p'; uint8_t TTM_BEHAVIOUR_MASK = 0x02; uint8_t TTM_REPLY_MASK = 0x01; uint8_t PROGRAM_NO_CHANGE = 0x7f; uint8_t PROGRAM_DEFAULTS = 0x00; uint8_t PROGRAM_CHANGE_MASK = 0x80; uint8_t HIGH_NIBBLE = 0xF0; uint8_t LOW_NIBBLE = 0x0F; uint8_t STATUS_PROG_AUTH_MASK = 0x30; uint8_t PORT_STATUS_LPT_MODE = 0x02; uint8_t PORT_STATUS_SHORT = 0x04; uint8_t PORT_STATUS_ERROR = 0x04; uint8_t PORT_STATUS_DISABLED_MASK = 0x08; uint8_t PORT_STATUS_MERGE = 0x08; uint8_t PORT_STATUS_DMX_TEXT = 0x10; uint8_t PORT_STATUS_DMX_SIP = 0x20; uint8_t PORT_STATUS_DMX_TEST = 0x40; uint8_t PORT_STATUS_ACT_MASK = 0x80; uint8_t PORT_DISABLE_MASK = 0x01; uint8_t TOD_RESPONSE_FULL = 0x00; uint8_t TOD_RESPONSE_NAK = 0x00; uint8_t MIN_PACKET_SIZE = 10; uint8_t MERGE_TIMEOUT_SECONDS = 10; uint8_t FIRMWARE_TIMEOUT_SECONDS = 20; uint8_t RECV_NO_DATA = 1; uint8_t MAX_NODE_BCAST_LIMIT = 30; // always bcast after this point #ifndef TRUE int TRUE = 1; int FALSE = 0; #endif uint16_t LOW_BYTE = 0x00FF; uint16_t HIGH_BYTE = 0xFF00; void copy_apr_to_node_entry(artnet_node_entry e, artnet_reply_t *reply); int find_nodes_from_uni(node_list_t *nl, uint8_t uni, SI *ips, int size); /* * Creates a new ArtNet node. * Takes a string containing the ip address to bind to, if the string is NULL * it uses the first non loopback address * * @param ip the IP address to bind to * @param debug level of logging provided 0: none * @return an artnet_node, or NULL on failure */ artnet_node artnet_new(const char *ip, int verbose) { node n; int i; n = malloc(sizeof(artnet_node_t)); if (!n) { artnet_error("malloc failure"); return NULL; } memset(n, 0x0, sizeof(artnet_node_t)); // init node listing n->node_list.first = NULL; n->node_list.current = NULL; n->node_list.last = NULL; n->node_list.length = 0; n->state.verbose = verbose; n->state.oem_hi = OEM_HI; n->state.oem_lo = OEM_LO; n->state.esta_hi = ESTA_HI; n->state.esta_lo = ESTA_LO; n->state.bcast_limit = 0; n->peering.peer = NULL; n->peering.master = TRUE; n->sd = INVALID_SOCKET; if (artnet_net_init(n, ip)) { free(n); return NULL; } // now setup the default parameters n->state.send_apr_on_change = FALSE; n->state.ar_count = 0; n->state.report_code = ARTNET_RCPOWEROK; n->state.reply_addr.s_addr = 0; n->state.mode = ARTNET_STANDBY; // set all ports to MERGE HTP mode and disable for (i=0; i < ARTNET_MAX_PORTS; i++) { n->ports.out[i].merge_mode = ARTNET_MERGE_HTP; n->ports.out[i].port_enabled = FALSE; n->ports.in[i].port_enabled = FALSE; // reset tods reset_tod(&n->ports.in[i].port_tod); reset_tod(&n->ports.out[i].port_tod); } return n; } /* * Starts the ArtNet node. * Binds the network socket and sends an ArtPoll * @param vn the artnet_node * @return 0 on success, non 0 on failure * */ int artnet_start(artnet_node vn) { node n = (node) vn; int ret; check_nullnode(vn); if (n->state.mode != ARTNET_STANDBY) return ARTNET_ESTATE; if ((ret = artnet_net_start(n))) return ret; n->state.mode = ARTNET_ON; if (n->state.reply_addr.s_addr == 0) { n->state.reply_addr = n->state.bcast_addr; } // build the initial reply if ((ret = artnet_tx_build_art_poll_reply(n))) return ret; if (n->state.node_type == ARTNET_SRV) { // poll the network if ((ret = artnet_tx_poll(n,NULL, ARTNET_TTM_AUTO))) return ret; if ((ret = artnet_tx_tod_request(n))) return ret; } else { // send a reply on startup if ((ret = artnet_tx_poll_reply(n, FALSE))) return ret; } return ret; } /* * Stops the ArtNet node. This closes the network sockets held by the node * @param vn the artnet_node * @return 0 on success, non-0 on failure */ int artnet_stop(artnet_node vn) { node n = (node) vn; check_nullnode(vn); if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; artnet_net_close(n->sd); n->state.mode = ARTNET_STANDBY; return ARTNET_EOK; } /* * Free the memory associated with this node */ int artnet_destroy(artnet_node vn) { node n = (node) vn; node_entry_private_t *ent, *tmp; int i; check_nullnode(vn); // free any memory associated with firmware transfers for (ent = n->node_list.first; ent != NULL; ent = tmp) { if (ent->firmware.data != NULL) free(ent->firmware.data); tmp = ent->next; free(ent); } for (i =0; i < ARTNET_MAX_PORTS; i++) { flush_tod(&n->ports.in[i].port_tod); flush_tod(&n->ports.out[i].port_tod); } free(vn); return ARTNET_EOK; } /* * Set the OEM code * This can only be done in the standby state */ int artnet_setoem(artnet_node vn, uint8_t hi, uint8_t lo) { node n = (node) vn; check_nullnode(vn); if (n->state.mode != ARTNET_STANDBY) return ARTNET_ESTATE; n->state.oem_hi = hi; n->state.oem_lo = lo; return ARTNET_EOK; } /* * Set the ESTA code * This can only be done in the standby state */ int artnet_setesta(artnet_node vn, char hi, char lo) { node n = (node) vn; check_nullnode(vn); if (n->state.mode != ARTNET_STANDBY) return ARTNET_ESTATE; n->state.esta_hi = hi; n->state.esta_lo = lo; return ARTNET_EOK; } /* * Set the number of nodes above which we start to bcast data * @param vn the artnet_node * @param limit 0 to always broadcast */ int artnet_set_bcast_limit(artnet_node vn, int limit) { node n = (node) vn; check_nullnode(vn); if (limit > MAX_NODE_BCAST_LIMIT) { artnet_error("attempt to set bcast limit > %d", MAX_NODE_BCAST_LIMIT); return ARTNET_EARG; } n->state.bcast_limit = limit; return ARTNET_EOK; } /* * Handle any received packets. * This function is the workhorse of libartnet. You have a couple of options: * - use artnet_get_sd() to retrieve the socket descriptors and select to * detect network activity. Then call artnet_read(node,0) * when activity is detected. * - call artnet_read repeatedly from within a loop with an appropriate * timeout * * @param vn the artnet_node * @param timeout the number of seconds to block for if nothing is pending * @return 0 on success, -1 on failure */ int artnet_read(artnet_node vn, int timeout) { node n = (node) vn; node tmp; artnet_packet_t p; int ret; check_nullnode(vn); if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; while (1) { memset(&p.data, 0x0, sizeof(p.data)); // check timeouts now, else this packet may update the timestamps check_timeouts(n); if ((ret = artnet_net_recv(n, &p, timeout)) < 0) return ret; // nothing to read if (ret == RECV_NO_DATA) break; // skip this packet (filtered) if (p.length == 0) continue; for (tmp = n->peering.peer; tmp != NULL && tmp != n; tmp = tmp->peering.peer) check_timeouts(tmp); if (p.length > MIN_PACKET_SIZE && get_type(&p)) { handle(n, &p); for (tmp = n->peering.peer; tmp != NULL && tmp != n; tmp = tmp->peering.peer) { handle(tmp, &p); } } } return ARTNET_EOK; } /* * To get around the 4 universes per node limitation , we can start more than * one node on different ip addresses - you'll need to add aliases to your * network interface something like: * * $ ifconfig eth0:1 10.0.0.10 netmask 255.255.255.0 * * Then the nodes must be joined so that they can share the socket * bound to the broadcast address. * TODO: use IP_PKTINFO so that packets are sent from the correct source ip * * @param vn1 The artnet node * @param vn2 The second artnet node * * @return 0 on sucess, non 0 on failure */ int artnet_join(artnet_node vn1, artnet_node vn2) { check_nullnode(vn1); check_nullnode(vn2); node n1 = (node) vn1; node n2 = (node) vn2; node tmp, n; if (n1->state.mode == ARTNET_ON || n2->state.mode == ARTNET_ON) { artnet_error("%s called after artnet_start", __FUNCTION__); return ARTNET_EACTION; } tmp = n1->peering.peer == NULL ? n1 : n1->peering.peer; n1->peering.peer = n2; for (n = n2; n->peering.peer != NULL && n->peering.peer != n2; n = n->peering.peer) ; n->peering.peer = tmp; // make sure there is only 1 master for (n = n1->peering.peer; n != n1; n = n->peering.peer) n->peering.master = FALSE; n1->peering.master = TRUE; return ARTNET_ESTATE; } /* * This is used to set handlers for sent/received artnet packets. * If you're using a stock standard node you more than likely don't want * to set these. See the artnet_set_dmx_callback and artnet_set_firmware_callback. * If you want to get down and dirty with artnet packets, you can set this * read / manipulate packets as they arrive (or get sent) * * @param vn The artnet_node * @param handler The handler to set * @param fh A pointer to a function, set to NULL to turn off * The function should return 0, * @param data Data to be passed to the handler when its called * @return 0 on sucess, non 0 on failure */ int artnet_set_handler(artnet_node vn, artnet_handler_name_t handler, int (*fh)(artnet_node n, void *pp, void * d), void *data) { node n = (node) vn; callback_t *callback; check_nullnode(vn); switch(handler) { case ARTNET_RECV_HANDLER: callback = &n->callbacks.recv; break; case ARTNET_SEND_HANDLER: callback = &n->callbacks.send; break; case ARTNET_POLL_HANDLER: callback = &n->callbacks.poll; break; case ARTNET_REPLY_HANDLER: callback = &n->callbacks.reply; break; case ARTNET_ADDRESS_HANDLER: callback = &n->callbacks.address; break; case ARTNET_INPUT_HANDLER: callback = &n->callbacks.input; break; case ARTNET_DMX_HANDLER: callback = &n->callbacks.dmx; break; case ARTNET_TOD_REQUEST_HANDLER: callback = &n->callbacks.todrequest; break; case ARTNET_TOD_DATA_HANDLER: callback = &n->callbacks.toddata; break; case ARTNET_TOD_CONTROL_HANDLER: callback = &n->callbacks.todcontrol; break; case ARTNET_RDM_HANDLER: callback = &n->callbacks.rdm; break; default: artnet_error("%s : Invalid handler defined", __FUNCTION__); return ARTNET_EARG; } callback->fh = fh; callback->data = data; return ARTNET_EOK; } /* * This is a special callback which is invoked when dmx data is received. * * @param vn The artnet_node * @param fh The callback to invoke (parameters passwd are the artnet_node, the port_id * that received the dmx, and some user data * @param data Data to be passed to the handler when its called */ int artnet_set_dmx_handler(artnet_node vn, int (*fh)(artnet_node n, int port, void *d), void *data) { node n = (node) vn; check_nullnode(vn); n->callbacks.dmx_c.fh = fh; n->callbacks.dmx_c.data = data; return ARTNET_EOK; } /* * This is a special callback which is invoked when a firmware upload is received. * * @param vn The artnet_node * @param fh The callback to invoke (parameters passwd are the artnet_node, a value which * is true if this was a ubea upload, and some user data * @param data Data to be passed to the handler when its called */ int artnet_set_firmware_handler( artnet_node vn, int (*fh)(artnet_node n, int ubea, uint16_t *data, int length, void *d), void *data) { node n = (node) vn; check_nullnode(vn); n->callbacks.firmware_c.fh = fh; n->callbacks.firmware_c.data = data; return ARTNET_EOK; } /* * @param vn The artnet_node * @param fh The callback to invoke (parameters passwd are the artnet_node, a value which * is true if this was a ubea upload, and some user data * @param data Data to be passed to the handler when its called */ int artnet_set_program_handler(artnet_node vn, int (*fh)(artnet_node n, void *d), void *data) { node n = (node) vn; check_nullnode(vn); n->callbacks.program_c.fh = fh; n->callbacks.program_c.data = data; return ARTNET_EOK; } /* * * @param vn The artnet_node * @param fh The callback to invoke (parameters passed are the artnet_node, pointer to the * rdm data, the length of the data and the user data * @param data Data to be passed to the handler when its called * */ int artnet_set_rdm_handler( artnet_node vn, int (*fh)(artnet_node n, int address, uint8_t *rdm, int length, void *d), void *data) { node n = (node) vn; check_nullnode(vn); n->callbacks.rdm_c.fh = fh; n->callbacks.rdm_c.data = data; return ARTNET_EOK; } int artnet_set_rdm_initiate_handler( artnet_node vn, int (*fh)(artnet_node n, int port, void *d), void *data) { node n = (node) vn; check_nullnode(vn); n->callbacks.rdm_init_c.fh = fh; n->callbacks.rdm_init_c.data = data; return ARTNET_EOK; } int artnet_set_rdm_tod_handler( artnet_node vn, int (*fh)(artnet_node n, int port, void *d), void *data) { node n = (node) vn; check_nullnode(vn); n->callbacks.rdm_tod_c.fh = fh; n->callbacks.rdm_tod_c.data = data; return ARTNET_EOK; } // sends a poll to the specified ip, or if null, will broadcast // talk_to_me - modify remote nodes behaviour, see spec // TODO - this should clear the node list - but this will cause issues if the caller holds references // to certain nodes /** * * @param vn the artnet_node * @param ip the ip address to send to, NULL will broadcast the ArtPoll * @param talk_to_me the value for the talk to me */ int artnet_send_poll(artnet_node vn, const char *ip, artnet_ttm_value_t talk_to_me) { node n = (node) vn; check_nullnode(vn); if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; if (n->state.node_type == ARTNET_SRV || n->state.node_type == ARTNET_RAW) { return artnet_tx_poll(n, ip, talk_to_me); } artnet_error("%s : Not sending poll, not a server or raw device", __FUNCTION__); return ARTNET_ESTATE; } /* * Sends an artpoll reply * * @param vn the artnet_node */ int artnet_send_poll_reply(artnet_node vn) { node n = (node) vn; check_nullnode(vn); if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; return artnet_tx_poll_reply(n, FALSE); } /* * Sends some dmx data * * @param vn the artnet_node */ int artnet_send_dmx(artnet_node vn, int port_id, int16_t length, const uint8_t *data) { node n = (node) vn; artnet_packet_t p; int ret; input_port_t *port; check_nullnode(vn); if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; if (port_id < 0 || port_id >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, port_id); return ARTNET_EARG; } port = &n->ports.in[port_id]; if (length < 1 || length > ARTNET_DMX_LENGTH) { artnet_error("%s : Length of dmx data out of bounds (%i < 1 || %i > ARTNET_MAX_DMX)", __FUNCTION__, length); return ARTNET_EARG; } if (port->port_status & PORT_STATUS_DISABLED_MASK) { artnet_error("%s : attempt to send on a disabled port (id:%i)", __FUNCTION__, port_id); return ARTNET_EARG; } // ok we're going to send now, make sure we turn the activity bit on port->port_status = port->port_status | PORT_STATUS_ACT_MASK; p.length = sizeof(artnet_dmx_t) - (ARTNET_DMX_LENGTH - length); // now build packet memcpy(&p.data.admx.id, ARTNET_STRING, ARTNET_STRING_SIZE); p.data.admx.opCode = htols(ARTNET_DMX); p.data.admx.verH = 0; p.data.admx.ver = ARTNET_VERSION; p.data.admx.sequence = port->seq; p.data.admx.physical = port_id; p.data.admx.universe = htols(port->port_addr); // set length p.data.admx.lengthHi = short_get_high_byte(length); p.data.admx.length = short_get_low_byte(length); memcpy(&p.data.admx.data, data, length); // default to bcast p.to.s_addr = n->state.bcast_addr.s_addr; if (n->state.bcast_limit == 0) { if ((ret = artnet_net_send(n, &p))) return ret; } else { // find the number of ports for this uni SI *ips = malloc(sizeof(SI) * n->state.bcast_limit); int nodes = find_nodes_from_uni(&n->node_list, port->port_addr, ips, n->state.bcast_limit); if (nodes > n->state.bcast_limit) { // fall back to broadcast free(ips); if ((ret = artnet_net_send(n, &p))) { return ret; } } else { // unicast to the specified nodes int i; for (i =0; i < nodes; i++) { p.to = ips[i]; artnet_net_send(n, &p); } free(ips); } } port->seq++; return ARTNET_EOK; } /* * Use for performance testing. * This allows data to be sent on any universe, not just the ones that have * ports configured. */ int artnet_raw_send_dmx(artnet_node vn, uint8_t uni, int16_t length, const uint8_t *data) { node n = (node) vn; artnet_packet_t p; check_nullnode(vn); if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; if (n->state.node_type != ARTNET_RAW) return ARTNET_ESTATE; if ( length < 1 || length > ARTNET_DMX_LENGTH) { artnet_error("%s : Length of dmx data out of bounds (%i < 1 || %i > ARTNET_MAX_DMX)", __FUNCTION__, length); return ARTNET_EARG; } // set dst addr and length p.to.s_addr = n->state.bcast_addr.s_addr; p.length = sizeof(artnet_dmx_t) - (ARTNET_DMX_LENGTH - length); // now build packet memcpy( &p.data.admx.id, ARTNET_STRING, ARTNET_STRING_SIZE); p.data.admx.opCode = htols(ARTNET_DMX); p.data.admx.verH = 0; p.data.admx.ver = ARTNET_VERSION; p.data.admx.sequence = 0; p.data.admx.physical = 0; p.data.admx.universe = uni; // set length p.data.admx.lengthHi = short_get_high_byte(length); p.data.admx.length = short_get_low_byte(length); memcpy(&p.data.admx.data, data, length); return artnet_net_send(n, &p); } int artnet_send_address(artnet_node vn, artnet_node_entry e, const char *shortName, const char *longName, uint8_t inAddr[ARTNET_MAX_PORTS], uint8_t outAddr[ARTNET_MAX_PORTS], uint8_t subAddr, artnet_port_command_t cmd) { node n = (node) vn; artnet_packet_t p; node_entry_private_t *ent = find_private_entry(n,e); check_nullnode(vn); if (e == NULL || ent == NULL) return ARTNET_EARG; if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; if (n->state.node_type == ARTNET_SRV || n->state.node_type == ARTNET_RAW) { p.to.s_addr = ent->ip.s_addr; p.length = sizeof(artnet_address_t); p.type = ARTNET_ADDRESS; // now build packet, copy the number of ports from the reply recieved from this node memcpy( &p.data.addr.id, ARTNET_STRING, ARTNET_STRING_SIZE); p.data.addr.opCode = htols(ARTNET_ADDRESS); p.data.addr.verH = 0; p.data.addr.ver = ARTNET_VERSION; p.data.addr.filler1 = 0; p.data.addr.filler2 = 0; strncpy((char*) &p.data.addr.shortname, shortName, ARTNET_SHORT_NAME_LENGTH); strncpy((char*) &p.data.addr.longname, longName, ARTNET_LONG_NAME_LENGTH); memcpy(&p.data.addr.swin, inAddr, ARTNET_MAX_PORTS); memcpy(&p.data.addr.swout, outAddr, ARTNET_MAX_PORTS); p.data.addr.subnet = subAddr; p.data.addr.swvideo = 0x00; p.data.addr.command = cmd; return artnet_net_send(n, &p); } return ARTNET_ESTATE; } /* * Sends an ArtInput packet to the specified node, this packet is used to * enable/disable the input ports on the remote node. * * 0x01 disable port * 0x00 enable port * * NOTE: should have enums here instead of uint8_t for settings * */ int artnet_send_input(artnet_node vn, artnet_node_entry e, uint8_t settings[ARTNET_MAX_PORTS]) { node n = (node) vn; artnet_packet_t p; node_entry_private_t *ent = find_private_entry(n,e); check_nullnode(vn); if (e == NULL) return ARTNET_EARG; if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; if (n->state.node_type == ARTNET_SRV || n->state.node_type == ARTNET_RAW) { // set dst, type and length p.to.s_addr = ent->ip.s_addr; p.length = sizeof(artnet_input_t); p.type = ARTNET_INPUT; // now build packet, copy the number of ports from the reply recieved from this node memcpy( &p.data.ainput.id, ARTNET_STRING, ARTNET_STRING_SIZE); p.data.ainput.opCode = htols(ARTNET_INPUT); p.data.ainput.verH = 0; p.data.ainput.ver = ARTNET_VERSION; p.data.ainput.filler1 = 0; p.data.ainput.filler2 = 0; p.data.ainput.numbportsH = short_get_high_byte(e->numbports); p.data.ainput.numbports = short_get_low_byte(e->numbports); memcpy(&p.data.ainput.input, &settings, ARTNET_MAX_PORTS); return artnet_net_send(n, &p); } return ARTNET_ESTATE; } /* * * Sends a series of ArtFirmwareMaster packets to the specified node, these are used to * upload firmware to the remote node. * * We send the first packet now, and another one every time we get a ArtFirmwareReply from * the node * * @param vn the artnet_node * @param e the node entry to send firmware to * @param ubea set to a true value if this is a ubea upload * @param data pointer to the firmware * @param length the number of 16bit words of the firmware (length * 2 = size in bytes) * @param fh the callback that is invoked when the transfer is complete * @param user_data data to be passed to the callback */ int artnet_send_firmware( artnet_node vn, artnet_node_entry e, int ubea, uint16_t *data, int length, int (*fh)(artnet_node n, artnet_firmware_status_code code, void *d), void *user_data) { node n = (node) vn; node_entry_private_t *ent = find_private_entry(n,e); int blen; check_nullnode(vn); if (e == NULL || ent == NULL) return ARTNET_EARG; if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; if (n->state.node_type == ARTNET_SRV || n->state.node_type == ARTNET_RAW) { // length in bytes blen = length * sizeof(uint16_t); // store the parameters for this transfer ent->firmware.data = malloc(blen); if ( ent->firmware.data == NULL) { artnet_error_malloc(); return ARTNET_EMEM; } ent->firmware.bytes_current = 0; ent->firmware.bytes_total = blen; ent->firmware.peer = ent->ip; ent->firmware.ubea = ubea; // entry->firmware.last_time set upon sending a packet // id of the current block ent->firmware.expected_block = 0; ent->firmware.callback = fh; ent->firmware.user_data = user_data; memcpy(ent->firmware.data, data, blen); return artnet_tx_firmware_packet(n, &ent->firmware); } return ARTNET_ESTATE; } /** * * Sends an ArtFirmwareReply packet to the specified node, * this packet is used to acknowledge firmware master packets. * * Note, you should never call this function directly, it is provided for * completness and will only work if the node type is ARTNET_RAW * * @param vn the artnet_node * @param e the node entry to send firmware to * @param code the status code to send */ int artnet_send_firmware_reply(artnet_node vn, artnet_node_entry e, artnet_firmware_status_code code) { node n = (node) vn; node_entry_private_t *ent = find_private_entry(n,e); check_nullnode(vn); if (e == NULL || ent == NULL) return ARTNET_EARG; if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; return artnet_tx_firmware_reply(n, ent->ip.s_addr, code); } /* * Sends a tod request * * @param vn the artnet node * */ int artnet_send_tod_request(artnet_node vn) { node n = (node) vn; check_nullnode(vn); return artnet_tx_tod_request(n); } /* * Sends a tod control datagram * * @param vn the artnet node * @param address the universe address to control * @param action the action to take * */ int artnet_send_tod_control(artnet_node vn, uint8_t address, artnet_tod_command_code action) { node n = (node) vn; check_nullnode(vn); return artnet_tx_tod_control(n, address, action); } /* * Send a tod data datagram * Note you should not use this, a TodData message should only be sent in response * to a request, or when a RDM device is added. Use artnet_add_rdm_device() or artnet_add_rdm_devices() instead * * @param */ int artnet_send_tod_data(artnet_node vn, int port) { node n = (node) vn; check_nullnode(vn); // should update the check for enabled port here if (port < 0 || port >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, port); return ARTNET_EARG; } return artnet_tx_tod_data(n, port); } /* * Send a rdm datagram * @param address the universe address to send to * @param data the rdm data to send * @param length the length of the rdm data */ int artnet_send_rdm(artnet_node vn, uint8_t address, uint8_t *data, int length) { node n = (node) vn; check_nullnode(vn); //we check that we are using this address return artnet_tx_rdm(n, address, data, length); } /* * Add a rdm device to our tod. * @param port the port the device is connected to * @param the uid of the device */ int artnet_add_rdm_device(artnet_node vn, int port, uint8_t uid[ARTNET_RDM_UID_WIDTH]) { node n = (node) vn; check_nullnode(vn); if (port < 0 || port >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, port); return ARTNET_EARG; } // add uid to tod for this port add_tod_uid(&n->ports.out[port].port_tod, uid); // notify everyone our tod changed return artnet_tx_tod_data(n, port); } /* * add a list of rdm devices to our tod, use this for full discovery * * @param port the port the device is connected to * @param uid pointer to the uids * @param count number of uids */ int artnet_add_rdm_devices(artnet_node vn, int port, uint8_t *uid, int count) { node n = (node) vn; int i; check_nullnode(vn); if (port < 0 || port >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, port); return ARTNET_EARG; } if (count < 0) return ARTNET_EARG; for (i = 0; i < count; i++) { // add uid to tod for this port add_tod_uid(&n->ports.out[port].port_tod, uid); uid += ARTNET_RDM_UID_WIDTH; } // notify everyone our tod changed return artnet_tx_tod_data(n, port); } /* * remove a rdm device to our tod. * * @param port the port the device was connected to * @param the uid of the device */ int artnet_remove_rdm_device(artnet_node vn, int port, uint8_t uid[ARTNET_RDM_UID_WIDTH]) { node n = (node) vn; check_nullnode(vn); if (port < 0 || port >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, port); return ARTNET_EARG; } // remove uid to tod for this port remove_tod_uid(&n->ports.out[port].port_tod, uid); // notify everyone our tod changed return artnet_tx_tod_data(n, port); } /* * Reads the latest dmx data * @param vn the artnet node * @param port_id the port to read data from * @param length * @return a pointer to the dmx data, NULL on error */ uint8_t *artnet_read_dmx(artnet_node vn, int port_id, int *length) { node n = (node) vn; if (n == NULL) return NULL; if (port_id < 0 || port_id >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, port_id); return NULL; } *length = n->ports.out[port_id].length; return &n->ports.out[port_id].data[0]; } //-------------------------------------- // Functions to change the node state (setters) // type : server, node, mserver, raw int artnet_set_node_type(artnet_node vn, artnet_node_type type) { node n = (node) vn; check_nullnode(vn); n->state.node_type = type; return ARTNET_EOK; } /** * Sets the artnet subnet address for this node. * The subnet address has nothing to do with IP addresses). An ArtNet subnet is a grouping of 16 DMX universes * (ie. ports) * * The subnet address is between 0 and 15. If the supplied address is larger than 15, the * lower 4 bits will be used in setting the address. * * It will have no effect if the node is under network control. * * Note that changing the subnet address will cause the universe addresses of all ports to change. * * @param vn the artnet_node * @param subnet new subnet address */ int artnet_set_subnet_addr(artnet_node vn, uint8_t subnet) { node n = (node) vn; int i, ret; check_nullnode(vn); n->state.default_subnet = subnet; // if not under network control, and the subnet is different from the current one if (!n->state.subnet_net_ctl && subnet != n->state.subnet) { n->state.subnet = subnet; // redo the addresses for each port for (i =0; i < ARTNET_MAX_PORTS; i++) { n->ports.in[i].port_addr = ((n->state.subnet & LOW_NIBBLE) << 4) | (n->ports.in[i].port_addr & LOW_NIBBLE); // reset dmx sequence number n->ports.in[i].seq = 0; n->ports.out[i].port_addr = ((n->state.subnet & LOW_NIBBLE) << 4) | (n->ports.out[i].port_addr & LOW_NIBBLE); } if (n->state.mode == ARTNET_ON) { if ((ret = artnet_tx_build_art_poll_reply(n))) return ret; return artnet_tx_poll_reply(n,FALSE); } } else if (n->state.subnet_net_ctl ) { // trying to change subnet addr while under network control n->state.report_code = ARTNET_RCUSERFAIL; } return ARTNET_EOK; } /** * Sets the short name of the node. * The string should be null terminated and a maxmium of 18 Characters will be used * * @param vn the artnet_node * @param name the short name of the node. */ int artnet_set_short_name(artnet_node vn, const char *name) { node n = (node) vn; check_nullnode(vn); strncpy((char *) &n->state.short_name, name, ARTNET_SHORT_NAME_LENGTH); n->state.short_name[ARTNET_SHORT_NAME_LENGTH-1] = 0x00; return artnet_tx_build_art_poll_reply(n); } /* * Sets the long name of the node. * The string should be null terminated and a maximium of 64 characters will be used * * @param vn the artnet_node * @param name the node's long name */ int artnet_set_long_name(artnet_node vn, const char *name) { node n = (node) vn; check_nullnode(vn); strncpy((char *) &n->state.long_name, name, ARTNET_LONG_NAME_LENGTH); n->state.long_name[ARTNET_LONG_NAME_LENGTH-1] = 0x00; return artnet_tx_build_art_poll_reply(n); } /* * Sets the direction and type of port * @param vn the artnet_node * @param id * @param direction * @param data */ int artnet_set_port_type(artnet_node vn, int port_id, artnet_port_settings_t settings, artnet_port_data_code data) { node n = (node) vn; check_nullnode(vn); if (port_id < 0 || port_id >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, port_id); return ARTNET_EARG; } n->ports.types[port_id] = settings | data; return ARTNET_EOK; } /* * Sets the port address of the port. * * Just to set some terms straight: * - subnet address, is 4 bits, set on a per-node basis * - port address, 4 bits, set on a per-port basis * - universe address, 8 bits derrived from the subnet and port addresses, specific (but may * not be unique) to a port. * * The upper four bits of the universe address are from the subnet address, while the lower * four are from the port address. * * So for example, if the subnet address of the node is 0x03, and the port address is * 0x02, the universe address for the port will be 0x32. * * As the port address is between 0 and 15, only the lower 4 bits of the addr argument * will be used. * * The operation may have no affect if the port is under network control. * * @param vn the artnet_node * @param id the phyiscal port number (from 0 to ARTNET_MAX_PORTS-1 ) * @param dir either ARTNET_INPUT_PORT or ARTNET_OUTPUT_PORT * @param addr the new port address */ int artnet_set_port_addr(artnet_node vn, int id, artnet_port_dir_t dir, uint8_t addr) { node n = (node) vn; int ret; int changed = 0; g_port_t *port; check_nullnode(vn); if (id < 0 || id >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, id); return ARTNET_EARG; } if (addr > 16) { artnet_error("%s : Attempt to set port %i to invalid address %#hhx\n", __FUNCTION__, id, addr); return ARTNET_EARG; } if (dir == ARTNET_INPUT_PORT) { port = &n->ports.in[id].port; changed = n->ports.in[id].port_enabled?0:1; n->ports.in[id].port_enabled = TRUE; } else if (dir == ARTNET_OUTPUT_PORT) { port = &n->ports.out[id].port; changed = n->ports.out[id].port_enabled?0:1; n->ports.out[id].port_enabled = TRUE; } else { artnet_error("%s : Invalid port direction\n", __FUNCTION__); return ARTNET_EARG; } port->default_addr = addr; // if not under network control and address is changing if (!port->net_ctl && (changed || (addr & LOW_NIBBLE) != (port->addr & LOW_NIBBLE))) { port->addr = ((n->state.subnet & LOW_NIBBLE) << 4) | (addr & LOW_NIBBLE); // reset seq if input port if (dir == ARTNET_INPUT_PORT) n->ports.in[id].seq = 0; if (n->state.mode == ARTNET_ON) { if ((ret = artnet_tx_build_art_poll_reply(n))) return ret; return artnet_tx_poll_reply(n,FALSE); } } else if (port->net_ctl) { // trying to change port addr while under network control n->state.report_code = ARTNET_RCUSERFAIL; } return ARTNET_EOK; } /* * Returns the universe address of this port * * @param vn the artnet_node * @param id the phyiscal port number (from 0 to ARTNET_MAX_PORTS-1 ) * @param dir either ARTNET_INPUT_PORT or ARTNET_OUTPUT_PO * * @return the universe address, or < 0 on error */ int artnet_get_universe_addr(artnet_node vn, int id, artnet_port_dir_t dir) { node n = (node) vn; check_nullnode(vn); if (id < 0 || id >= ARTNET_MAX_PORTS) { artnet_error("%s : port index out of bounds (%i < 0 || %i > ARTNET_MAX_PORTS)", __FUNCTION__, id); return ARTNET_EARG; } if (dir == ARTNET_INPUT_PORT) return n->ports.in[id].port.addr; else if (dir == ARTNET_OUTPUT_PORT) return n->ports.out[id].port.addr; else { artnet_error("%s : Invalid port direction\n", __FUNCTION__); return ARTNET_EARG; } } int artnet_get_config(artnet_node vn, artnet_node_config_t *config) { int i; node n = (node) vn; check_nullnode(vn); strncpy(config->short_name, n->state.short_name, ARTNET_SHORT_NAME_LENGTH); strncpy(config->long_name, n->state.long_name, ARTNET_LONG_NAME_LENGTH); config->subnet = n->state.subnet; for (i = 0; i < ARTNET_MAX_PORTS; i++) { config->in_ports[i] = n->ports.in[i].port.addr & LOW_NIBBLE; config->out_ports[i] = n->ports.out[i].port.addr & LOW_NIBBLE; } return ARTNET_EOK; } /* * Dumps the node config to stdout. * * @param vn the artnet_node */ int artnet_dump_config(artnet_node vn) { node n = (node) vn; check_nullnode(vn); printf("#### NODE CONFIG ####\n"); printf("Node Type: %i\n", n->state.node_type); printf("Short Name: %s\n", n->state.short_name); printf("Long Name: %s\n", n->state.long_name); printf("Subnet: %#hx\n", n->state.subnet); printf("Default Subnet: %#hx\n", n->state.default_subnet); printf("Net Ctl: %i\n", n->state.subnet_net_ctl); printf("#####################\n"); return ARTNET_EOK; } /* * Returns the socket descriptor associated with this artnet_node. * libartnet currently uses two descriptors per node, one bound * to the network address and one bound to the subnet broadcast address * * @param vn the artnet_node * @param socket the index of the socket descriptor to fetch (0 or 1) * @return the socket descriptor */ int artnet_get_sd(artnet_node vn) { node n = (node) vn; check_nullnode(vn); if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; return n->sd; } /** * Sets the file descriptors in the fdset that we are interested in. * * @param vn the artnet_node * @param fdset pointer to the fdset to change * @return the maxfd+1 */ int artnet_set_fdset(artnet_node vn, fd_set *fdset) { node n = (node) vn; check_nullnode(vn); if (!fdset) return ARTNET_EARG; if (n->state.mode != ARTNET_ON) return ARTNET_EACTION; return artnet_net_set_fdset(n, fdset); } /** * Returns the artnet_node_list. * The artnet_node_list holds artnet_node_entry(s) that represent the discovered * remote nodes on the network * NOTE: this function is not THREAD SAFE * * @param vn the artnet_node * @return the artnet_node_list */ artnet_node_list artnet_get_nl(artnet_node vn) { node n = (node) vn; if (!vn) return NULL; return &n->node_list; } /** * Repositions the pointer to the first entry in the artnet_node_list * NOTE: this function is not THREAD SAFE * * @param vnl the artnet_node_list * @return the first artnet_node_entry in the list, or NULL if the list is empty */ artnet_node_entry artnet_nl_first(artnet_node_list vnl) { node_list_t *nl = (node_list_t*) vnl; if (!nl) return NULL; nl->current = nl->first; return &nl->current->pub; } /** * Moves the pointer to the next element in the artnet_node_list * NOTE: this function is not THREAD SAFE * * @param vnl the artnet_node_list * @return the next artnet_node_entry, or NULL if the end of the list is reached */ artnet_node_entry artnet_nl_next(artnet_node_list vnl) { node_list_t *nl = (node_list_t*) vnl; if (!nl) return NULL; nl->current = nl->current->next; return &nl->current->pub; } /* * Returns the length of the artnet_node_list * NOTE: this function is not THREAD SAFE * * @param vnl the artnet_node_list * @return the length of the list */ int artnet_nl_get_length(artnet_node_list vnl) { node_list_t *nl = (node_list_t*) vnl; if (!nl) return 0; return nl->length; } /* * Return a pointer to the staticly allocated error string */ char *artnet_strerror() { return artnet_errstr; } //----------------------------------------------------------------------------- // Private functions follow //----------------------------------------------------------------------------- int artnet_nl_update(node_list_t *nl, artnet_packet reply) { node_entry_private_t *entry; entry = find_entry_from_ip(nl, reply->from); if (!entry) { // add to list entry = (node_entry_private_t*) malloc(sizeof(node_entry_private_t)); if (!entry) { artnet_error_malloc(); return ARTNET_EMEM; } memset(entry, 0x00, sizeof(node_entry_private_t)); copy_apr_to_node_entry(&entry->pub, &reply->data.ar); entry->ip = reply->from; entry->next = NULL; if (!nl->first) { nl->first = entry; nl->last = entry; } else { nl->last->next = entry; nl->last = entry; } nl->length++; } else { // update entry copy_apr_to_node_entry(&entry->pub, &reply->data.ar); } return ARTNET_EOK; } /* * check if this packet is in list */ node_entry_private_t *find_entry_from_ip(node_list_t *nl, SI ip) { node_entry_private_t *tmp; for (tmp = nl->first; tmp; tmp = tmp->next) { if (ip.s_addr == tmp->ip.s_addr) break; } return tmp; } /* * Find all nodes with a port bound to a particular universe * @param nl the node list * @param uni the universe to search for * @param ips store matching node ips here * @param size size of ips * @return number of nodes matched */ int find_nodes_from_uni(node_list_t *nl, uint8_t uni, SI *ips, int size) { node_entry_private_t *tmp; int count = 0; int i,j = 0; for (tmp = nl->first; tmp; tmp = tmp->next) { int added = FALSE; for (i =0; i < tmp->pub.numbports; i++) { if (tmp->pub.swout[i] == uni && ips) { if (j < size && !added) { ips[j++] = tmp->ip; added = TRUE; } count++; } } } return count; } /* * Add a node to the node list from an ArtPollReply msg */ void copy_apr_to_node_entry(artnet_node_entry e, artnet_reply_t *reply) { // the ip is network byte ordered memcpy(&e->ip, &reply->ip, 4); e->ver = bytes_to_short(reply->verH, reply->ver); e->sub = bytes_to_short(reply->subH, reply->sub); e->oem = bytes_to_short(reply->oemH, reply->oem); e->ubea = reply->ubea; memcpy(&e->etsaman, &reply->etsaman, 2); memcpy(&e->shortname, &reply->shortname, sizeof(e->shortname)); memcpy(&e->longname, &reply->longname, sizeof(e->longname)); memcpy(&e->nodereport, &reply->nodereport, sizeof(e->nodereport)); e->numbports = bytes_to_short(reply->numbportsH, reply->numbports); memcpy(&e->porttypes, &reply->porttypes, ARTNET_MAX_PORTS); memcpy(&e->goodinput, &reply->goodinput, ARTNET_MAX_PORTS); memcpy(&e->goodinput, &reply->goodinput, ARTNET_MAX_PORTS); memcpy(&e->goodoutput, &reply->goodoutput, ARTNET_MAX_PORTS); memcpy(&e->swin, &reply->swin, ARTNET_MAX_PORTS); memcpy(&e->swout, &reply->swout, ARTNET_MAX_PORTS); e->swvideo = reply->swvideo; e->swmacro = reply->swmacro; e->swremote = reply->swremote; e->style = reply->style; memcpy(&e->mac, &reply->mac, ARTNET_MAC_SIZE); } /* * find a node_entry in the node list */ node_entry_private_t *find_private_entry(node n, artnet_node_entry e) { if (!e) return NULL; node_entry_private_t *tmp; // check if this packet is in list for(tmp = n->node_list.first; tmp; tmp = tmp->next) { if (!memcmp(&e->ip, &tmp->pub.ip, 4)) break; } return tmp; } void check_timeouts(node n) { time_t now = time(NULL); if (n->firmware.peer.s_addr != 0 && (now - n->firmware.last_time >= FIRMWARE_TIMEOUT_SECONDS)) { printf("firmware timeout\n"); reset_firmware_upload(n); n->state.report_code = ARTNET_RCFIRMWAREFAIL; // spec says to set ArtPollReply->Status here, but don't know to what value } }