Anachro Bus Routing plan

I want some kind of "message board table" based on received/sent message types

For routing, you always echo messages from one bus to the other.

Everyone just sends to "everyone" (for now?) If you are a dom, you periodically transmit frames on your turn If you're a sub, schedule in downstream incoming frames either: send out a dedupe message every 15-30s

When joining a network:

  • Send dedupe code (maybe also make this the announce message?)
    • This triggers other device to start sending message?
  • Wait for N seconds to see if an echo occurs

Dedupe message is just 4 bytes of unique id, 4 bytes random number. If you hear the message come back, disregard that channel for 30-60 seconds, set some warning code (yellow light)?

Device discovery?

Avoiding fixed device ids?

Maybe:

In the future: maybe have some kind of crypto here? Probably not wanted/needed here yet.

Overall summary

  • Have a "broadcast" window, something like 1ms
    • Dom sends out random u32 and largest available ID range
    • Anyone not on the bus must jitter between 100us-900us
    • Then send their IDu8, plus (ID as u32 muladd random)
    • "READY" state achieved!
    • If not ready, wait for next round
    • Send ACK for any success number
  • For each READY number, next
    • Send new random number challenge
    • Respond (jittered) with response
    • If multiple good responses for one number, NAK number
    • "SET" state achieved!
  • For each SET number, next
    • Send new random number challenge
    • respond EXACTLY 10us (or something)
    • "ACTIVE" state achieved
    • Send "active ack"

Dom

  • Spend 999ms doing normal stuff
  • Top of every second:

#![allow(unused)]

fn main() {
// Note: opt_ids: None => "all call" response message
async fn broadcast_initial(min_us, max_us, total_us) -> (Timer::Tick, Vec<Response>) {
    let routing_mtx = acquire_routing().await;
    let (min_id, max_id) = routing_mtx.get_id_range();
    drop(routing_mtx);

    let bus_mtx = acquire_bus().await;

    let rand_nonce: u32 = rng.gen();
    bus_mtx.send_blocking(
        gen_broadcast(rand_nonce, min_us, max_us, min_id, max_id)
    ).await();
    let start = timer::now();

    let messages: Vec<BroadcastMsg, 8> = Vec::new();

    // TODO: How do we keep the messages coming?
    bus_mtx.start_listening();

    loop {
        let elapsed = timer::micros_since(start);
        if (elapsed >= total_us) || messages.is_full() {
            break;
        }
        let remaining = total_us - elapsed;

        if let Ok(Some(msg: BroadcastMsg)) = bus_mtx.receive_timeout(remaining) {
            // TODO: Checks ID is free (needs routing table!)
            if msg.check(rand_id) {
                messages.push(msg);
            }
        }
    }

    bus_mtx.stop_listening();
    bus_mtx.clear_queue();

    // We are now 1s later, immediately send ACKs
    let mut seen_ids = hash_set![];
    let mut dupe_ids = hash_set![];

    resps.iter().for_each(|r| {
        let dupe = seen_ids.insert(r.id());
        if dupe {
            dupe_ids.insert(r.id());
        }
    });

    let good_resps = resps
        .into_iter()
        .filter(|r| !dupe_ids.contans(r.id()))
        .take(4)
        .collect();

    good_resps
        .iter()
        for_each(|r| bus_mtx.send_ack(r.id()));

    (start, good_resps)
}

async fn manage_discovery() {
    async_sleep_millis(timer::now(), rng.rand_range(250..=750)).await;

    loop {
        // NOTE: on success, takes 1ms or so
        defmt::info!("Announce!");
        let (ready_start, readys) = broadcast_initial(None, 1_000, 9_000, 10_000)
            .await?;

        if readys.is_empty() {
            continue;
        }

        defmt::info!("{:?} Readys, checking...", readys.len());
        async_sleep_ms(ready_start, rng.rand_range(700..=1000)).await;

        // NOTE: on success, takes 250-1000us or so
        let (steady_start, steadys) = {
            let bus_mtx = acquire_bus().await;
            for ready in readys {
                broadcast_ping(&mut bus_mtx, /* ??? */).await;
                // TODO
            }
            // ?
        };

        if steadys.is_empty() {
            continue;
        }

        defmt::info!("{:?} Steadys, checking...", steadys.len());
        async_sleep_ms(steady_start, rng.rand_range(700..=1000)).await;

        // Note: takes 50us
        let (actives_start, new_actives) = {
            let bus_mtx = acquire_bus().await;
            for steady in steadys {
                broadcast_ping(&mut bus_mtx, /* ??? */).await;
                // TODO
            }
            // ?
        };

        if new_actives.is_empty() {
            continue;
        }

        // Add new devices to be processed
        let routing_mtx = acquire_routing().await;
        new_actives.for_each(|r| routing_mtx.push_new_id(r));
        drop(routing_mtx);

        defmt::info!("{:?} New devices!", steadys.len());
        async_sleep_ms(actives_start, rng.rand_range(700..=1000)).await;
    }
}
}

Sub

Mostly the opposite as above, but also:

  • If in the READY/STEADY/ACTIVE/CONNECTED state, don't respond to all call
  • If >= 3 seconds from last ACK without moving forward (except in connected), reset to IDLE
  • MAYBE don't bid on every all-call? 1/2? 1/4? 1/8?

Broadcast/response - broadcast_initial

Dom sends

# broadcast initial
[ random_number: u32 ][ min_wait_us: u32 ][ max_wait_us: u32 ][ min_id: u32 ][ max_id: u32 ]

After rng.gen_range(min_wait_us..=max_wait_us) us, Sub sends back:

# broadcast ack
[ own_id: u32 ][ own_id_checksum: u32 ]

where:

  • own_id: rng.gen_range(min_id..=max_id)
  • own_id_checksum: own_id.wrapping_mul(random_number).wrapping_add(random_number)

If successful, Dom sends:

# broadcast ackack
[ own_id: u32 ][ SUCCESS_MAGIC_WORD: u32 ]

Then twice: - broadcast_ping

Dom sends

# broadcast confirm
[ random_number: u32 ][ min_wait_us: u32 ][ max_wait_us: u32 ][ id: u32 ]

where id should == own_id and random_number is a TOTALLY NEW random number

TODO: Maybe just make this a dest field, with the initial broadcast being 0 or something

After rng.gen_range(min_wait_us..=max_wait_us) us, Sub sends back:

# broadcast ack
[ own_id: u32 ][ own_id_checksum: u32 ]

where:

  • own_id: rng.gen_range(min_id..=max_id)
  • own_id_checksum: own_id.wrapping_mul(random_number).wrapping_add(random_number)

If successful, Dom sends:

# broadcast ackack
[ own_id: u32 ][ SUCCESS_MAGIC_WORD: u32 ]

Routing thoughts

Okay, so above I described "everyone broadcasts everwhere", but I think I need to decide what kind of stuff I need.

I think I have two main choices:

  1. Keep the bus everywhere idea
    • All devices would basically just look for specific message IDs on the bus
    • All devices would have to listen and decode (or at least filter packed ID) continuously
    • This probably wouldn't work great with Anachro, which is more point-to-point
    • This wouldn't require any kind of "coordinator" role
  2. Do real routing, focus on (sender, receiver) pairs
    • Easiest way: Expect a DAG, with the arbitrator "rooted" at the terminal node
      • Have "send up" and "send down" primatives
        • They push an addr on each level
          • moving up push on sub -> dom
          • moving down push on dom -> sub
          • What about sub/sub devices?
        • When we hit the terminal node, the message stops propigating

Prop routing

  • dom only (head)
  • sub only (bus OR tail)
  • dom/sub (head + bus)
  • sub/sub (tail + bus)
  • dom/dom (head + head, not possible yet)

Example network 1

  • Device A - dom only
    • Device B (id: 1) - sub only (bus)
    • Device C (id: 2) - sub only (bus)
    • Device D (id: 3) - dom/sub (bus)
      • Device F (id: 1) - sub only (bus)
      • Device G (id: 2) - sub only (bus)
      • Device H (id: 3) - sub (tail)
    • Device E (id: 4) - sub only (tail)

Examples:

Device A would be prompted (how?) to send a "Send down" message:

  • to B: [1]
    • Terminates here
  • to C: [2]
    • Terminates here
  • to D: [3]
    • To F: [3, 1]
      • Terminates here
    • To G: [3, 2]
      • Terminates here
    • To H: [3, 3]
      • Terminates here
  • to E: [4]
    • Terminates here

Foreach terminator, respond with the end message. Responses:

  • [1, $anachro_id]
  • [2, $anachro_id]
  • [3, 1, $anachro_id]
  • [3, 2, $anachro_id]
  • [3, 3, $anachro_id]
  • [4, $anachro_id]

I guess what I really want is to be able to include a unique ID in the response, like the anachro uuid. I guess it's my bus, why not?

OH, but I guess I don't need a GLOBAL routing table, just:

  • Which way is the anachro arbitrator
  • Which anachro devices are on which sub nodes

Can I use this to handle the sub/sub tail case?

  • Rooted node starts
    • Subs log onto the bus
    • If a Sub is also a dom:
      • Start domming

The process for "joining" the anachro message bus is:

  • If we're a power-dom (anachro arbitrator), just start domming immediately
  • Otherwise, wait for one of the ports to become active

Profiles

  • power-dom: anachro router + 1x dom port
  • switch: anachro device + 1x dom port + 1x sub port

Pains in the ass

  • What if a tail is also a sub on a bus?
    • This is annoying, because they are a dom on neither interface
    • This requires subs to ALSO have a routing table, I guess