2020-04-11 23:08:17 +02:00
/*
* Copyright ( C ) 2020 by Jonathan Naylor G4KLX
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include "Config.h"
# include "Globals.h"
# include "FM.h"
2020-05-11 19:01:50 +02:00
const uint16_t FM_TX_BLOCK_SIZE = 250U ;
const uint16_t FM_SERIAL_BLOCK_SIZE = 127U ;
2020-05-08 13:30:15 +02:00
2020-04-15 16:24:01 +02:00
CFM : : CFM ( ) :
m_callsign ( ) ,
m_rfAck ( ) ,
2020-05-07 23:01:48 +02:00
m_extAck ( ) ,
2020-04-18 14:55:34 +02:00
m_ctcssRX ( ) ,
m_ctcssTX ( ) ,
2020-04-15 16:24:01 +02:00
m_timeoutTone ( ) ,
m_state ( FS_LISTENING ) ,
m_callsignAtStart ( false ) ,
m_callsignAtEnd ( false ) ,
2020-05-04 23:18:51 +02:00
m_callsignAtLatch ( false ) ,
2020-04-15 16:24:01 +02:00
m_callsignTimer ( ) ,
m_timeoutTimer ( ) ,
m_holdoffTimer ( ) ,
m_kerchunkTimer ( ) ,
m_ackMinTimer ( ) ,
m_ackDelayTimer ( ) ,
2020-04-23 21:16:52 +02:00
m_hangTimer ( ) ,
2020-05-11 14:15:13 +02:00
m_statusTimer ( ) ,
2020-04-29 20:42:20 +02:00
m_filterStage1 ( 724 , 1448 , 724 , 32768 , - 37895 , 21352 ) , //3rd order Cheby Filter 300 to 2700Hz, 0.2dB passband ripple, sampling rate 24kHz
2020-04-24 09:10:50 +02:00
m_filterStage2 ( 32768 , 0 , - 32768 , 32768 , - 50339 , 19052 ) ,
2020-04-25 16:17:11 +02:00
m_filterStage3 ( 32768 , - 65536 , 32768 , 32768 , - 64075 , 31460 ) ,
2020-04-25 23:32:10 +02:00
m_blanking ( ) ,
2020-04-25 16:17:11 +02:00
m_useCOS ( true ) ,
2020-05-06 12:34:04 +02:00
m_cosInvert ( false ) ,
2020-04-28 21:37:06 +02:00
m_rfAudioBoost ( 1U ) ,
2020-05-07 23:01:48 +02:00
m_extAudioBoost ( 1U ) ,
2020-05-09 07:57:45 +02:00
m_downsampler ( 1200U ) , // 100 ms of audio
2020-05-08 21:47:03 +02:00
m_extEnabled ( false ) ,
2020-05-08 16:48:03 +02:00
m_rxLevel ( 1 ) ,
2020-05-09 14:06:32 +02:00
m_inputRFRB ( 4800U ) , // 200ms of audio
m_outputRFRB ( 2400U ) , // 100ms of audio
m_inputExtRB ( 2400U ) // 100ms of Audio
2020-04-11 23:08:17 +02:00
{
2020-05-11 14:15:13 +02:00
m_statusTimer . setTimeout ( 1U , 0U ) ;
2020-05-09 14:01:02 +02:00
insertDelay ( 100U ) ;
2020-04-11 23:08:17 +02:00
}
2020-05-11 22:32:24 +02:00
void CFM : : samples ( bool cos , q15_t * samples , uint8_t length )
2020-04-11 23:08:17 +02:00
{
2020-05-06 12:34:04 +02:00
if ( m_useCOS ) {
if ( m_cosInvert )
cos = ! cos ;
} else {
2020-04-25 18:29:10 +02:00
cos = true ;
2020-05-06 12:34:04 +02:00
}
2020-04-25 16:17:11 +02:00
2020-05-03 14:08:33 +02:00
clock ( length ) ;
2020-04-25 18:29:10 +02:00
uint8_t i = 0U ;
for ( ; i < length ; i + + ) {
2020-05-08 09:01:00 +02:00
// ARMv7-M has hardware integer division
2020-05-09 08:23:02 +02:00
q15_t currentRFSample = q15_t ( ( q31_t ( samples [ i ] ) < < 8 ) / m_rxLevel ) ;
uint8_t ctcssState = m_ctcssRX . process ( currentRFSample ) ;
2020-04-21 23:03:30 +02:00
2020-05-10 18:13:10 +02:00
if ( ! m_useCOS ) {
// Delay the audio by 100ms to better match the CTCSS detector output
m_inputRFRB . put ( currentRFSample ) ;
m_inputRFRB . get ( currentRFSample ) ;
}
2020-05-09 14:01:02 +02:00
2020-05-09 08:23:02 +02:00
q15_t currentExtSample ;
bool inputExt = m_inputExtRB . get ( currentExtSample ) ; //always consume the external input data so it does not overflow
2020-05-10 21:59:14 +02:00
inputExt = inputExt & & m_extEnabled ;
2020-04-22 11:04:27 +02:00
2020-05-11 14:15:13 +02:00
if ( ! inputExt & & ( CTCSS_NOT_READY ( ctcssState ) ) ) {
2020-05-10 09:07:21 +02:00
//Not enough samples to determine if you have CTCSS, just carry on. But only if we haven't any external data in the queue
2020-04-22 11:04:27 +02:00
continue ;
2020-05-11 14:15:13 +02:00
} else if ( ( inputExt | | CTCSS_READY ( ctcssState ) ) ) {
2020-04-22 11:04:27 +02:00
//we had enough samples for CTCSS and we are in some other mode than FM
2020-04-22 15:08:34 +02:00
bool validCTCSS = CTCSS_VALID ( ctcssState ) ;
2020-05-09 08:23:02 +02:00
stateMachine ( validCTCSS & & cos , inputExt ) ;
2020-05-11 14:15:13 +02:00
} else if ( ( inputExt | | CTCSS_READY ( ctcssState ) ) ) {
2020-04-22 11:04:27 +02:00
//We had enough samples for CTCSS and we are in FM mode, trigger the state machine
2020-04-22 15:08:34 +02:00
bool validCTCSS = CTCSS_VALID ( ctcssState ) ;
2020-05-09 08:23:02 +02:00
stateMachine ( validCTCSS & & cos , inputExt ) ;
2020-05-11 14:15:13 +02:00
} else if ( ( inputExt | | CTCSS_NOT_READY ( ctcssState ) ) & & i = = length - 1 ) {
2020-04-22 11:04:27 +02:00
//Not enough samples for CTCSS but we already are in FM, trigger the state machine
//but do not trigger the state machine on every single sample, save CPU!
2020-04-28 21:37:06 +02:00
bool validCTCSS = CTCSS_VALID ( ctcssState ) ;
2020-05-09 08:23:02 +02:00
stateMachine ( validCTCSS & & cos , inputExt ) ;
2020-04-22 11:04:27 +02:00
}
2020-04-28 22:35:06 +02:00
2020-05-09 08:23:02 +02:00
q15_t currentSample = currentRFSample ;
2020-05-09 08:29:31 +02:00
q15_t currentBoost = m_rfAudioBoost ;
if ( m_state = = FS_RELAYING_EXT | | m_state = = FS_KERCHUNK_EXT ) {
2020-05-09 08:23:02 +02:00
currentSample = currentExtSample ;
2020-05-09 08:29:31 +02:00
currentBoost = m_extAudioBoost ;
}
2020-05-09 08:23:02 +02:00
2020-05-07 23:39:17 +02:00
// Only let RF audio through when relaying RF audio
2020-05-09 08:29:31 +02:00
if ( m_state = = FS_RELAYING_RF | | m_state = = FS_KERCHUNK_RF | | m_state = = FS_RELAYING_EXT | | m_state = = FS_KERCHUNK_EXT ) {
2020-05-10 21:59:14 +02:00
currentSample = m_blanking . process ( currentSample ) ;
2020-05-10 22:25:10 +02:00
2020-05-09 08:29:31 +02:00
if ( m_extEnabled & & ( m_state = = FS_RELAYING_RF | | m_state = = FS_KERCHUNK_RF ) )
2020-05-07 23:39:17 +02:00
m_downsampler . addSample ( currentSample ) ;
2020-05-09 08:29:31 +02:00
currentSample * = currentBoost ;
2020-04-29 21:59:48 +02:00
} else {
2020-04-29 22:51:12 +02:00
currentSample = 0 ;
2020-04-29 21:59:48 +02:00
}
2020-04-25 16:17:11 +02:00
2020-05-07 23:39:17 +02:00
if ( ! m_callsign . isRunning ( ) & & ! m_extAck . isRunning ( ) )
2020-04-22 23:10:34 +02:00
currentSample + = m_rfAck . getHighAudio ( ) ;
2020-05-07 23:39:17 +02:00
if ( ! m_callsign . isRunning ( ) & & ! m_rfAck . isRunning ( ) )
currentSample + = m_extAck . getHighAudio ( ) ;
if ( ! m_rfAck . isRunning ( ) & & ! m_extAck . isRunning ( ) ) {
2020-04-22 23:10:34 +02:00
if ( m_state = = FS_LISTENING )
currentSample + = m_callsign . getHighAudio ( ) ;
else
currentSample + = m_callsign . getLowAudio ( ) ;
}
2020-04-15 17:31:49 +02:00
2020-05-07 23:39:17 +02:00
if ( ! m_callsign . isRunning ( ) & & ! m_rfAck . isRunning ( ) & & ! m_extAck . isRunning ( ) )
2020-04-21 22:00:11 +02:00
currentSample + = m_timeoutTone . getAudio ( ) ;
2020-04-21 21:36:30 +02:00
2020-04-28 22:35:06 +02:00
currentSample = m_filterStage3 . filter ( m_filterStage2 . filter ( m_filterStage1 . filter ( currentSample ) ) ) ;
2020-04-28 21:37:06 +02:00
2020-04-21 22:00:11 +02:00
currentSample + = m_ctcssTX . getAudio ( ) ;
2020-04-15 17:31:49 +02:00
2020-05-11 14:15:13 +02:00
m_outputRFRB . put ( currentSample ) ;
2020-05-11 22:32:24 +02:00
//samples[i] = currentSample;
2020-04-21 20:38:02 +02:00
}
2020-05-11 22:32:24 +02:00
2020-05-11 22:35:46 +02:00
// XXX This relays audio correctly, no tones yet, process need to be commented
2020-05-11 22:32:24 +02:00
// if (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF || m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT)
// io.write(STATE_FM, samples, i);
2020-04-11 23:08:17 +02:00
}
void CFM : : process ( )
{
2020-05-11 22:32:24 +02:00
uint16_t space = io . getSpace ( ) - 2U ;
uint16_t length = m_outputRFRB . getData ( ) ;
if ( space > 2 & & length > = FM_TX_BLOCK_SIZE ) {
if ( length > FM_TX_BLOCK_SIZE )
length = FM_TX_BLOCK_SIZE ;
if ( space > FM_TX_BLOCK_SIZE )
space = FM_TX_BLOCK_SIZE ;
if ( length > space )
length = space ;
q15_t samples [ FM_TX_BLOCK_SIZE ] ;
for ( uint16_t i = 0U ; i < length ; i + + ) {
q15_t sample = 0 ;
m_outputRFRB . get ( sample ) ;
samples [ i ] = sample ;
}
2020-05-11 19:01:50 +02:00
2020-05-11 22:32:24 +02:00
io . write ( STATE_FM , samples , length ) ;
}
2020-05-11 19:01:50 +02:00
// //Write audio to serial
// if (m_downsampler.getData() >= 127 || m_state != STATE_FM) {//if we just left FM mode, so write all what is left in buffer, regardless of the amoutn of data
// uint16_t length = m_downsampler.getData();
// if(length > FM_SERIAL_BLOCK_SIZE)//max message size on serial is 127
// length = FM_SERIAL_BLOCK_SIZE;
// if(length > FM_SERIAL_BLOCK_SIZE)
// length = FM_SERIAL_BLOCK_SIZE;
// uint8_t serialSamples[FM_SERIAL_BLOCK_SIZE];
// for(uint16_t i = 0U; i < length; i++) {
// uint8_t serialSample = 0U;
// m_downsampler.getPackedData(serialSample);
// serialSamples[i] = serialSample;
// }
// serial.writeFMData(serialSamples, length);
// }
2020-04-11 23:08:17 +02:00
}
void CFM : : reset ( )
{
2020-05-02 15:47:43 +02:00
m_state = FS_LISTENING ;
m_callsignTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_kerchunkTimer . stop ( ) ;
m_ackMinTimer . stop ( ) ;
m_ackDelayTimer . stop ( ) ;
m_hangTimer . stop ( ) ;
2020-05-11 14:15:13 +02:00
m_statusTimer . stop ( ) ;
2020-05-02 15:47:43 +02:00
2020-04-18 17:48:30 +02:00
m_ctcssRX . reset ( ) ;
2020-05-02 15:47:43 +02:00
m_rfAck . stop ( ) ;
2020-05-07 23:39:17 +02:00
m_extAck . stop ( ) ;
2020-05-02 15:47:43 +02:00
m_callsign . stop ( ) ;
m_timeoutTone . stop ( ) ;
2020-05-08 16:48:03 +02:00
2020-05-09 08:00:24 +02:00
m_outputRFRB . reset ( ) ;
2020-05-09 10:57:16 +02:00
m_inputExtRB . reset ( ) ;
m_downsampler . reset ( ) ;
2020-04-11 23:08:17 +02:00
}
2020-04-12 16:28:56 +02:00
2020-05-04 23:18:51 +02:00
uint8_t CFM : : setCallsign ( const char * callsign , uint8_t speed , uint16_t frequency , uint8_t time , uint8_t holdoff , uint8_t highLevel , uint8_t lowLevel , bool callsignAtStart , bool callsignAtEnd , bool callsignAtLatch )
2020-04-12 16:28:56 +02:00
{
2020-04-15 16:24:01 +02:00
m_callsignAtStart = callsignAtStart ;
m_callsignAtEnd = callsignAtEnd ;
2020-05-04 23:18:51 +02:00
m_callsignAtLatch = callsignAtLatch ;
2020-04-15 16:24:01 +02:00
2020-04-24 13:15:58 +02:00
uint16_t holdoffTime = holdoff * 60U ;
2020-04-15 23:41:30 +02:00
uint16_t callsignTime = time * 60U ;
m_holdoffTimer . setTimeout ( holdoffTime , 0U ) ;
m_callsignTimer . setTimeout ( callsignTime , 0U ) ;
2020-04-16 15:00:31 +02:00
2020-04-24 13:15:58 +02:00
if ( holdoffTime > 0U )
m_holdoffTimer . start ( ) ;
2020-04-22 23:10:34 +02:00
return m_callsign . setParams ( callsign , speed , frequency , highLevel , lowLevel ) ;
2020-04-12 16:28:56 +02:00
}
2020-04-16 15:00:31 +02:00
uint8_t CFM : : setAck ( const char * rfAck , uint8_t speed , uint16_t frequency , uint8_t minTime , uint16_t delay , uint8_t level )
2020-04-12 16:28:56 +02:00
{
2020-04-15 18:18:01 +02:00
m_ackDelayTimer . setTimeout ( 0U , delay ) ;
2020-05-04 23:06:57 +02:00
if ( minTime > 0U )
m_ackMinTimer . setTimeout ( minTime , delay ) ;
2020-04-16 15:00:31 +02:00
2020-04-22 23:10:34 +02:00
return m_rfAck . setParams ( rfAck , speed , frequency , level , level ) ;
2020-04-12 16:28:56 +02:00
}
2020-05-06 12:34:04 +02:00
uint8_t CFM : : setMisc ( uint16_t timeout , uint8_t timeoutLevel , uint8_t ctcssFrequency , uint8_t ctcssThreshold , uint8_t ctcssLevel , uint8_t kerchunkTime , uint8_t hangTime , bool useCOS , bool cosInvert , uint8_t rfAudioBoost , uint8_t maxDev , uint8_t rxLevel )
2020-04-12 16:28:56 +02:00
{
2020-05-06 12:34:04 +02:00
m_useCOS = useCOS ;
m_cosInvert = cosInvert ;
2020-04-28 15:43:40 +02:00
m_rfAudioBoost = q15_t ( rfAudioBoost ) ;
2020-04-25 16:17:11 +02:00
2020-04-15 18:18:01 +02:00
m_timeoutTimer . setTimeout ( timeout , 0U ) ;
m_kerchunkTimer . setTimeout ( kerchunkTime , 0U ) ;
m_hangTimer . setTimeout ( hangTime , 0U ) ;
2020-04-16 15:00:31 +02:00
m_timeoutTone . setParams ( timeoutLevel ) ;
2020-04-25 23:32:10 +02:00
m_blanking . setParams ( maxDev , timeoutLevel ) ;
2020-04-16 15:00:31 +02:00
2020-05-08 09:01:00 +02:00
m_rxLevel = rxLevel ; //q15_t(255)/q15_t(rxLevel >> 1);
uint8_t ret = m_ctcssRX . setParams ( ctcssFrequency , ctcssThreshold ) ;
2020-04-18 14:55:34 +02:00
if ( ret ! = 0U )
return ret ;
2020-04-16 15:00:31 +02:00
2020-04-18 14:55:34 +02:00
return m_ctcssTX . setParams ( ctcssFrequency , ctcssLevel ) ;
2020-04-12 16:28:56 +02:00
}
2020-04-15 17:31:49 +02:00
2020-05-07 23:01:48 +02:00
uint8_t CFM : : setExt ( const char * ack , uint8_t audioBoost , uint8_t speed , uint16_t frequency , uint8_t level )
{
m_extEnabled = true ;
m_extAudioBoost = q15_t ( audioBoost ) ;
return m_extAck . setParams ( ack , speed , frequency , level , level ) ;
}
2020-05-07 23:39:17 +02:00
void CFM : : stateMachine ( bool validRFSignal , bool validExtSignal )
2020-04-15 17:31:49 +02:00
{
switch ( m_state ) {
case FS_LISTENING :
2020-05-07 23:39:17 +02:00
listeningState ( validRFSignal , validExtSignal ) ;
break ;
case FS_KERCHUNK_RF :
kerchunkRFState ( validRFSignal ) ;
2020-04-15 17:31:49 +02:00
break ;
2020-05-07 23:39:17 +02:00
case FS_RELAYING_RF :
relayingRFState ( validRFSignal ) ;
2020-04-15 17:31:49 +02:00
break ;
2020-05-07 23:39:17 +02:00
case FS_RELAYING_WAIT_RF :
relayingRFWaitState ( validRFSignal ) ;
2020-04-15 17:31:49 +02:00
break ;
2020-05-07 23:39:17 +02:00
case FS_TIMEOUT_RF :
timeoutRFState ( validRFSignal ) ;
2020-04-15 17:31:49 +02:00
break ;
2020-05-07 23:39:17 +02:00
case FS_TIMEOUT_WAIT_RF :
timeoutRFWaitState ( validRFSignal ) ;
2020-04-15 17:31:49 +02:00
break ;
2020-05-07 23:39:17 +02:00
case FS_KERCHUNK_EXT :
kerchunkExtState ( validExtSignal ) ;
break ;
case FS_RELAYING_EXT :
relayingExtState ( validExtSignal ) ;
break ;
case FS_RELAYING_WAIT_EXT :
relayingExtWaitState ( validExtSignal ) ;
break ;
case FS_TIMEOUT_EXT :
timeoutExtState ( validExtSignal ) ;
break ;
case FS_TIMEOUT_WAIT_EXT :
timeoutExtWaitState ( validExtSignal ) ;
2020-04-15 17:31:49 +02:00
break ;
case FS_HANG :
2020-05-07 23:39:17 +02:00
hangState ( validRFSignal , validExtSignal ) ;
2020-04-15 17:31:49 +02:00
break ;
default :
break ;
}
2020-05-11 22:32:24 +02:00
if ( m_state = = FS_LISTENING ) {
m_outputRFRB . reset ( ) ;
m_downsampler . reset ( ) ;
}
2020-04-15 17:31:49 +02:00
}
2020-05-03 14:08:33 +02:00
void CFM : : clock ( uint8_t length )
{
m_callsignTimer . clock ( length ) ;
m_timeoutTimer . clock ( length ) ;
m_holdoffTimer . clock ( length ) ;
m_kerchunkTimer . clock ( length ) ;
m_ackMinTimer . clock ( length ) ;
m_ackDelayTimer . clock ( length ) ;
m_hangTimer . clock ( length ) ;
2020-05-11 14:15:13 +02:00
m_statusTimer . clock ( length ) ;
if ( m_statusTimer . isRunning ( ) & & m_statusTimer . hasExpired ( ) ) {
serial . writeFMStatus ( ) ;
m_statusTimer . start ( ) ;
}
2020-05-03 14:08:33 +02:00
}
2020-05-07 23:39:17 +02:00
void CFM : : listeningState ( bool validRFSignal , bool validExtSignal )
2020-04-15 17:31:49 +02:00
{
2020-05-07 23:39:17 +02:00
if ( validRFSignal ) {
if ( m_kerchunkTimer . getTimeout ( ) > 0U ) {
DEBUG1 ( " State to KERCHUNK_RF " ) ;
m_state = FS_KERCHUNK_RF ;
m_kerchunkTimer . start ( ) ;
if ( m_callsignAtStart & & ! m_callsignAtLatch )
sendCallsign ( ) ;
} else {
DEBUG1 ( " State to RELAYING_RF " ) ;
m_state = FS_RELAYING_RF ;
if ( m_callsignAtStart )
sendCallsign ( ) ;
}
2020-05-09 14:10:02 +02:00
insertSilence ( 50U ) ;
2020-05-07 23:39:17 +02:00
beginRelaying ( ) ;
m_callsignTimer . start ( ) ;
2020-05-11 14:15:13 +02:00
m_statusTimer . start ( ) ;
serial . writeFMStatus ( ) ;
2020-05-07 23:39:17 +02:00
} else if ( validExtSignal ) {
2020-04-21 21:00:09 +02:00
if ( m_kerchunkTimer . getTimeout ( ) > 0U ) {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to KERCHUNK_EXT " ) ;
m_state = FS_KERCHUNK_EXT ;
2020-04-21 21:00:09 +02:00
m_kerchunkTimer . start ( ) ;
2020-05-04 23:18:51 +02:00
if ( m_callsignAtStart & & ! m_callsignAtLatch )
sendCallsign ( ) ;
2020-04-21 21:00:09 +02:00
} else {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to RELAYING_EXT " ) ;
m_state = FS_RELAYING_EXT ;
2020-04-21 21:00:09 +02:00
if ( m_callsignAtStart )
sendCallsign ( ) ;
}
2020-04-15 17:31:49 +02:00
2020-05-08 16:48:03 +02:00
insertSilence ( 50U ) ;
2020-04-21 21:00:09 +02:00
beginRelaying ( ) ;
2020-04-15 17:31:49 +02:00
2020-04-21 21:00:09 +02:00
m_callsignTimer . start ( ) ;
2020-04-15 17:31:49 +02:00
2020-05-11 14:15:13 +02:00
m_statusTimer . start ( ) ;
serial . writeFMStatus ( ) ;
2020-04-21 21:00:09 +02:00
}
2020-04-15 17:31:49 +02:00
}
2020-05-07 23:39:17 +02:00
void CFM : : kerchunkRFState ( bool validSignal )
2020-04-15 17:31:49 +02:00
{
if ( validSignal ) {
if ( m_kerchunkTimer . hasExpired ( ) ) {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to RELAYING_RF " ) ;
m_state = FS_RELAYING_RF ;
2020-04-15 17:31:49 +02:00
m_kerchunkTimer . stop ( ) ;
2020-05-04 23:18:51 +02:00
if ( m_callsignAtStart & & m_callsignAtLatch ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
2020-04-15 17:31:49 +02:00
}
} else {
2020-04-17 19:53:28 +02:00
DEBUG1 ( " State to LISTENING " ) ;
2020-04-15 17:31:49 +02:00
m_state = FS_LISTENING ;
m_kerchunkTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_ackMinTimer . stop ( ) ;
m_callsignTimer . stop ( ) ;
2020-05-11 16:00:53 +02:00
m_statusTimer . stop ( ) ;
2020-04-15 17:31:49 +02:00
}
}
2020-05-07 23:39:17 +02:00
void CFM : : relayingRFState ( bool validSignal )
2020-04-15 17:31:49 +02:00
{
if ( validSignal ) {
if ( m_timeoutTimer . isRunning ( ) & & m_timeoutTimer . hasExpired ( ) ) {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to TIMEOUT_RF " ) ;
m_state = FS_TIMEOUT_RF ;
2020-04-15 17:31:49 +02:00
m_ackMinTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_timeoutTone . start ( ) ;
}
} else {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to RELAYING_WAIT_RF " ) ;
m_state = FS_RELAYING_WAIT_RF ;
2020-04-15 17:31:49 +02:00
m_ackDelayTimer . start ( ) ;
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
2020-05-07 23:39:17 +02:00
void CFM : : relayingRFWaitState ( bool validSignal )
2020-04-15 17:31:49 +02:00
{
if ( validSignal ) {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to RELAYING_RF " ) ;
m_state = FS_RELAYING_RF ;
2020-04-15 17:31:49 +02:00
m_ackDelayTimer . stop ( ) ;
} else {
if ( m_ackDelayTimer . isRunning ( ) & & m_ackDelayTimer . hasExpired ( ) ) {
2020-04-17 19:53:28 +02:00
DEBUG1 ( " State to HANG " ) ;
2020-04-15 17:31:49 +02:00
m_state = FS_HANG ;
if ( m_ackMinTimer . isRunning ( ) ) {
if ( m_ackMinTimer . hasExpired ( ) ) {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " Send RF ack " ) ;
2020-04-15 17:31:49 +02:00
m_rfAck . start ( ) ;
m_ackMinTimer . stop ( ) ;
}
} else {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " Send RF ack " ) ;
2020-04-15 17:31:49 +02:00
m_rfAck . start ( ) ;
m_ackMinTimer . stop ( ) ;
}
m_ackDelayTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_hangTimer . start ( ) ;
}
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
2020-05-07 23:39:17 +02:00
void CFM : : kerchunkExtState ( bool validSignal )
2020-04-15 17:31:49 +02:00
{
if ( validSignal ) {
2020-05-07 23:39:17 +02:00
if ( m_kerchunkTimer . hasExpired ( ) ) {
DEBUG1 ( " State to RELAYING_EXT " ) ;
m_state = FS_RELAYING_EXT ;
m_kerchunkTimer . stop ( ) ;
if ( m_callsignAtStart & & m_callsignAtLatch ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
} else {
DEBUG1 ( " State to LISTENING " ) ;
m_state = FS_LISTENING ;
m_kerchunkTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_ackMinTimer . stop ( ) ;
m_callsignTimer . stop ( ) ;
2020-05-11 16:00:53 +02:00
m_statusTimer . stop ( ) ;
2020-05-07 23:39:17 +02:00
}
}
void CFM : : relayingExtState ( bool validSignal )
{
if ( validSignal ) {
if ( m_timeoutTimer . isRunning ( ) & & m_timeoutTimer . hasExpired ( ) ) {
DEBUG1 ( " State to TIMEOUT_EXT " ) ;
m_state = FS_TIMEOUT_EXT ;
m_ackMinTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_timeoutTone . start ( ) ;
}
} else {
DEBUG1 ( " State to RELAYING_WAIT_EXT " ) ;
m_state = FS_RELAYING_WAIT_EXT ;
m_ackDelayTimer . start ( ) ;
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
void CFM : : relayingExtWaitState ( bool validSignal )
{
if ( validSignal ) {
DEBUG1 ( " State to RELAYING_EXT " ) ;
m_state = FS_RELAYING_EXT ;
m_ackDelayTimer . stop ( ) ;
} else {
if ( m_ackDelayTimer . isRunning ( ) & & m_ackDelayTimer . hasExpired ( ) ) {
DEBUG1 ( " State to HANG " ) ;
m_state = FS_HANG ;
if ( m_ackMinTimer . isRunning ( ) ) {
if ( m_ackMinTimer . hasExpired ( ) ) {
DEBUG1 ( " Send Ext ack " ) ;
m_extAck . start ( ) ;
m_ackMinTimer . stop ( ) ;
}
} else {
DEBUG1 ( " Send Ext ack " ) ;
m_extAck . start ( ) ;
m_ackMinTimer . stop ( ) ;
}
m_ackDelayTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_hangTimer . start ( ) ;
}
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
void CFM : : hangState ( bool validRFSignal , bool validExtSignal )
{
if ( validRFSignal ) {
DEBUG1 ( " State to RELAYING_RF " ) ;
m_state = FS_RELAYING_RF ;
DEBUG1 ( " Stop ack " ) ;
m_rfAck . stop ( ) ;
m_extAck . stop ( ) ;
beginRelaying ( ) ;
} else if ( validExtSignal ) {
DEBUG1 ( " State to RELAYING_EXT " ) ;
m_state = FS_RELAYING_EXT ;
2020-04-17 19:53:28 +02:00
DEBUG1 ( " Stop ack " ) ;
2020-04-15 17:31:49 +02:00
m_rfAck . stop ( ) ;
2020-05-07 23:39:17 +02:00
m_extAck . stop ( ) ;
2020-04-15 17:31:49 +02:00
beginRelaying ( ) ;
} else {
if ( m_hangTimer . isRunning ( ) & & m_hangTimer . hasExpired ( ) ) {
2020-04-17 19:53:28 +02:00
DEBUG1 ( " State to LISTENING " ) ;
2020-04-15 17:31:49 +02:00
m_state = FS_LISTENING ;
m_hangTimer . stop ( ) ;
2020-05-11 16:00:53 +02:00
m_statusTimer . stop ( ) ;
2020-04-15 17:31:49 +02:00
if ( m_callsignAtEnd )
sendCallsign ( ) ;
m_callsignTimer . stop ( ) ;
}
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
2020-05-07 23:39:17 +02:00
void CFM : : timeoutRFState ( bool validSignal )
2020-04-15 17:31:49 +02:00
{
if ( ! validSignal ) {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to TIMEOUT_WAIT_RF " ) ;
m_state = FS_TIMEOUT_WAIT_RF ;
2020-04-15 17:31:49 +02:00
m_ackDelayTimer . start ( ) ;
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
2020-05-07 23:39:17 +02:00
void CFM : : timeoutRFWaitState ( bool validSignal )
2020-04-15 17:31:49 +02:00
{
if ( validSignal ) {
2020-05-07 23:39:17 +02:00
DEBUG1 ( " State to TIMEOUT_RF " ) ;
m_state = FS_TIMEOUT_RF ;
2020-04-15 17:31:49 +02:00
m_ackDelayTimer . stop ( ) ;
} else {
if ( m_ackDelayTimer . isRunning ( ) & & m_ackDelayTimer . hasExpired ( ) ) {
2020-04-17 19:53:28 +02:00
DEBUG1 ( " State to HANG " ) ;
2020-04-15 17:31:49 +02:00
m_state = FS_HANG ;
m_timeoutTone . stop ( ) ;
2020-05-07 23:39:17 +02:00
DEBUG1 ( " Send RF ack " ) ;
2020-04-15 17:31:49 +02:00
m_rfAck . start ( ) ;
m_ackDelayTimer . stop ( ) ;
m_ackMinTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_hangTimer . start ( ) ;
}
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
2020-05-07 23:39:17 +02:00
void CFM : : timeoutExtState ( bool validSignal )
{
if ( ! validSignal ) {
DEBUG1 ( " State to TIMEOUT_WAIT_EXT " ) ;
m_state = FS_TIMEOUT_WAIT_EXT ;
m_ackDelayTimer . start ( ) ;
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
void CFM : : timeoutExtWaitState ( bool validSignal )
{
if ( validSignal ) {
DEBUG1 ( " State to TIMEOUT_EXT " ) ;
m_state = FS_TIMEOUT_EXT ;
m_ackDelayTimer . stop ( ) ;
} else {
if ( m_ackDelayTimer . isRunning ( ) & & m_ackDelayTimer . hasExpired ( ) ) {
DEBUG1 ( " State to HANG " ) ;
m_state = FS_HANG ;
m_timeoutTone . stop ( ) ;
DEBUG1 ( " Send Ext ack " ) ;
m_extAck . start ( ) ;
m_ackDelayTimer . stop ( ) ;
m_ackMinTimer . stop ( ) ;
m_timeoutTimer . stop ( ) ;
m_hangTimer . start ( ) ;
}
}
if ( m_callsignTimer . isRunning ( ) & & m_callsignTimer . hasExpired ( ) ) {
sendCallsign ( ) ;
m_callsignTimer . start ( ) ;
}
}
2020-04-15 17:31:49 +02:00
void CFM : : sendCallsign ( )
{
if ( m_holdoffTimer . isRunning ( ) ) {
if ( m_holdoffTimer . hasExpired ( ) ) {
2020-04-17 19:53:28 +02:00
DEBUG1 ( " Send callsign " ) ;
2020-04-15 17:31:49 +02:00
m_callsign . start ( ) ;
m_holdoffTimer . start ( ) ;
}
} else {
2020-04-17 19:53:28 +02:00
DEBUG1 ( " Send callsign " ) ;
2020-04-15 17:31:49 +02:00
m_callsign . start ( ) ;
}
}
void CFM : : beginRelaying ( )
{
m_timeoutTimer . start ( ) ;
m_ackMinTimer . start ( ) ;
}
2020-05-07 23:07:58 +02:00
uint8_t CFM : : getSpace ( ) const
{
// The amount of free space for receiving external audio, in bytes.
2020-05-09 08:00:24 +02:00
return m_inputExtRB . getSpace ( ) ;
2020-05-07 23:07:58 +02:00
}
uint8_t CFM : : writeData ( const uint8_t * data , uint8_t length )
{
2020-05-08 13:30:15 +02:00
for ( uint8_t i = 0U ; i < length ; i + = 3U ) {
uint16_t sample1 = 0U ;
uint16_t sample2 = 0U ;
2020-05-08 17:41:12 +02:00
uint32_t MASK = 0x00000FFFU ;
2020-05-08 13:30:15 +02:00
2020-05-08 17:41:12 +02:00
uint32_t pack = 0U ;
uint8_t * packPointer = ( uint8_t * ) & pack ;
2020-05-08 13:30:15 +02:00
2020-05-08 17:41:12 +02:00
packPointer [ 1U ] = data [ i ] ;
packPointer [ 2U ] = data [ i + 1U ] ;
packPointer [ 3U ] = data [ i + 2U ] ;
2020-05-08 13:30:15 +02:00
2020-05-08 17:41:12 +02:00
sample2 = uint16_t ( pack & MASK ) ;
sample1 = uint16_t ( pack > > 12 ) ;
2020-05-08 13:30:15 +02:00
2020-05-09 07:57:45 +02:00
// Convert from uint16_t (0 - +4095) to Q15 (-2048 - +2047).
// Incoming data has sample rate 8kHz, just add 2 empty samples after
// every incoming sample to upsample to 24kHz
2020-05-09 08:00:24 +02:00
m_inputExtRB . put ( q15_t ( sample1 ) - 2048 ) ;
m_inputExtRB . put ( 0 ) ;
m_inputExtRB . put ( 0 ) ;
m_inputExtRB . put ( q15_t ( sample2 ) - 2048 ) ;
m_inputExtRB . put ( 0 ) ;
m_inputExtRB . put ( 0 ) ;
2020-05-08 13:30:15 +02:00
}
// Received audio is now in Q15 format in samples, with length nSamples.
2020-05-07 23:07:58 +02:00
return 0U ;
}
2020-05-08 21:39:18 +02:00
2020-05-09 14:01:02 +02:00
void CFM : : insertDelay ( uint16_t ms )
{
uint32_t nSamples = ms * 24U ;
for ( uint32_t i = 0U ; i < nSamples ; i + + )
2020-05-09 14:06:32 +02:00
m_inputRFRB . put ( 0 ) ;
2020-05-09 14:01:02 +02:00
}
2020-05-08 16:48:03 +02:00
void CFM : : insertSilence ( uint16_t ms )
{
uint32_t nSamples = ms * 24U ;
for ( uint32_t i = 0U ; i < nSamples ; i + + )
2020-05-09 08:00:24 +02:00
m_outputRFRB . put ( 0 ) ;
2020-05-08 16:48:03 +02:00
}