RF22
IPGateway.pde

Sketch to provide an IP gateway for a set of RF22 radios (Datagram, ReliableDatagram, Router or Mesh) Routes UDP messages from an internet connection using an Ethernet Shield and sends them to a radio whose ID is based on the UDP port. Replies are sent back to the originating UDP address and port

// IPGateway.ino
// -*- mode: C++ -*-
//
// This example Arduino sketch shows how to implement an RF22-IP gateway.
//
// UDP packets sent to this IP address will be sent to an RF22 radio. The radio will be selected
// based on the destination UDP port.
// eg UDP messages to port 2 will be sent to the RF22 radio with address 2. 
// The payload of the UDP message will be sent as data to the radio.
//
// If a message is sent from another radio to this one, it will be relayed to the internet.
// The destination IP address and port for the messages are deduced from the senders node ID.
// If a message was previouslt releayed from the internet to that id, then the reply wil be sent to the 
// senders IP address and port.
// If there has been no recent mesasge sent to that node, the radio message will be relayed to the 
// defaultReplyAddress and defaultReplyPort defined below
// The radio message contents are sent as the UDP payload.
//
// Test this with the example sketch rf22_datagram_server:
// Send  UDP mesage to this IP address, with UDP port 2. The mesage will be sent to the rf22_datagram_server.
// Its reply will be relayed back to the original IP address and port.
// For example, using sendUDP.pl
// # perl sendUDP.pl -s 203.63.154.101 -p 2 -w hello
// And hello back to you
//
// It requires hardware
//   RF22 radio
//   Arduino Uno + Ethernet Shield
//      or...
//   Arduino EtherTen
//
// Libraries required are:
//   Ethernet (included in Arduino 1.0)
//   EtherRaw from http://www.open.com.au/mikem/arduino/EtherRaw
//   RF22 from http://www.open.com.au/mikem/arduino/RF22
//
// This code will NOT work with old EtherShields such as this one:
//    http://www.dfrobot.com/index.php?route=product/product&product_id=52
// because the old ones do not play nicely with other SPI devices (such as the RF22): they do not disable their SPI pins 
// when not slave selected.
// Later EtherShields (such as the 05 and 06 models) are fine.
// EtherTen is also fine
//
// Also, you MUST connect the RF22 in a slightly different way to the RF22 library documentation.
// The RF22 Slave Select pin must be connected to Arduino pin 9 
// (not the usual pin 10: 10 is used as slave select by the Ethernet library for the ethernet w5100 chip)
//
// Caution: reply speed is usually dominated by the on-the-air radio speed. With FSK_Rb2_4Fd36 modulation
// and messages of 7 and 22 octets, the send-reply response time to another radio is about 180ms
//
// Tested with EtherTen, RF22 1.19, EtherRaw 1.0, arduino 1.0
//
// Copyright (C) 2012 Mike McCauley

#include <SPI.h>
#include <Ethernet.h>
#include <utility/socket.h>
#include <EtherRaw.h>
#include <RF22Datagram.h>
#include <RF22.h>

#define THIS_ADDRESS 1

// Singleton instance of the radio. You can use RF22Datagram, RF22ReliableDatagram
// or whatever, according to whatever the other radios support
RF22Datagram rf22(THIS_ADDRESS, 9); // Need different SS pin so dont conflict with EtherShield

// Enter a MAC address and IP address for this gateway below.
// The IP address will be dependent on your local network:
MACAddress mac(0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED);
IPAddress ip(203, 63, 154, 101);

// If a radio message is received and ther eis no previously recorded sou8rce adress, send the message to 
// to this default address
IPv4Address defaultReplyAddress(203, 63, 154, 29);
uint16_t defaultReplyPort = 9048;

uint8_t macraw_socket = 0;
uint8_t udp_reply_socket = 1;

void setup()
{
  delay(100); // EtherTen likes a delay like this so the w5100 starts OK
  
  // start the Ethernet chip. You can also specifiy the internet gateway address etc here, if you want
  Ethernet.begin(mac.address(), ip);  
  Serial.begin(115200);
  
  // Create a MACRAW socket to listen for incoming UDP packets
  // Cant use ordinary UDP socket, since they can only listen to one port at a time.
  socket(macraw_socket, SnMR::MACRAW, 0, 0);

  if (!rf22.init())
    Serial.println("RF22 init failed");
  // Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36
}

// recvfrom in socket.cpp is guilty of buffer overflows
// This version is specifically for MACRAW
// and will only fill buf up to max of len octets
// returns the amount actually copied to buf
uint16_t macraw_read(SOCKET s, uint8_t *buf, uint16_t len)
{
  uint8_t head[2];
  uint16_t data_len=0;
  uint16_t ptr=0;

  if (!W5100.getRXReceivedSize(s))
    return 0;
  ptr = W5100.readSnRX_RD(s);
  W5100.read_data(s, (uint8_t*)ptr, head, 2);
  ptr+=2;
  data_len = head[0];
  data_len = (data_len<<8) + head[1] - 2;
  // Only copy as much as will fil the buffer
  if (len > data_len)
    len = data_len;
  W5100.read_data(s, (uint8_t*)ptr, buf, len);
  ptr += data_len;
  W5100.writeSnRX_RD(s, ptr);
  W5100.execCmdSn(s, Sock_RECV);
  return len;
}

// Structure for recording where requests come from and are sent to
typedef struct Source
{
  IPv4Address  source;
  uint16_t    sourcePort;
  uint16_t    destPort;
} Source;

#define NUM_SOURCE_RECORDS 4
Source sources[NUM_SOURCE_RECORDS];
uint8_t next_source_record = 0;

struct Source* findSourceRecord(uint16_t destPort)
{
  uint8_t i;
  
  for (i =0; i < NUM_SOURCE_RECORDS; i++)
  {
    if (sources[i].destPort == destPort)
      return &sources[i];
  }
  return NULL;
}

// Record the most recent sender to the dest port
void recordSource(const IPv4Address& source, uint16_t sourcePort, uint16_t destPort)
{
  Source* s = findSourceRecord(destPort);
  
  if (!s)
  {
    s = &sources[next_source_record];
    next_source_record = (next_source_record + 1) % NUM_SOURCE_RECORDS;
  }
  
  s->source = source;
  s->sourcePort = sourcePort;
  s->destPort = destPort;

}

void loop()
{
  uint16_t len;
  uint8_t buf[200];

  // First see if anyone sent a UDP message for transmit to a radio node
  // Disable interrupts while we poll the w5100
  // else if an RF22 interrupt occurs, the ISR may not be able to contact the RF22 due to the state of the
  // SPI interface at the time 
  cli();
  len = macraw_read(macraw_socket, buf, sizeof(buf));
  sei();
  
  if (len)
  {
    EthernetHeader* h = (EthernetHeader*)buf;
      
//    h->printTo(Serial);
    // Is it for our MAC? Ignore broadcasts
    if (!(h->dest() == mac))
      return;
    // Is it IPv4?
    if (h->ethertype() != EthernetHeader::ETH_P_IP)
      return;
    PacketIPv4* p = (PacketIPv4*)h->payload();  
    // Is it really IPv4?
    if (p->version() != IPVERSION)
      return;
    // Is it addressed to our IP address?
    if (!(ip == p->dest().address()))
      return;
      
//    p->printTo(Serial);
    // Is it UDP?
    if (p->protocol() != PacketIPv4::IPPROTO_UDP)
      return;
    // Get UDP data
    PacketUDP* u = (PacketUDP*)p->payload();
//    u->printTo(Serial);
//    RF22::printBuffer("payload:", buf, len);
    
    // Only send if its for another radio
    // REVISIT: if the message was for this radio, could implement command to configure the radio
    if (u->destPort() != THIS_ADDRESS)
    {
 //     Serial.println((const char*)u->payload());
       recordSource(p->source(), u->sourcePort(), u->destPort());
      if (rf22.sendto(u->payload(), u->payload_length(), u->destPort()))
        rf22.waitPacketSent();
    }
  }

  // Now see if anyone has sent us a message
  // And send it out on the internet
  uint8_t recvlen = sizeof(buf);
  uint8_t from;
  uint8_t to;      
  if (rf22.recvfrom(buf, &recvlen, &from, &to))
  {
 //   Serial.print("got message from : 0x");
 //   Serial.print(from, HEX);
 //   Serial.print(": ");
 //   Serial.println((char*)buf);
    
    // Forward the payload to the internet
    cli();
    socket(udp_reply_socket, SnMR::UDP, from, 0);
    // If we have recorded a source address/port for this radio, use it
    // Else use the default address/port
    Source* s = findSourceRecord(from);
    if (s)
      sendto(udp_reply_socket, buf, recvlen, (uint8_t*)s->source.address(), s->sourcePort);
    else
       sendto(udp_reply_socket, buf, recvlen, (uint8_t*)defaultReplyAddress.address(), defaultReplyPort);
    // We have to close this socket again, otherwise it will take priority over the MACRAW socket and MACRAW wont 
    // receive UDP requests for this port again.
    close(udp_reply_socket);
    sei();
  }
}