4  Unet basics

Now that we have a basic understanding of how UnetStack works, it is time to take the next step into setting up and configuring underwater networks, or simply Unets.

4.1 Node names and addresses

Unet nodes are identified by unique addresses within the Unet. Small Unets might use 8-bit addresses, supporting up to 255 different nodes. Larger Unets might use 16-bit addresses, supporting up to 65535 different nodes in the network. The address space is controlled by the parameter node.addressSize, and must be set to the same value (either 8 or 16) on all nodes in a Unet.

Note2-node network

The code examples in this chapter assume that you have a simulated Unet (the 2-node-network simulation from Chapter 2) freshly started, and you’re connected to the shell of one of the nodes. However, if you have access to modems, you may choose to use the real Unet and connect to the shell of one of the modem nodes.

To check the current address size on your node:

> node
« Node information »

Manages and maintains node information and attributes.

[org.arl.unet.nodeinfo.NodeInfoParam]
  address = 232
  addressSize = 8
  canForward ⤇ true
  depth = 15
  diveRate = null
  isSynchronized ⤇ false
  location = [0.0, 0.0, -15.0]
  locationAccuracy = null
  mobility = false
  nodeName = A
  origin = [NaN, NaN]
  pitch = 0
  ready ⤇ true
  roll = 0
  salinity = 35
  soundSpeed = 1539.168121822446
  speed = null
  time ⤇ Wed Jul 08 22:53:00 SGT 2026
  timeStability = null
  waterTemperature = 27
  yaw = 0
  yawRate = null

Address 0 is a broadcast address. All other addresses may be assigned to nodes in a Unet. Each Unet node is also associated with a node name (node.nodeName). If a node name is not explicitly set, it defaults to the string representation of the node address.

Descriptive node names may be used, if desired:

> node.nodeName = 'buoy_A';
> node
« Node information »

Manages and maintains node information and attributes.

[org.arl.unet.nodeinfo.NodeInfoParam]
  address = 232
  addressSize = 8
  canForward ⤇ true
  depth = 15
  diveRate = null
  isSynchronized ⤇ false
  location = [0.0, 0.0, -15.0]
  locationAccuracy = null
  mobility = false
  nodeName = buoy_A
  origin = [NaN, NaN]
  pitch = 0
  ready ⤇ true
  roll = 0
  salinity = 35
  soundSpeed = 1539.168121822446
  speed = null
  time ⤇ Wed Jul 08 22:53:06 SGT 2026
  timeStability = null
  waterTemperature = 27
  yaw = 0
  yawRate = null

It is recommended that, if descriptive node names are used, the corresponding node addresses be set using the ADDRESS_RESOLUTION service. This ensures that name-to-address resolution leads to the correct address for the node. The ADDRESS_RESOLUTION service can be accessed via the host() shell command:

> host('buoy_A')
68
> node.address = host(node.nodeName)
68

The default ADDRESS_RESOLUTION agent in UnetStack maps node names to node addresses using a hash function. This approach reduces network traffic for host name resolution, but can lead to address conflicts between nodes if two names happen to map to the same address. It is the responsibility of the network engineer to resolve address conflicts manually during the setup of the network, if the default ADDRESS_RESOLUTION agent is used. For small networks, this is simply a matter of checking that all chosen node names in the network lead to unique node addresses:

> ['buoy_A', 'auv_1', 'auv_2', 'sensor_adcp1', 'sensor_ctd1'].each { name ->
-   print "${name}: ${host(name)}"
- };
buoy_A: 68
auv_1: 150
auv_2: 109
sensor_adcp1: 43
sensor_ctd1: 14

4.2 Protocol numbers

As seen in Chapter 2, datagrams represent packets of data sent between nodes. Each node may have multiple agents and applications running on it, and so we need a way to specify which application the datagram is meant for. To aid with this, each datagram is associated with a protocol number that identifies the consumer on the destination node that the datagram is intended for. The consumer may be an agent or an end-user application. Protocol numbers can be thought of as port numbers in TCP/IP or UDP/IP.

Protocol number 0 (Protocol.DATA) is used for generic application data. Protocol numbers from 1 to 31 (Protocol.USER-1) are reserved for use by default stack agents. Protocol numbers from 32 (Protocol.USER) to 63 (Protocol.MAX) are available for end-user applications to use.

In Chapter 2, we sent datagrams without specifying protocol numbers. They used the default user protocol number (0). We can specify the protocol number while transmitting using dtx:

> dtx 0, 'hello'.bytes, protocol: Protocol.USER
AGREE
caddy >> DatagramTransmissionNtf:INFORM[id:019f4238-017c-7ab6-ce8c-81aef7115152 to:0]

or via a UnetSocket:

> s = new UnetSocket(this);
> s.connect(0, Protocol.USER);
> s.send('hello'.bytes)
true
> s.close();

In both cases, the datagram will arrive at the receiver, but won’t be shown by default (only protocol 0 is shown). To enable display of Protocol.USER, add Protocol.USER to the list of datagrams to be shown on the receiver:

> dshow Protocol.USER

Now transmit again, and you will see the received datagrams on the shell:

phy >> RxFrameNtf:INFORM[type:CONTROL from:232 protocol:32 rxStartTime:2997017959 rssi:-72.6 (5 bytes)]
  68656c6c6f

To turn off display of Protocol.USER:

> dshow -Protocol.USER

If we wanted to receive only Protocol.USER datagrams programmatically using a UnetSocket, we’d bind that socket to the protocol number:

> s = new UnetSocket(this);
> s.bind(Protocol.USER);

Now, if you transmitted a protocol 0 datagram from the other node, the socket will not receive it. However, if you transmitted a Protocol.USER datagram, it will be correctly received:

> rx = s.receive()
RxFrameNtf:INFORM[type:CONTROL from:232 protocol:32 rxStartTime:3005717959 rssi:-72.6 (5 bytes)]