Hi,
You can do a "read" like this (in Ruby)
msg = send_msg "read", "0 10 ivr/8000/ivr-enter_ext.wav extno 10000 #,*"
Then you wait for CHANNEL_EXECUTION_COMPLETE.
Yes, it's possible to simulate a noop like this:
fs_render 'api sleep 1', 'content_type', 'api/response'
I think using the Freeswitch command "read", like shown before, is better than playback followed by digit-by-digit processing, but the latter may have some advantages (you can make decisions quicker; if you already know where to take the user after he's keyed in two digits, no point waiting for the others, like you have to with "read").
As for nil DTMF codes, that's something I've never encountered. But, to be honest, I haven't used Liverpie in 6 years, and Freeswitch may have changed some things in their API, in the meantime. You should investigate by having Liverpie in debug mode and looking for lines that start with "Sending DTMF ". Basically, if Liverpie detects an inband DTMF from Freeswitch, it will call the webapp with whatever Freeswitch populated the "dtmf_digit" variable. No idea why that should be nil, though, without access to all logs.