Re: Forward LIRC from one machine to another via UDP over the network?

Paul Fox <[email protected]> Sat, 31 May 2025 18:51:00 -0400
Newsgroups gmane.comp.hardware.lirc
Message-ID <[email protected]>
------- =_aaaaaaaaaa0
Content-Type: text/plain; charset="us-ascii"
Content-ID: <181635.1748731860.1-xLQi51zkbdE9bcD+F6QJUYSwOGtdHRLOnKs2IOyhHvI@public.gmane.org>

patrick wrote:
 >    I cobbled together this command on the RPi, hoping it would send the IR
 >    signals to the MythTV computer in the same way the HDHR did but it doesn't
 >    seem to be working.  I'm running irw on the 192.168.1.2 computer but it's
 >    not receiving any signals.
 > 
 >    /usr/bin/mode2 -d /dev/lirc0 | irtext2udp | socat -
 >    udp-datagram:[1]192.168.1.2:5000,broadcast
 > 
 >    I'm hoping someone else has done this or has experience with it and could
 >    offer me some assistance please.

Unless something has changed recently (and I doubt it), irtext2udp
is broken, because it matches the documentation of the UDP protocol, which
is also broken.  I submitted patches for these issues in March 2022:
    https://sourceforge.net/p/lirc/tickets/370/
I also sent mail to the (this) list about it at the same time -- I'm sure
it's in the archives.  So that's probably at least part of your problem.

For various reasons (mostly historical, but also having to do with my
network topology), I don't do lircd processing on the raspberry pi boxes
that are scattered around the house, each with an IR receiver.  Instead,
I forward the IR to a central server, where there's an instance of
lircd running for every client.  Since UDP can't make it through
my firewall, I send it over tcp, through an ssh tunnel, and convert
back to UDP at the destination.

For all of those reasons, I don't use irtext2udp.  I use my own script
(attached), which takes mode2 format and converts it to the UDP
format, and sends it to a tcp port.  You'll see that it sends to a port
on "localhost" -- that's because it's actually being forwarded across
an ssh tunnel.

On the receiving end, I have a listener that gets data from the other end
of the ssh tunnel.  The meat of it is this line:
   socat -u tcp4-listen:88${octet},reuseaddr,fork UDP:localhost:87${destoctet}
The 88xx address is what the ssh server delivers to, and the 87xx
address is the appropriate lircd daemon.

Hope some part of this helps.

paul
=----------------------
paul fox, [email protected] (arlington, ma, where it's 59.2 degrees)


------- =_aaaaaaaaaa0
Content-Type: text/x-shellscript; charset="utf-8"; name="irmode2-to-udp"
Content-Description: irmode2-to-udp
Content-Disposition: attachment; filename="irmode2-to-udp"
Content-Transfer-Encoding: quoted-printable

#!/bin/bash

# Convert pulse/space microsecond tick data in "mode2" format to the
# UDP driver's binary format.
# "mode2" data looks like:
#   pulse  3000
#   space  1000
#   pulse  2995
#   space  1001
#    ... etc
# Low values (that fit in 15 bits) are combined with the high-bit
# pulse/space flag, and sent as two little-endian bytes.  Larger
# values send two bytes of 0 plus the flag, and then 4 bytes of value,
# again in little-endian format.
#
# The UDP packets consist of a number of little endian 16-bit
# integers.  The high bit signifies the state of the received signal;
# set indicates a space, clear a mark.  The low 15 bits specify how
# long the signal lasted.
# =

# With the default resolution setting (61) the times are measured in
# 1/16384 second intervals.  This was used by old hardware using a
# cheap 32kHz clock crystal, when designing new devices a 1MHz 1=C2=B5s
# clock is recommended.
# =

# To allow long times to be transfered a long UDP input format is
# available, this consists of a zero time as specified by the short
# format followed by a four byte little endian time value.  This
# should only be needed occasionally for example the time between
# button presses.

set -e
set -o pipefail

oldudp=3D;

while [ "$1" ]
do
    case "$1" in
    oldudp) oldudp=3Doldudp ;;
    esac
    shift
done


ir-ctl --receive --mode2 |
  while read state ticks
  do
    : got: .$state. .$ticks.

    timeout=3D; pulse=3D;

    case $state in
    pulse)   pulse=3Dtrue ;;
    space)   pulse=3D; ;;
    timeout) timeout=3Dtrue; ;;
    "")      continue ;;
    *)       echo "unknown keyword '$state'" >&2; continue;;
    esac

    # convert the microsecond timings to the 1/16384 second intervals
    # used in the "old days".
    test "$oldudp" && (( ticks =3D (ticks + 30) / 61 ))

    if [ "$timeout" ]
    then
	printf "%b" "\x00" "\x80"
	printf "%b" "\xff" "\xff" "\xff" "\xff"

    elif (( ticks < 32768 ))
    then
	# add the high-bit flag for spaces
	test "$pulse" || (( ticks +=3D 32768 ))

	# first create two hex format ascii bytes
	printf -v hb0 "%x" $((  ticks        & 0xff ))
	printf -v hb1 "%x" $(( (ticks >>  8) & 0xff ))

	# then print those ascii bytes as binary, in l-e format
	printf "%b" "\x$hb0" "\x$hb1"

    else
	# for a "long" response, there's a two-byte little-endian
	# header: zero plus the high-bit space flag ...
	if [ $pulse ]
	then
	    printf "%b" "\x00" "\x00"
	else
	    printf "%b" "\x00" "\x80"
	fi

	# ... followed by a little-endian longword.
	# first create four bytes, in ascii format
	printf -v hb0 "%x" $((  ticks        & 0xff ))
	printf -v hb1 "%x" $(( (ticks >>  8) & 0xff ))
	printf -v hb2 "%x" $(( (ticks >> 16) & 0xff ))
	printf -v hb3 "%x" $(( (ticks >> 24) & 0xff ))

	# then print those ascii bytes to binary, in l-e format
	printf "%b" "\x$hb0" "\x$hb1" "\x$hb2" "\x$hb3"
    fi

  done | nc localhost 8700 || echo pipe failed >&2


------- =_aaaaaaaaaa0
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline


------- =_aaaaaaaaaa0--