syndilights/open-lighting-architecture/ola-0.8.4/plugins/espnet/EspNetNode.cpp

449 lines
11 KiB
C++

/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* EspNetNode.cpp
* A EspNet node
* Copyright (C) 2005-2009 Simon Newton
*/
#include <string.h>
#include <algorithm>
#include <map>
#include <string>
#include "ola/Logging.h"
#include "ola/network/NetworkUtils.h"
#include "ola/network/InterfacePicker.h"
#include "plugins/espnet/EspNetNode.h"
namespace ola {
namespace plugin {
namespace espnet {
using std::string;
using std::map;
using ola::network::UdpSocket;
using ola::network::HostToNetwork;
using ola::network::NetworkToHost;
using ola::Closure;
const char EspNetNode::NODE_NAME[] = "OLA Node";
/*
* Create a new node
* @param ip_address the IP address to prefer to listen on, if NULL we choose
* one.
*/
EspNetNode::EspNetNode(const string &ip_address)
: m_running(false),
m_options(DEFAULT_OPTIONS),
m_tos(DEFAULT_TOS),
m_ttl(DEFAULT_TTL),
m_universe(0),
m_type(ESPNET_NODE_TYPE_IO),
m_node_name(NODE_NAME),
m_preferred_ip(ip_address) {
}
/*
* Cleanup
*/
EspNetNode::~EspNetNode() {
Stop();
std::map<uint8_t, universe_handler>::iterator iter;
for (iter = m_handlers.begin(); iter != m_handlers.end(); ++iter) {
delete iter->second.closure;
}
m_handlers.clear();
}
/*
* Start this node
*/
bool EspNetNode::Start() {
if (m_running)
return false;
ola::network::InterfacePicker *picker =
ola::network::InterfacePicker::NewPicker();
if (!picker->ChooseInterface(&m_interface, m_preferred_ip)) {
OLA_INFO << "Failed to find an interface";
delete picker;
return false;
}
delete picker;
if (!InitNetwork())
return false;
m_running = true;
return true;
}
/*
* Stop this node
*/
bool EspNetNode::Stop() {
if (!m_running)
return false;
m_running = false;
return true;
}
/*
* Called when there is data on this socket
*/
void EspNetNode::SocketReady() {
espnet_packet_union_t packet;
memset(&packet, 0, sizeof(packet));
struct sockaddr_in source;
socklen_t source_length = sizeof(source);
ssize_t packet_size = sizeof(packet);
if (!m_socket.RecvFrom(reinterpret_cast<uint8_t*>(&packet),
&packet_size,
source,
source_length))
return;
if (packet_size < (ssize_t) sizeof(packet.poll.head)) {
OLA_WARN << "Small espnet packet received, discarding";
return;
}
// skip packets sent by us
if (source.sin_addr.s_addr == m_interface.ip_address.s_addr) {
return;
}
switch (NetworkToHost(packet.poll.head)) {
case ESPNET_POLL:
HandlePoll(packet.poll, packet_size, source.sin_addr);
break;
case ESPNET_REPLY:
HandleReply(packet.reply, packet_size, source.sin_addr);
break;
case ESPNET_DMX:
HandleData(packet.dmx, packet_size, source.sin_addr);
break;
case ESPNET_ACK:
HandleAck(packet.ack, packet_size, source.sin_addr);
break;
default:
OLA_INFO << "Skipping a packet with invalid header" << packet.poll.head;
}
}
/*
* Set the closure to be called when we receive data for this universe.
* @param universe the universe to register the handler for
* @param handler the Closure to call when there is data for this universe.
* Ownership of the closure is transferred to the node.
*/
bool EspNetNode::SetHandler(uint8_t universe,
DmxBuffer *buffer,
Closure<void> *closure) {
if (!closure)
return false;
map<uint8_t, universe_handler>::iterator iter =
m_handlers.find(universe);
if (iter == m_handlers.end()) {
universe_handler handler;
handler.buffer = buffer;
handler.closure = closure;
m_handlers[universe] = handler;
} else {
Closure<void> *old_closure = iter->second.closure;
iter->second.closure = closure;
delete old_closure;
}
return true;
}
/*
* Remove the handler for this universe
* @param universe the universe handler to remove
* @param true if removed, false if it didn't exist
*/
bool EspNetNode::RemoveHandler(uint8_t universe) {
map<uint8_t, universe_handler>::iterator iter =
m_handlers.find(universe);
if (iter != m_handlers.end()) {
Closure<void> *old_closure = iter->second.closure;
m_handlers.erase(iter);
delete old_closure;
return true;
}
return false;
}
/*
* Send an Esp Poll
* @param full_poll
*/
bool EspNetNode::SendPoll(bool full_poll) {
if (!m_running)
return false;
return SendEspPoll(m_interface.bcast_address, full_poll);
}
/*
* Send some DMX data
* @param universe the id of the universe to send
* @param buffer the DMX data
* @return true if it was send successfully, false otherwise
*/
bool EspNetNode::SendDMX(uint8_t universe, const ola::DmxBuffer &buffer) {
if (!m_running)
return false;
return SendEspData(m_interface.bcast_address, universe, buffer);
}
/*
* Setup the networking compoents.
*/
bool EspNetNode::InitNetwork() {
if (!m_socket.Init()) {
OLA_WARN << "Socket init failed";
return false;
}
if (!m_socket.Bind(ESPNET_PORT)) {
OLA_WARN << "Failed to bind to:" << ESPNET_PORT;
return false;
}
if (!m_socket.EnableBroadcast()) {
OLA_WARN << "Failed to enable broadcasting";
return false;
}
m_socket.SetOnData(NewClosure(this, &EspNetNode::SocketReady));
return true;
}
/*
* Handle an Esp Poll packet
*/
void EspNetNode::HandlePoll(const espnet_poll_t &poll,
ssize_t length,
const struct in_addr &source) {
OLA_DEBUG << "Got ESP Poll " << poll.type;
if (length < (ssize_t) sizeof(espnet_poll_t)) {
OLA_DEBUG << "Poll size too small " << length << " < " <<
sizeof(espnet_poll_t);
return;
}
if (poll.type)
SendEspPollReply(source);
else
SendEspAck(source, 0, 0);
}
/*
* Handle an Esp reply packet
*/
void EspNetNode::HandleReply(const espnet_poll_reply_t &reply,
ssize_t length,
const struct in_addr &source) {
if (length < (ssize_t) sizeof(espnet_poll_reply_t)) {
OLA_DEBUG << "Poll reply size too small " << length << " < " <<
sizeof(espnet_poll_reply_t);
return;
}
// TODO(simon): Call a handler here
(void) reply;
(void) source;
}
/*
* Handle a Esp Ack packet
*/
void EspNetNode::HandleAck(const espnet_ack_t &ack,
ssize_t length,
const struct in_addr &source) {
if (length < (ssize_t) sizeof(espnet_ack_t)) {
OLA_DEBUG << "Ack size too small " << length << " < " <<
sizeof(espnet_ack_t);
return;
}
(void) ack;
(void) source;
}
/*
* Handle an Esp data packet
*/
void EspNetNode::HandleData(const espnet_data_t &data,
ssize_t length,
const struct in_addr &source) {
static const ssize_t header_size = sizeof(espnet_data_t) - DMX_UNIVERSE_SIZE;
if (length < header_size) {
OLA_DEBUG << "Data size too small " << length << " < " << header_size;
return;
}
map<uint8_t, universe_handler>::iterator iter =
m_handlers.find(data.universe);
if (iter == m_handlers.end()) {
OLA_DEBUG << "Not interested in universe " <<
static_cast<int>(data.universe) << ", skipping ";
return;
}
ssize_t data_size = std::min(length - header_size,
(ssize_t) NetworkToHost(data.size));
// we ignore the start code
switch (data.type) {
case DATA_RAW:
iter->second.buffer->Set(data.data, data_size);
break;
case DATA_PAIRS:
OLA_WARN << "espnet data pairs aren't supported";
return;
case DATA_RLE:
m_decoder.Decode(iter->second.buffer, data.data, data_size);
break;
default:
OLA_WARN << "unknown espnet data type " << data.type;
return;
}
iter->second.closure->Run();
(void) source;
}
/*
* Send an EspNet poll
*/
bool EspNetNode::SendEspPoll(const struct in_addr &dst, bool full) {
espnet_packet_union_t packet;
packet.poll.head = HostToNetwork((uint32_t) ESPNET_POLL);
packet.poll.type = full;
return SendPacket(dst, packet, sizeof(packet.poll));
}
/*
* Send an EspNet Ack
*/
bool EspNetNode::SendEspAck(const struct in_addr &dst,
uint8_t status,
uint8_t crc) {
espnet_packet_union_t packet;
packet.ack.head = HostToNetwork((uint32_t) ESPNET_ACK);
packet.ack.status = status;
packet.ack.crc = crc;
return SendPacket(dst, packet, sizeof(packet.ack));
}
/*
* Send an EspNet Poll Reply
*/
bool EspNetNode::SendEspPollReply(const struct in_addr &dst) {
espnet_packet_union_t packet;
packet.reply.head = HostToNetwork((uint32_t) ESPNET_REPLY);
memcpy(packet.reply.mac, m_interface.hw_address, ola::network::MAC_LENGTH);
packet.reply.type = HostToNetwork((uint32_t) m_type);
packet.reply.version = FIRMWARE_VERSION;
packet.reply.sw = SWITCH_SETTINGS;
memcpy(packet.reply.name, m_node_name.data(), ESPNET_NAME_LENGTH);
packet.reply.name[ESPNET_NAME_LENGTH - 1] = 0;
packet.reply.option = m_options;
packet.reply.option = 0x01;
packet.reply.tos = m_tos;
packet.reply.ttl = m_ttl;
packet.reply.config.listen = 0x04;
memcpy(&packet.reply.config.ip, &m_interface.ip_address.s_addr,
sizeof(packet.reply.config.ip));
packet.reply.config.universe = m_universe;
return SendPacket(dst, packet, sizeof(packet.reply));
}
/*
* Send an EspNet data packet
*/
bool EspNetNode::SendEspData(const struct in_addr &dst,
uint8_t universe,
const DmxBuffer &buffer) {
espnet_packet_union_t packet;
memset(&packet.dmx, 0, sizeof(packet.dmx));
packet.dmx.head = HostToNetwork((uint32_t) ESPNET_DMX);
packet.dmx.universe = universe;
packet.dmx.start = START_CODE;
packet.dmx.type = DATA_RAW;
unsigned int size = DMX_UNIVERSE_SIZE;
buffer.Get(packet.dmx.data, &size);
packet.dmx.size = HostToNetwork((uint16_t) size);
return SendPacket(dst, packet, sizeof(packet.dmx));
}
/*
* Send an EspNet packet
*/
bool EspNetNode::SendPacket(const struct in_addr &dst,
const espnet_packet_union_t &packet,
unsigned int size) {
struct sockaddr_in m_destination;
memset(&m_destination, 0, sizeof(m_destination));
m_destination.sin_family = AF_INET;
m_destination.sin_port = HostToNetwork((uint16_t) ESPNET_PORT);
m_destination.sin_addr = dst;
ssize_t bytes_sent = m_socket.SendTo(
reinterpret_cast<const uint8_t*>(&packet),
size,
m_destination);
if (bytes_sent != (ssize_t) size) {
OLA_WARN << "Only sent " << bytes_sent << " of " << size;
return false;
}
return true;
}
} // espnet
} // plugin
} // ola