Okay, so I have SMS working already, and a nice library to let me send and receive them, complete with emojis and international characters etc. But what I don't yet have is the voice call control working in the same way. The reason being that the EC25 module I have, while supporting 4G, lacks VoLTE support for the Optus Network in Australia.
I've poked the support at Quectel who make the modem, and I have a new firmware build for the modem from them which apparently supports VoLTE. So let's try that.
Except to do that we have to get the modem into Emergency Download Mode (EDL).
I tried tying the BOOT pin to GND (=normal boot) or to 1.8V. Tying it to 1.8V gets some other situation, but I don't know if it's actually EDL mode. At least QFirehose still fails to talk to it.
So it turns out you can't do it over the UART pins -- it has to be via the micro USB port on the evaluation board.
With that, it was easy to do the firmware update. So now let's try to enable VoLTE.
So, the firmware still lacks an Optus VoLTE profile -- but it does have a Telstra one.
After some poke and fiddle, I now have the modem such that it will receive phone calls, but trying to make one still results in NO CARRIER. This could be because of differences in the VoLTE profiles between Optus and Telstra, but it still strikes me as odd.
To get it to this state I had to use:
at+qmbncfg="AutoSel",0
at+qmbncfg="Select","Telstra-Commercial_VoLTE"
at+CFUN=1,1
<wait for the modem to reboot>
Then check that this command:
at+qcfg="ims"
Returns +QCFG: "ims",1,1 instead of +QCFG: "ims",1,0.
So there probably isn't any change to the MEGAphone software required for this -- it's just a once-off modem setup. We could make a MEGA65 native utility to set this all up, though. And perhaps we should use at+qcfg="ims" periodically to get the 3G/4G/VoLTE status and show that on the status bar, so that if this is a problem for others, they can at least see what's going on.
Okay, so the solution to that is to put a ; at end of the dial command to tell the Quectel modem that we want a voice call, not a data call.
So I now have working in-bound and out-bound calls :)
Requirements Specification
So I can finally get back to implementing our call state management library that will meet the following requirements:
1. Provide a mechanism to dial a number to establish a call.
2. Monitor the modem through +QIND status messages to tell the software what call state we are in.
3. Provide a function to accept a call.
4. Provide a function to hang up or reject a call.
5. Not allow the establishing of a second call while one is already in progress.
6. Periodically issue AT+qcfg="ims" and report current VoLTE status.
7. Provide a function to query the current microphone and speaker gain for the handset.
8. Provide a function to set the microphone gain.
9. Provide a function to set the speaker gain.
10. Provide a function to set the side-tone (i.e., "fold back from microphone into headset so you have a sense of being heard by the phone" gain)
11. Provide a function to mute a call.
12. Provide a function to unmute a call.
It probably makes sense to expand the existing modem library I've made to include those functions, rather than split the library, when both need to be parsing modem responses etc.
So let's start with the functions to establish, accept and end calls, since they're nice and easy. As are the mute, unmute and the various set gain functions. That gets us 1,3-4,7-11. We'll tackle the remaining ones in turn, starting with parsing for +QIND and other relevant messages.
We need to parse for +QIND messages that indicate call state, as well as +QCFG messages that tell us our VoLTE status. The place for this to happen is in modem_parse_line() where we already look for ERROR, OK and +CMGL.
The +QIND messages for ccinfo have this format:
+QIND: "ccinfo",<id>,<dir>,<state>,<mode>,<mpty>,<number>,<type>[,<alpha>]
Examples I've seen are like this:
RING
+QIND: "ccinfo",4,1,4,0,0,"+61870106565",128
ata
OK
+QIND: "ccinfo",4,1,0,0,0,"+61870106565",128
ath0
OK
+QIND: "ccinfo",4,1,-1,0,0,"+61870106565",128
and
atdt+61870106565;
OK
+QIND: "ccinfo",3,0,3,0,0,"61870106565",129
+QIND: "ccinfo",3,0,0,0,0,"61870106565",129
+QIND: "ccinfo",3,0,-1,0,0,"61870106565",129
NO CARRIER
The meanings of those fields are documented with the AT+CLCC command, e.g., here.
But the run-down of the fields in order is:
ID : Internal call number, e.g., for using with AT commands for handling conference calls etc.
DIR : 0 = call from the phone, 1 = call to the phone.
STATE : 0= active, 1= held, 2=dialing (outbound calls), 3=alerting (inbound calls), 4=ringing (inbound calls), 5=waiting (inbound call). There is also the undocumented value "-1", which means "call just ended" as far as I can tell.
MODE: 0=voice, 1=data, 2=fax
MPTY: 0=not part of a multi-party/conference call, 1= it is part of one
NUMBER: The phone number
TYPE: Type of phone number: 129 = unknown type, 145 = international number (beginning with +), 161= national number. Looks like 129 is international number without the +.
ALPHA: Text of contact name if the number matches an entry in the phone's internal contact list (which we're not using).
So the DIR and STATE numbers are important for us to determine the actual call-state of the phone, and NUMBER and TYPE for getting the phone number of inbound calls. The rest is probably not that relevant for us.
Okay, I have the call state parsing working. The modem by default doesn't play a "calling tone" when you dial, nor when the call is being connected. The EC25 has support for playing a single tone, and I may eventually use that to send pulses of tone to indicate ringing. But for now, I'm using a much more fun feature of the modem: It has a built-in text-to-speech reader!
So now when you dial, it literally says "dialing" in your ear. And then it says "ring ring" when the far end starts ringing.
Ironically on the real MEGA65 hardware it will be easier to put timed tone work in here, case we have the video frame counter to keep time, but in the standalone Linux test program, that's obviously not available.
I've also put in some code to make sure that a previous command finishes before we send the next command to the modem, so that no commands get lost, and generally fixed a number of call handling bugs.
But I can now establish and hang up a call.
So now to implement waiting for an incoming call -- which was super simple.
That just leaves checking the VoLTE state periodically. The easiest way I can think to do that, is to do a randomised check when modem_poll() is being called, but no command response is pending, and with some low probability query the VoLTE status, so that it happens often enough.
For now, I'll just implement a modem_query_volte() command. This will work asynchronously -- i.e., you will have to call modem_poll() and then it will parse the response whenever it comes and update the shared.volte_enabled variable.
The question is how to demonstrate this? I'd go for a video, but the only practical ways I can show it working will need to show my phone number. So let's instead take a look at the API, some of which will be familiar from the SMS API post:
char modem_init(void);
Initialise the modem for operation.
char modem_poll(void);
Poll the modem for any responses, or to get a line of input from the modem for custom processing.
void modem_parse_line(void);
Automatically called by modem_poll() to process a line from the modem, e.g., a +QIND line.
char modem_place_call(void);
Establish a call, and set the call state accordingly to the CALLSTATE_CALLING state.
void modem_answer_call(void);
Answer an in-coming call and set the call state to CALLSTATE_CONNECTED.
void modem_hangup_call(void);
Terminate (or reject) a call and set the call state to CALLSTATE_DISCONNECTED. Also cancels the mute state.
void modem_mute_call(void);
Mute the audio channel in a call.
void modem_unmute_call(void);
Cancel the muting of the audio channel in a call.
void modem_toggle_mute(void);
Toggle the mute status of a call (convenient for just having a MUTE button that toggles).
char modem_set_mic_gain(uint8_t gain);
Set the microphone gain: 0 - 255, 255 = loudest.
char modem_set_headset_gain(uint8_t gain);
Set the headset gain: 0 - 255, 255 = loudest.
char modem_set_sidetone_gain(uint8_t gain);
Set the side-tone (microphone to speaker "comfort" loop) gain: 0 - 255, 255 = loudest.
void modem_query_volte(void);
Check whether the modem's cellular connection supports VoLTE.
void modem_query_network(void);
Request that the modem provide the name of the cellular network we are connected to.
Okay, so that's the API described. This lets us do things like this:
$ bin/modem /dev/ttyUSB1 115200 init sidetone=50 headset=100 mic=100 network volte callrx
make: 'bin/modem' is up to date.
INFO: Setting side-tone level to '50'
INFO: Setting headset level to '100'
INFO: Network name is 'YES OPTUS'.
INFO: VoLTE is enabled.
Then it will sit there until a call is incoming, because I asked it to wait for a call with callrx. Then when that call comes we get something like this:
Answer incoming call from '<number redacted>'
INFO: Notifying user of changed call state.
Okay, so that more or less confirms that.
And here's a short video showing it in action, with numbers redacted via a bit of shell-scripting magic:
No comments:
Post a Comment