// UdpBroadcast - send and receive real data across the Thread mesh.
//
// Flash this same sketch to two or more boards. Once attached, every board
// multicasts a numbered hello over UDP every 3 seconds and prints whatever
// it receives from the others:
//
//   role -> leader
//   TX  "hello #1 from 0x2C00"
//   RX  from fdde:...:5d31  "hello #1 from 0x2C01"
//
// UDP runs through the official OpenThread API (Thread.instance()): open a
// socket, bind a port, send to the realm-local all-nodes multicast address
// ff03::1 - every node in the mesh receives it.

#include <NiusThread.h>

#include <stdio.h>   // snprintf
#include <string.h>  // strlen, memset

extern "C" {
#include <openthread/udp.h>
#include <openthread/ip6.h>
#include <openthread/message.h>
}

static const uint16_t kUdpPort = 12345;

static const uint8_t kNetworkKey[16] = {
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
};

static otUdpSocket sSocket;

// Called by OpenThread (from Thread.process()) for every received datagram.
static void handleUdp(void *aContext, otMessage *aMessage, const otMessageInfo *aInfo) {
  (void)aContext;

  char payload[64];
  uint16_t len = otMessageRead(aMessage, otMessageGetOffset(aMessage),
                               payload, sizeof(payload) - 1);
  payload[len] = '\0';

  char from[OT_IP6_ADDRESS_STRING_SIZE];
  otIp6AddressToString(&aInfo->mPeerAddr, from, sizeof(from));

  Serial.print("RX  from ");
  Serial.print(from);
  Serial.print("  \"");
  Serial.print(payload);
  Serial.println("\"");
}

static void sendHello() {
  static uint32_t counter = 0;

  otInstance *ot = Thread.instance();

  char text[48];
  snprintf(text, sizeof(text), "hello #%lu from 0x%04X",
           (unsigned long)++counter, Thread.rloc16());

  otMessageInfo info;
  memset(&info, 0, sizeof(info));
  otIp6AddressFromString("ff03::1", &info.mPeerAddr);  // all nodes in the mesh
  info.mPeerPort = kUdpPort;

  otMessage *msg = otUdpNewMessage(ot, NULL);
  if (msg == NULL) {
    return;
  }
  if (otMessageAppend(msg, text, strlen(text)) != OT_ERROR_NONE ||
      otUdpSend(ot, &sSocket, msg, &info) != OT_ERROR_NONE) {
    otMessageFree(msg);
    return;
  }

  Serial.print("TX  \"");
  Serial.print(text);
  Serial.println("\"");
}

void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 3000) {}

  Thread.begin();
  Thread.setNetwork("ArduinoNRF", 11, 0xBEEF, kNetworkKey);
  Thread.start();

  // One UDP socket, bound to our port, receiving into handleUdp().
  otSockAddr bindAddr;
  memset(&bindAddr, 0, sizeof(bindAddr));
  bindAddr.mPort = kUdpPort;
  otUdpOpen(Thread.instance(), &sSocket, handleUdp, NULL);
  otUdpBind(Thread.instance(), &sSocket, &bindAddr, OT_NETIF_THREAD_INTERNAL);

  Serial.println("UdpBroadcast: attaching...");
}

void loop() {
  static ThreadClass::Role lastRole = ThreadClass::ROLE_DISABLED;
  static uint32_t lastSend = 0;

  Thread.process();

  if (Thread.role() != lastRole) {
    lastRole = Thread.role();
    Serial.print("role -> ");
    Serial.println(Thread.roleString());
  }

  if (Thread.isAttached() && millis() - lastSend >= 3000) {
    lastSend = millis();
    sendHello();
  }
}
