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:
- 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
- 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
- They push an addr on each level
- Have "send up" and "send down" primatives
- Easiest way: Expect a DAG, with the arbitrator "rooted" at the terminal node
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 F: [3, 1]
- 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