Project: a reliable link
Receive a route as packets out of order, reject the damaged one, and drive it.
Do this lesson in the simulatorThe radio drops, delays and damages messages, just as a real network does. In this project the Base station sends the robot a route to drive, split into packets. They arrive out of order, and one is damaged on the way. Your robot must do what the transport layer of TCP/IP does for every web page you load: check each packet, ask for a damaged one again, put them back in order, and only then act on the whole message.
What the project uses
| Idea | Where you met it | What it does here |
|---|---|---|
| A protocol | A12.1 | both ends agree the packet format and the replies |
| Packet switching | A12.3 | the route is split into packets that may arrive in any order |
| Sequence numbers | A12.3 | each packet says where it belongs: 2/3 is packet 2 of 3 |
| A checksum | A12.4 | the receiver recalculates it to detect damage |
| Acknowledgements | A12.2, A12.4 | ACK confirms a good packet, NAK asks for a damaged one again |
| Layers | A12.4 | receiving and checking packets is kept separate from driving the route |
The packet format
Each packet is text:
<number>/<total>:<command>*<checksum>
For example 1/3:forward 30*45. The checksum is the one from A12.4: add the character codes of everything before the *, take the result modulo 256, and write it as two uppercase hexadecimal digits.
How a single bit is caught
In transit, one bit of packet 2 flips. The character 2 is code 50, 00110010; flip the bit worth 4 and it becomes 00110110, which is 54, the character 6. The command right 20 now reads right 60, which would drive the robot off course. The sum of the codes has gone up by 4, so the checksum no longer matches:
def checksum(text):
total = 0
for ch in text:
total = total + ord(ch)
return format(total % 256, "02X")
sent = "2/3:right 20"
received = "2/3:right 60"
print(format(ord("2"), "08b"), "->", format(ord("6"), "08b"))
print("checksum sent:", checksum(sent), " checksum of what arrived:", checksum(received))
# the weakness: swapping two characters leaves the sum unchanged
print("swapped:", checksum("2/3:right 02"))
A simple sum catches any single-bit error, but not two characters swapped, or two errors that cancel out. Real protocols use stronger checks: TCP uses a 16-bit checksum over its segments, and Ethernet and Wi-Fi frames end with a 32-bit cyclic redundancy check (CRC). The receiver's side of the protocol is the same whatever the check: if it fails, throw the packet away and get it again.
Designing the program
Split the job into two parts, the way the layers split a network:
- Receive: listen until every packet of the message is held and good. For each packet that arrives, split off the checksum at the
*, then the header at the:, then the number and total at the/. Recalculate the checksum. If it matches, store the command under its number and sendACK <number>. If not, discard it and sendNAK <number>, which makes the Base send it again. - Act: only when every packet is held, go through the numbers 1 to the total in order and drive each command.
In OCR-style pseudocode, the receive part is:
good = {}
total = 0
while total == 0 OR good.length < total
for each message received
body, check = split message at the last "*"
header, command = split body at the first ":"
number, count = split header at "/"
if checksum(body) == check then
good[number] = command
total = count
send("ACK " + number)
else
send("NAK " + number)
endif
next message
endwhile
Why acknowledge good packets as well? Without an ACK, the sender cannot tell a packet that arrived from one that was lost altogether. A real sender starts a timer for every packet and sends it again if no ACK arrives in time; that covers lost packets as well as damaged ones.
Task: a reliable link
The robot starts facing forward. The Base sends a route of three packets in the format above, one every half second, out of order. One packet arrives damaged. When the Base hears NAK <number>, it sends that packet again.
- Receive: check
messages()every 0.1 s. For each packet, recalculate its checksum. If it matches, store the command under the packet's number and sendACK <number>. If it does not, printpacket <number> corruptand sendNAK <number>. Keep going until you hold a good copy of every packet, as many as the total in the header says. - Act: for each number from 1 to the total, in order, print
run: <command>, then drive it at speed 50. A command is a direction (forward,backward,leftorright, where left and right slide sideways) and a whole number of centimetres. - Send
DONE.
Do not drive until every packet is held, and read the route from the packets rather than typing it. If you act on the damaged packet, you will end up in the red zone.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
def checksum(text):
total = 0
for ch in text:
total = total + ord(ch)
return format(total % 256, "02X")
wait(3)
for sender, text in messages():
print(text)
Challenges
- Count how many packets you received in total, including the damaged one, and print the fraction that were good.
- Ignore a duplicate: if a good packet you already hold arrives again, acknowledge it but do not store it twice. Why must you still send the ACK?
- Replace the checksum with one that notices two characters being swapped, for example by multiplying each code by its position before adding. Test it with the swapped example in the cell.