syndilights/open-lighting-architecture/libartnet-1.1.0/artnet/artnet.c

1637 lines
43 KiB
C

/*
* 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
}
}