I have built a simple CW remote keyer in python that works very good in this early stage. Its inspired by #DL4YHF Remote CW keyer that can be found here -
https://www.qsl.net/dl4yhf/Remote_CW_Keyer/Remote_CW_Keyer.htm

Example video of me running manual keying via a serial-interface from Bencher to PC.
The "sender" to the left and "receiver" to the right. The interface is over UDP.

Its in a early stage and its missing the output node to key the remote radio.

#MorseCode #CW #RemoteRadio #opensource #Hamradio #RemoteStation

@sm0onr very nice! I've been thinking about something very similar this week.

My thinking is to use ESP32 based devices that could either take an input from a paddle/key, produce a sidetone, or send output to a rig. All three functions via a separate TRS jack.

Anyways really cool to see another approach!

@KZ4LN Thanks! Do you have any specific device in mind? I would prefer one with an ethernet interface. WiFi might work but it’s all about latency outside you LAN.

@sm0onr I don't have a specific platform picked yet. You make a very good point about latency though.

Perhaps targeting a raspberry pi isn't such a bad idea (in which case a python based project becomes completely reasonable....)

I liked the wire protocol from a github project called netcw. 11 years stale and targetting Win32 API, but a well defined UDP packet worth peeking at.

https://github.com/W8BSD/netcw

GitHub - W8BSD/netcw: Transmits CW over the internet

Transmits CW over the internet. Contribute to W8BSD/netcw development by creating an account on GitHub.

GitHub

My understanding has thus far been that one would use UDP if it doesn't matter if some data is lost (network time protocol being a standard example) and TCP if you want all data to be received.

Should I rethink that?

@KZ4LN @sm0onr

@dj3ei @sm0onr You are absolutely correct on this point. UDP also has the traditional advantage of lower latency.

It's a tradeoff of course. With UDP, you need to implement retries at the application level (because in this case it very much does matter if you miss a "ok. stop beeping now." event)

TCP is going to be much simpler to write logic for thanks to that reliability.

Without having tried yet, I'd lean toward TCP. Without knowing what network is between the units, caution is sensible.

@KZ4LN @dj3ei
The protocol is event driven and there are mechanism to handle lost bytes in the receiver. My primary goal is to run this on a LAN and then I think UDP is the best.
Ill probably experiment and benchmark with improvements including TCP if really needed.
Iv done tests over my cellular network and it handles jitter and latency very good as far as Iv seen.

My gut feeling: In a LAN or local WiFi net, anything will work just fine.

Out of the top of my head, for the general situation, I would try sending packets that end in breaks. I would also send some sort of timestamp with each package.

The receiver can then establish a notion of the longest delay seen, and delay all output accordingly. That should result in the most faithful representation possible of the precise timing ("fist") seen at the sender side. Nice for straight keys.

@sm0onr @KZ4LN

That's a design decision I'm proposing: We accept an up to 2 or 3 seconds latency. (A "0" at 12 wpm takes 1.9-2.6 s to send, so you would wait up to that long to start sending the parcel, if we decide to emit network data at character breaks only). This latency buys us faithful representation of "fist" at low bandwidth with TCP.

Contest work at, say, 20 wpm would require less latency. For contest work, we also might accept less faithfulness if that buys us less latency.

@sm0onr @KZ4LN

@dj3ei @KZ4LN @victor_59

I have now tested output via a raspberry pi4 over wifi. Sidetone is on the sender side.

Sorry for the low volume...

#MorseCode
#CW
#RemoteRadio
#opensource
#Hamradio

@sm0onr @dj3ei @victor_59 brilliant!

I haven't played with the code yet. I will. Thank you for sharing. You've clearly put a lot of thought into the protocol.

I don't understand how duration of events works with straight key. I assume with paddle you're anticipating "this is a dit. At 24WPM that's 50ms".

Improvement suggestion. you correctly note minimum Ethernet frame size is 46 bytes. Toss the past few events into the frame. It's literally padding anyways! Free n-event error recovery.

You're right - with a straight key, the sender doesn't know the duration until key-up.

Good sugestion! This will add some latency but I guess the most important benefit is the possibility for error recovery.

@KZ4LN @dj3ei @victor_59

@sm0onr @dj3ei @victor_59

This might be why retroactively sending "time since last event" is common. At 5WPM a dah is 240ms long. Longer than jitter timeout.

Perhaps intermediate status frames might make sense? E.g. "User is still pressing paddle 6 seconds later..." (tuning maybe?)

As far as redundant events in the padding, no need to get too crazy. Past the jitter window and the remote side's already stuttering.

We sent and event at key-down
send_event(key_down=True, duration=short_up_time)
then after 6 sec
send_event(key_down=False, duration=6000ms)
Time calculated from first event.

@KZ4LN @dj3ei @victor_59

Iv now tested an event-batching version
that groups multiple events into batches of up to 10 events, then sends complete batch every 200ms or when batch is full

edit: Can be configured but not tested.

Thanks Stu for the suggestion!

@KZ4LN @dj3ei @victor_59

Update (1/3)
I have don extensive testing for the past week with both UDP and TCP variants and come up with a slight modification that looks promising to handle network "disturbance". This has improved both UDP and TCP variants.

@KZ4LN @dj3ei @victor_59

#MorseCode
#CW
#RemoteRadio
#opensource
#Hamradio

Update (2/3)

**Duration-based (version1)**
Sending an "A" at 25 wpm
T+0ms: Send packet [seq=1, DOWN, 48ms] # Dit starts
T+48ms: Send packet [seq=2, UP, 48ms] # Dit ends, space starts
T+96ms: Send packet [seq=3, DOWN, 144ms] # Dah starts
T+240ms: Send packet [seq=4, UP, 48ms] # Dah ends
β†’ Queue buildup, audio artifacts

#MorseCode
#CW
#RemoteRadio
#opensource
#Hamradio

@KZ4LN @dj3ei @victor_59

Update (3/3)

**Duration + Timestamps (version2)**
Sending an "A" at 25 wpm
T+0ms: Send packet [seq=1, DOWN, 48ms, ts=0ms] # Dit starts
T+48ms: Send packet [seq=2, UP, 48ms, ts=48ms] # Dit ends
T+96ms: Send packet [seq=3, DOWN, 144ms, ts=96ms] # Dah starts
T+240ms: Send packet [seq=4, UP, 48ms, ts=240ms] # Dah ends
β†’ No queue buildup, perfect timing

#MorseCode
#CW
#RemoteRadio
#opensource
#Hamradio
@KZ4LN @dj3ei @victor_59

@sm0onr @dj3ei @victor_59 makes sense. Knowing the timestamp sequence on sender side should mean the same timing can be reproduced on recv side, regardless of the latency between nodes. Though "time zero" will be different between stations, the relative times between events can be preserved (with deliberate delay for jitter of course).

So sending both time of event and duration? Does duration become redundant?

My take (for now πŸ˜… ): Timestamp solves the scheduling problem (when to play), duration solves the playout problem (how long to play).

@KZ4LN @dj3ei @victor_59

Stu, probably redundant for TCP...

@KZ4LN @dj3ei @victor_59

Iv built a "proof of concept" page here just for testing of the protocol.
https://cw-studio.pages.dev/

Manual connection still possible from manual keying via PC client.

@KZ4LN @dj3ei @victor_59

#MorseCode
#CW
#RemoteRadio
#opensource
#Hamradio

CW Studio - TCP Timestamp Protocol