15. Physical service


Agents offering the PHYSICAL service are most commonly modem drivers and modem simulators. They support messages and parameters that are explained below. PHYSICAL service providers may also provide optional capabilities to send frames triggered at a specified time, or send timestamped frames where the timestamp is embedded in the transmitted frame.

Agents implementing the PHYSICAL service typically directly access the channel, bypassing any MAC protocol that may be in use in the network. It is highly recommended that clients wishing to use the PHYSICAL service consult with the MAC service for advice on when it is safe to access the channel, so as not to adversely affect the network performance.

15.1. Overview

All agents supporting the PHYSICAL service must also support the DATAGRAM service ( Chapter 14 ).

15.1.1. Messages

Agents supporting the PHYSICAL service provide a set of messages to manage frame transmission and reception:

  • TxFrameReq AGREE / REFUSE / FAILURE — transmit a frame

  • TxRawFrameReq AGREE / REFUSE / FAILURE — transmit a frame without headers

  • ClearReq AGREE / REFUSE / FAILURE — cancel all ongoing and pending transmissions

  • RxFrameNtf — sent to agent’s topic when a frame addressed to the node is received, and the agent’s SNOOP sub-topic when a frame addressed to other nodes is overheard

  • TxFrameNtf — sent to requestor when a frame is transmitted

  • RxFrameStartNtf — sent to agent’s topic when a frame is detected

  • TxFrameStartNtf — sent to agent’s topic when a frame transmission is started

  • BadFrameNtf — sent to agent’s topic when a bad frame is received

  • CollisionNtf — sent to agent’s topic when a frame is detected (and dropped) while another frame is being received

The TxFrameReq class extends a DatagramReq to add physical layer options, and the RxFrameNtf class extends a DatagramNtf to add physical layer metadata.

15.1.2. Parameters

Agents offering the PHYSICAL service support the following parameters:

  • rxEnable — true if reception is enabled, false otherwise

  • propagationSpeed — signal propagation speed in m/s

  • time — current physical layer clock time in microseconds

  • busy — true if modem is busy transmitting/receiving (carrier sense), false if modem is idle

  • refPowerLevel — reference source level in dB (re micro-Pascals @ 1m for underwater modems)

  • maxPowerLevel — maximum allowable transmission power in dB re refPowerLevel

  • minPowerLevel — minimum allowable transmission power in dB re refPowerLevel

  • rxSensitivity — reference receive sensitivity (dB re micro Pascals for underwater modems)

All physical layer timestamps are in microseconds as per the clock provided by the time parameter. This clock is generally not synchronized with the platform clock (system time).

In addition to the above parameters, agents also support indexed (frame type) parameters:

  • frameDuration — frame duration in seconds (maximum duration in case of variable frame length)

  • powerLevel — transmission power in dB re refPowerLevel

  • errorDetection — number of bytes used for error detection

  • frameLength — frame length in bytes (maximum length in case of variable frame length)

  • maxFrameLength — maximum possible frame length in bytes

  • fec — forward error correction (FEC) code (0 = none/default, otherwise base 1 index from fecList)

  • fecList — list of available FEC code names (in the order of increasing robustness), may be null if FEC change not supported

  • dataRate — effective frame data rate (bps)

  • llr — true to enable log-likelihood ratio reporting in BadFrameNtf , false otherwise

Seconds, milliseconds or microseconds?

You’ll notice that we use seconds as a unit of time in some places, and milliseconds in others, and microseconds in yet others. While we recognize that this can be confusing at times, there is a good reason for this.

Whenever time or duration can be a float , we prefer to use seconds as our unit of time. We also define some Groovy syntactic sugar to allow writing down values in our preferred units, while automatically converting them to seconds. For example: 10.s → 10.0, 10.ms → 0.01, and 1.minute → 60.

Many existing Java and fjåge API calls (e.g. currentTimeMillis() , WakerBehavior() , TickerBehavior() ) use the long data type for time in milliseconds. Where UnetStack inherits that API, we have no choice but to stick with milliseconds. Do be careful NOT to use values such as 10.ms there, as these are really values in seconds.

The only time value in microseconds is the PHYSICAL service’s time parameter, and the corresponding rxTime , recTime and txTime timestamps. This is also inherited by the synchronization time offset between node times in the RANGING service.

Two frame types are defined:

15.1.3. Capabilities

Agents may support several optional capabilities:

Agents advertising this capability are able to transmit frames with a transmission timestamp (start of transmission) encapsulated in the frame. This is requested through the timestamped flag in the TxFrameReq message. In order to do the timestamping, the frame has to be scheduled for transmission after a short delay. This delay is configured via an additional parameter:

Agents advertising this capability are able to start transmitting a frame at a specified time (on a best effort basis). The time is given in the txTime attribute of the TxFrameReq message.

If an agent supports the ANEP-87 JANUS standard, it advertises this capability. An additional frame type (indexed parameter set) is defined:

The JANUS capability also adds one parameter:

  • janus — true for JANUS frame type, false for all other frame types

It also adds two JANUS-specific messages that are supported:

If an agent advertises this capability, it supports an additional request to perform FEC decoding:

  • FecDecodeReq AGREE / REFUSE / FAILURE — attempt FEC decoding a frame, and if successful, send out a RxFrameNtf

15.2. CONTROL and DATA channels

The physical layer in UnetStack typically supports 2 logical channels (3 if JANUS is supported). The CONTROL channel provides low-rate, robust communication that allows exchange of small amounts of control information in the network. The DATA channel is a usually a higher rate communication link, but may require tuning to operate well in various environmental conditions.

The configurable parameters of the CONTROL and DATA channels depend strongly on the device (modem) in use. The Unet simulator provides a simplified physical layer ( HalfDuplexModem ) that captures the essential aspects of the communication using the two channels, exposing only a limited set of parameters. When configuring a real network, you should refer to your modem’s manual on advise on how best to set up the physical layer parameters.

Fire up the 2-node network simulation and connect to node A’s shell. If you simply type phy , you can explore the physical layer parameters for the node:

> phy
« Half-duplex modem »

Generic half duplex modem simulator.

  MTU ⤇ 56
  RTU ⤇ 56

  busy ⤇ false
  maxPowerLevel = 0.0
  minPowerLevel = -96.0
  propagationSpeed ⤇ 1534.4574
  refPowerLevel = 185.0
  rxEnable = true
  rxSensitivity = -200.0
  time ⤇ 2178921675
  timestampedTxDelay = 1.0

The phy.MTU and phy.RTU parameters tells us the maximum and recommended amount of user data that can be transmitted in a single frame (56 bytes in this case) respectively. This is based on the DATA channel, as we will see shortly, since DatagramReq are fulfilled using the DATA channel. The PhysicalParam parameters provide us information on whether the channel is busy, transmission power levels supported, receiver sensitivity, and propagation speed of the signal (e.g. speed of sound for underwater modems). The phy.time parameter is a microsecond resolution clock that is used to timestamp all physical layer events such as frame transmission, reception, etc.

We can dig deeper into the parameters for the CONTROL and DATA channel separately:

> phy[CONTROL]
« PHY »

  MTU ⤇ 16
  RTU ⤇ 16

  dataRate = 202.10527
  errorDetection ⤇ 1
  fec ⤇ 0
  fecList ⤇ null
  frameDuration ⤇ 0.95
  frameLength = 24
  janus = false
  llr ⤇ false
  maxFrameLength = 128
  powerLevel = -10.0

> phy[DATA]
« PHY »

  MTU ⤇ 56
  RTU ⤇ 56

  dataRate = 731.4286
  errorDetection ⤇ 1
  fec ⤇ 0
  fecList ⤇ null
  frameDuration ⤇ 0.7
  frameLength = 64
  janus = false
  llr ⤇ false
  maxFrameLength = 512
  powerLevel = -10.0
The values you see above are specific to this simulated network, and will generally be different for different networks, depending on the devices that are being used and the environment that they are deployed in.

Here are a few important parameters to take note of:

  • Note that MTU for the CONTROL channel is 16 bytes, whereas DATA channel’s MTU is 56 bytes. CONTROL frames typically carry less data, but are more robust.

  • The frameLength for the CONTROL and DATA channels are 8 bytes longer than the corresponding MTU . The difference is due to header information that the frames carry. The number of bytes taken by the header is device dependent, and also a function of network configuration (e.g. changes in node.addressSize may change header size).

  • Typically physical layer agents allow setting of the frameLength parameter, and the MTU parameter is automatically determined based on the necessary headers. The maxFrameLength parameter indicates the maximum size of the frame supported.

  • The frameDuration for the CONTROL channel is about 0.95 seconds, whereas that for the DATA channel is 0.7 seconds. While the CONTROL frames carry less data, they also have lower data rate and so may have comparable duration as the DATA frames.

  • The dataRate reported by the channel is the effective data rate in bps including the header bits, i.e., it is the frame length in bits divided by the frame duration.

  • The powerLevel parameter controls the transmission power used by the channel. This value is in dB, with reference to the phy.refPowerLevel , and may range between phy.minPowerLevel and phy.maxPowerLevel .

  • The errorDetection parameter reports the number of bytes used for error detection CRC (value of 1 indicates that we are using a 8-bit CRC). Some modems will allow you to set this to 2 to switch to 16-bit CRC, if you desire a lower probability of accepting a frame with some bit errors.

15.3. Modem physical layer

In the previous section, we explored several parameters from a simplified simulated physical layer. Next let’s look at a real modem. If you are lucky enough to own one with UnetStack on it, you can connect to it’s shell now. Otherwise, we can use Unet audio SDOAM as our test modem:

$ bin/unet audio
Modem web: http://localhost:8080/

On the web shell for the modem:

> phy
« Physical layer »

Provides software-defined physical layer communication services (including error detection & correction).

  MTU ⤇ 31
  RTU ⤇ 31

  busy ⤇ false
  maxPowerLevel ⤇ 0.0
  minPowerLevel ⤇ -138.0
  propagationSpeed = 1500.0
  refPowerLevel ⤇ 0.0
  rxEnable = true
  rxSensitivity ⤇ 0.0
  time = 4167772
  timestampedTxDelay = 1.0

  adcrate ⤇ 48000.0
  bbsblk ⤇ 6000
  bbscnt = 0
  bpfilter = true
  clockCalib = 1.0
  dacrate ⤇ 96000.0
  downconvRatio = 4.0
  fan = false
  fanctl = 45.0
  fullduplex = false
  gain = 0.0
  hpc = false
  inhibit = 120
  isc = true
  loopback = false
  model ⤇ Unet audio
  mute = true
  noise ⤇ -105.6
  npulses = 1
  pbsblk = 65536
  pbscnt = 0
  post = null
  poweramp = false
  preamp = true
  pulsedelay = 0
  serial ⤇ unetaudio
  standby = 15
  upconvRatio ⤇ 8.0
  vendor ⤇ UnetStack
  voltage ⤇ 0.0
  wakeupdelay = 400
  wakeuplen = 8000

For brevity, we have omitted the baseband service and scheduler service parameters in the listing above. Even then, there are many parameters that allow you to configure the SDOAM. We cannot cover each parameter in detail here, but we encourage you to explore the help pages for the parameters by simply typing help phy. followed by the parameter name.

Further, let’s look at the indexed parameters for the CONTROL channel:

> phy[CONTROL]
« PHY »

  MTU ⤇ 13
  RTU ⤇ 13

  dataRate ⤇ 70.588234
  errorDetection ⤇ true
  fec = 1
  fecList ⤇ [ICONV2]
  frameDuration ⤇ 2.04
  frameLength = 18
  janus = false
  llr = false
  maxFrameLength ⤇ 796
  powerLevel = -42.0

  chiplen = 1
  fmin = 9520.0
  fstep = 160.0
  hops = 13
  scrambler = 0
  sync = true
  tukey = true

  basebandExtra = 0
  basebandRx = false
  modulation = fhbfsk
  preamble = (480 samples)
  test = false
  threshold = 0.25
  valid ⤇ true

Again, we cannot cover all the parameters in detail here, but will draw your attention to a few important ones. You see that the modulation for the CONTROL channel is set to 'fhbfsk' (frequency-hopping binary frequency shift keying). Depending on your modem, different modulations may be supported. Once a modulation scheme is chosen, you see additional modulation-dependent parameters. In this case, these are the org.arl.yoda.FhbfskParam parameters such as fmin , fstep , hops , chiplen , tukey , etc. These parameters allow you to control the modulation’s frequency band, number of hops, chip duration, windowing, etc.

If you change modulation parameters, you have to remember to do it on all your modems in the network. Otherwise they will be speaking different languages , and they won’t be able to understand each other. Not all combination of modulation parameters are valid. The valid parameter tells us if the current setting is valid or not. If the setting is invalid, all transmission requests will be refused.

The preamble parameter determines a detection preamble that is transmitted before each frame. This is used by the receiving modem to determine the start of a frame. The threshold parameter controls the detection probability and false alarm rate for frame detection. A lower threshold will improve detection probability, but increase false alarm rate.

If the test flag is set on the transmission and reception modems, each transmit frame is filled with known test data. This allows the receiving modem to compute the bit error rate (BER), even when the frame has too many errors for FEC to be able to correct.

15.4. Transmitting & receiving using Unet audio

If you have two computers with speakers and microphones, you could run Unet audio on both, and communicate between the two. If you happen to have only one computer handy, do not worry — we can get one Unet audio instance to transmit and receive at the same time. This is full-duplex communication!

Real modems typically cannot do full-duplex communication because the weak incoming signals are masked by clutter from the strong outgoing signal. However, by adjusting the volume of your computer carefully, you can easily do full-duplex communication on your Unet audio SDOAM.

On Unet audio shell, enable full-duplex operation and try a transmission (you should be able to hear it from your computer speaker!). Your output might not look exactly the same, but let’s go over all the notifications we got and see if we can understand all of them:

> phy.fullduplex = true
> subscribe phy
> phy << new TxFrameReq()
phy >> TxFrameStartNtf:INFORM[type:CONTROL txTime:79322682]                (1)
phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:79309353 detector:0.87]  (2)
phy >> RxFrameStartNtf:INFORM[type:DATA rxTime:80659519 detector:0.26]     (3)
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:79310432]                     (4)
phy >> RxFrameNtf:INFORM[type:CONTROL from:1 rxTime:79309353 rssi:-29.3]   (5)
phy >> BadFrameNtf:INFORM[type:DATA rxTime:80659519 rssi:-38.5 (18 bytes)] (6)
1 Transmission of our requested CONTROL frame has started.
2 Our frame being transmitted was detected as a CONTROL frame, and reception has started.
3 Our frame being transmitted was wrongly detected (false alarm) as a DATA frame.
4 Transmission of our frame was completed.
5 Reception of the frame was completed, and successful.
6 The wrongly detected frame resulted in data that did not satisfy CRC, and hence reported as a bad frame.

To get rid of the false alarm on the DATA channel, we could either increase the detection threshold or turn off the detector completely ( phy[DATA].threshold = 0 ). For now, we’ll do the latter. Let’s also turn on the phy[CONTROL].test flag so that we can measure communication performance in terms of BER. To measure BER before error correction, we also need to turn off phy[CONTROL].fec :

> phy[DATA].threshold = 0
> phy[CONTROL].test = true
> phy[CONTROL].fec = 0

Now we can make 10 transmissions, 2 seconds apart, and watch the BER of the received frames:

> 10.times { phy << new TxFrameReq(); delay(2000); }
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:204359766]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:204385187 rssi:-28.9 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:205578432]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:205603853 rssi:-28.4 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:207567766]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:207589186 rssi:-28.5 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:209583766]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:209609187 rssi:-28.2 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:211573099]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:211594519 rssi:-28.3 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:213589099]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:213614520 rssi:-28.1 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:215578432]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:215599853 rssi:-28.5 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:217594432]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:217619853 rssi:-28.2 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:219583766]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:219605186 rssi:-28.0 cfo:0.0 ber:0/144 (18 bytes)]
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:221599766]
phy >> RxFrameNtf:INFORM[type:CONTROL rxTime:221625187 rssi:-27.7 cfo:0.0 ber:0/144 (18 bytes)]

For brevity, we have omitted the TxFrameStartNtf and RxFrameStartNtf messages. We see that no bits were in error, out of 144 transmitted bits. We had perfect communication, even without FEC! This is not surprising since the speaker and microphone are very close (and hence good signal-to-noise ratio), but real channels are rarely so forgiving. You can try this between 2 computers, and things may not be as rosy.

Feel free to play around with the parameters of the modulation scheme and try transmissions to get a feel for how the parameters affect communication performance. Since your transmission and reception modems are the same, you only need to set the parameters once! In real life, you’ll need to set the same parameters on all modems in your network.

Remember to turn off the phy[CONTROL].test flag before trying any data transfer. While the flag is on, no user data can be carried by the transmitted frames.

15.5. Timed and timestamped transmissions

To explore timed and timestamped transmissions, let’s go back to our 2-node network simulation. On the shell for node A:

> phy << new CapabilityReq()

We see that the phy agent supports the TIMESTAMPED_TX and TIMED_TX optional capabilities. Let us try them out. On node B:

> subscribe phy

Going back to node A, send a timestamped frame:

> phy << new TxFrameReq(timestamped: true)
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:2489196375]

We see that the frame was transmitted at time 2489196375 (when you try this, the time will of course be different). You should see the RxFrameNtf for this frame on node B:

phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:687419054]
phy >> RxFrameNtf:INFORM[type:CONTROL from:232 rxTime:687419054 txTime:2489196375]

Note that the RxFrameNtf now has an additional txTime field that’s populated, and the timestamp in there is the same as the txTime on node A’s TxFrameNtf . The frame was timestamped before transmission, and transmitted at exactly the intended time.

Timestamps take up bits in the transmitted frame. Your effective MTU for frames with timestamps is 6 bytes less than the advertised MTU .
Do bear in mind that the phy.time clocks on node A and B may not be synchronized. So timestamps from one node cannot be directly compared with timestamps on another node. In the above example, the rxTime was 687,419,054 microseconds, whereas the txTime was 2,489,196,375 microseconds. This does not mean that the frame was received before it was transmitted! It’s just that node A and B have an offset between their clocks.

Sometimes you may not need to transmit a timestamped frame, but you do want the frame to be transmitted at a specified time. On node A:

> t = phy.time + 5000000; println(t); phy << new TxFrameReq(txTime: t) (1)
3174864375                                                             (2)
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:3174864375]               (3)
1 t is the current time + 5 seconds. We ask for a frame to be transmitted at time t .
2 The value of time t is printed immediately (due to the println(t) ).
3 The TxFrameNtf message will appear after a few seconds, once the transmission is made. Note that the actual txTime when the transmission occurred matches with our requested value t .

If you check node B’s shell, you’ll find the corresponding RxFrameNtf , but it will not have a txTime field, as the frame transmitted was not timestamped.

The transmission time is honored on a best effort basis, which means that there could be a small difference between the requested time and the actual transmit time.

15.6. Snooping frames meant for other nodes

If you’re familiar with Ethernet network interface cards, you may have come across promiscuous mode . In this mode, the network card receives all packets that it hears, not just the ones that are addressed to the node. Agents providing the PHYSICAL service essentially do this continuously, but they send the notifications for frames intended for other nodes on a special sub-topic called SNOOP.

With the 2-node network simulation, let’s first only subscribe to the phy agent’s topic on node B:

> subscribe phy

From node A, transmit a frame to node B and to node C (node C does not exist in this network):

> phy << new TxFrameReq(to: host('B'))
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:4622534375]
> phy << new TxFrameReq(to: host('C'))
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:4623823375]

On node B, you’ll find that it receives the RxFrameStartNtf for both transmissions, but only the RxFrameNtf for the transmission addressed to node B:

phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:2820757054]
phy >> RxFrameNtf:INFORM[type:CONTROL from:232 to:31 rxTime:2820757054]
phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:2822046054]

The RxFrameStartNtf is sent when a frame is detected. At that point in time, the agent has no idea whom the frame is intended for, because the frame contents have not yet arrived. Only when the frame is received and decoded does the agent know the destination address. Seeing that the second frame was intended for node C, node B does not report a RxFrameNtf for it.

If you were interested in snooping conversations between other nodes, you could subscribe to the SNOOP topic on node B:

> subscribe topic(phy, org.arl.unet.phy.Physical.SNOOP)

Now try transmitting another frame from node A to node C. On node A:

> phy << new TxFrameReq(to: host('C'))
phy >> TxFrameNtf:INFORM[type:CONTROL txTime:4899843375]

Now you’ll see on node B that the corresponding RxFrameNtf is received:

phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:3098066054]
phy >> RxFrameNtf:INFORM[type:CONTROL from:232 to:74 rxTime:3098066054]

The to address of 74 corresponds to host('C') , but the frame is available for agents on node B through the SNOOP topic.

<<< [Datagram service] [Baseband service] >>>