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