Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions contrib/mac-reserver/MacReserver.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package org.arl.unet.mac

import groovy.transform.CompileStatic
import groovy.transform.MapConstructor
import org.arl.fjage.AgentID
import org.arl.fjage.Message
import org.arl.fjage.Performative
import org.arl.fjage.WakerBehavior
import org.arl.fjage.param.Parameter
import org.arl.unet.Services
import org.arl.unet.UnetAgent
import org.arl.unet.Utils

class MacReserver extends UnetAgent{

public static int RESERVATION_DURATION_S = 60

private AgentID mac = null
private WakerBehavior waker = null
private String reservationID = null

enum Params implements Parameter {
reserved
}

@Override
protected void setup() {
super.setup()
}

@Override
protected void startup() {
mac = agentForService(Services.MAC)
subscribeForService(Services.MAC)
reserve()
}

@Override
protected void shutdown() {
cancelReservation()
}

@Override
protected Message processRequest(Message msg) {
log.info "Received request: ${msg}"
if (msg instanceof CancelReq) {
if (reservationID != null) {
cancelReservation()
if (msg.cancelInterval > 0) {
reserveAfter(msg.cancelInterval)
}
}
return new Message(msg, Performative.AGREE)
} else if (msg instanceof ReserveReq) {
reserve()
return new Message(msg, Performative.AGREE)
}
return new Message(msg, Performative.NOT_UNDERSTOOD)
}

@Override
protected void processMessage(Message msg) {
if (msg instanceof ReservationStatusNtf) {
if (msg.status == ReservationStatus.START) {
log.info "Medium reserved for user"
}else if (msg.status == ReservationStatus.END) {
log.info "Medium reservation ended"
if (this.reservationID != null){
// the slot ended. re-reserve
this.reservationID = null
reserve()
}
}
}
}

boolean getReserved(){
return reservationID != null
}

@Override
List<Parameter> getParameterList(){ return allOf(Params) }

//////// private helpers

private void cancelReservation(){
if (!this.reservationID){
log.fine "No ongoing reservation to cancel"
return;
}
log.info "Canceling users's reservation of the medium..."
def rsp = mac.request(new ReservationCancelReq(id: reservationID))
if (!rsp || rsp.performative != Performative.AGREE) {
log.warning "Failed to cancel reservation: ${rsp}"
}
this.reservationID = null
}

private void reserve(){
if (reservationID != null) {
log.warning "Already reserved for user"
return
}
log.info "Reserving medium for user..."
def req = new ReservationReq(duration: RESERVATION_DURATION_S)
def rsp = mac.request(req)
if (!rsp || rsp.performative != Performative.AGREE) {
log.warning "Failed to make reservation: ${rsp}"
}else {
reservationID = req.messageID;
}
}

private void reserveAfter(int interval){
if (waker) waker.stop()
waker = new WakerBehavior(interval) {
@Override
void onWake() {
reserve()
}
}
add waker
}
}

@CompileStatic
class CancelReq extends Message {
int cancelInterval = 10000; // when to re-reserve the medium after canceling it

public CancelReq() {
super(Performative.REQUEST)
}

String toString() {
return Utils.formatMessage(this, "cancelInterval:", cancelInterval);
}
}

@CompileStatic
class ReserveReq extends Message {
public ReserveReq() {
super(Performative.REQUEST)
}
}
35 changes: 35 additions & 0 deletions contrib/mac-reserver/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# MAC Rerserver

The MAC reserver agent is an agent that constantly asks for a reservation of the medium using the [medium access control (MAC) Service](https://unetstack.net/handbook/unet-handbook.html#_medium_access_control) in UnetStack.

This agent is designed to be used to stop UnetStack from transmitting while some other device is using the acoustic communication channel, but when and how often the device would use the channel is not known in advance. So this agent reverses the logic and always keeps the medium reserved for the device. Once the user knows that the other device is done using the channel, the user can send a `CancelReq` message to the MAC reserver agent to allow UnetStack's [LINK service](https://unetstack.net/handbook/unet-handbook.html#_single_hop_links) to start using the channel again.

Optionally, the `CancelReq` message can also include a `cancelDuration` parameter to specify the duration of time (in ms) after which the MAC reserver agent automatically reserves the medium again for the device. If this parameter is not provided, the user has to manually send a `ReserveReq` message to reserve the medium again.

## Usage

The agent can be added to a running UnetStack instance by copying the `MacReserver.groovy` file to the `classes` directory of the UnetStack installation, and then adding the agent to the stack using the `container.add` command.

For example the following command adds a `MacReserver` agent to the stack and uses it to reserve the medium for the device:

```groovy
container.add 'res', new MacReserver()

> res
« MacReserver »

[MacReserver.Params]
reserved ⤇ false

res << new CancelReq() // to cancel the reservation
//...
res << ReserveReq() // to reserve the medium for
//...
res << new CancelReq(cancelDuration: 10000) // to cancel the reservation and re-reserve the medium after 10 seconds

```

## Notes
- The `MacReserver` agent exposes the current state of the reservation using the `reserved` parameter. This parameter is `true` when the medium is reserved and `false` when it is not.
- The `MacReserver` agent gets the longest MAC reservation time available in the stack (usually 60 seconds) and reserves the medium for that duration. When this duration is over, the agent reserves the medium again for the same duration. In between the two reservations, there is a small window at which point UnetStack's LINK service might try to transmit a frame. While this is a known edgecase, it's unlikely that this will happen in practice, and if UnetStack isn't able to transmit a frame for more than 60 seconds, the user should consider using a different approach to synchronize the transmissions.