#21 Coding the main logic

A phone in each ear. The easiest way to debug fast without picking up the receiver every five seconds.

Today was all about making the Python logic actually work.

Debugging a physical system can be exhausting. I didn’t want to spend the whole day opening phones, picking up receivers, and dialing numbers manually. To speed things up, I came up with a maker’s hack: I mapped each phone’s audio channel to one side of my headphones (Left for Phone 1, Right for Phone 2).

Instead of waiting for serial data from an actual Arduino, I built a Terminal Adapter that allowed me to type commands like T1_OFFH (Phone 1 Off-Hook) directly into my laptop. It was a refreshing day of pure coding, fixing things from my keyboard without touching a single screwdriver.

How the system thinks

To handle the complex user flow, I couldn’t rely on simple if-else statements, the code would have turned into spaghetti code instantly. Instead, I built a State Machine. I split the logic into two layers: System Modes and Phone States.

1. System Modes (global logic)

This tracks the overall state of the system:

2. Phone States (individual logic)

Each phone tracks its own lifecycle independently. A phone might be DIALING, RINGING, or RECORDING.

By separating these, the system can handle complex interruptions.

Example: If Phone 1 is recording a voicemail and Phone 2 is picked up mid-sentence, Phone 1 hears an incoming call beep. The user can then choose to dial a number to switch to a live call or refuse it to keep recording.

The Code

To make this work, I had to implement some multi-threading. Here is the breakdown of the Python logic.

__init__

In Python, methods like __init__ are called Dunder Methods (Double Under-score). These run automatically. The moment I create a new phone object (phone = Phone(1)), __init__ initializes its audio channels and dial buffers so it’s ready to go.

Multi-Threading

If the script is busy playing a 10-second intro file, it can’t listen for the rotary dial at the same time unless you use threads.

Type Hints

I used Type Hints like Optional[Phone]. This tells Python: This variable will eventually hold a Phone object, but right now, it’s empty (None). This is a simple safety net that prevents the program from crashing if it tries to talk to a Sender that hasn’t picked up yet.

Conclusion

The phones are now officially smart. They handle interruptions, they time out if you’re too slow, and they can even validate their own recordings.

Even though I am still using the Terminal to pretend I am an Arduino, the logic is ready for the real world. Hopefully, my soundcards arrive soon so I can move from my headphones to the actual phones!

Next Steps: Once the user flow is perfect, I’ll be adding the finishing touches: dial tones, busy signals, and that nostalgic waiting hum.

next post
previous post
Jana Elst

elst.jana@gmail.com

Ghent, Belgium