<|start_filename|>src/time/gngll_datetime_extract.h<|end_filename|>
#pragma once
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include "gps_datetime_extract_base.h"

class GNGLLDatetimeExtract : public GpsDatetimeExtractBase
{
public:
    GNGLLDatetimeExtract();
    void onCharReceived(char ch) override;

private:
    enum State {
        stWaitLine,
        stWaitTime,
        stGrabTime,
    };

    State m_state;
    int m_numCommas;
    int m_pos;
    char m_rmc[6];
    char m_tmpDatetime[6 + 6];
};


<|start_filename|>src/utils/jt_band_params.cpp<|end_filename|>
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include <stdint.h>
#include "jt_band_params.h"

#define DEFAULT_XTAL_FREQUENCY_IN_KHZ (27000)

//--------------------------------------------------------------------------------
JTBandParams::JTBandParams()
    : _jt_mode(0)
    , _pll_a(0)
    , _pll_b(0)
    , _pll_c(1)
    , _msync_div(1)
    , _r_div(1)
    , _baud_rate_dividend(1)
    , _baud_rate_divisor(1)
    , _tr_interval(0)
    , _xtal_frequency_in_KHz(DEFAULT_XTAL_FREQUENCY_IN_KHZ)
{

}

//--------------------------------------------------------------------------------
void JTBandParams::initFromJTBandDescr(JTBandDescr& bandDescr)
{
    _jt_mode = bandDescr.mode & 0x3f;
    _pll_a = bandDescr.pll_a;
    _pll_b = bandDescr.pll_b;
    _pll_c = bandDescr.pll_c;
    _msync_div = bandDescr.msync_div;
    _r_div = bandDescr.r_div;
    _baud_rate_dividend = bandDescr.baud_rate_dividend;
    _baud_rate_divisor = bandDescr.baud_rate_divisor;
    _tr_interval = bandDescr.tr_interval;
}

//--------------------------------------------------------------------------------
void JTBandParams::setXTALFrequencyInKHz(unsigned long frequencyInKHz)
{
    _xtal_frequency_in_KHz = frequencyInKHz;
}

//--------------------------------------------------------------------------------
void JTBandParams::getPLLParamsForSymbol(unsigned symbol, uint16_t& a, uint32_t& b, uint32_t& c) const
{
    a = _pll_a;
    b = _pll_b + symbol;
    c = _pll_c;
}

//--------------------------------------------------------------------------------
unsigned JTBandParams::approxFrequencyInMHz() const
{
    unsigned long res = getFrequencyInKHz() / 1000;
    return static_cast<unsigned>( res );
}

//--------------------------------------------------------------------------------
unsigned long JTBandParams::getFrequencyInKHz() const
{
    unsigned long xtalFreq = _xtal_frequency_in_KHz;
    unsigned long m = 1000;
    unsigned long f = m * _pll_b / _pll_c;
    unsigned long res = ( xtalFreq * _pll_a + xtalFreq * f / m ) / _msync_div / _r_div ;

    return res;
}

//--------------------------------------------------------------------------------
RF_Band JTBandParams::getBand() const
{
    
    unsigned long freq = getFrequencyInKHz();
    RF_Band res;

    if( freq < 138 )
    {
        res = RF_Band_2200m;
    }
    else if( freq < 480 )
    {
        res = RF_Band_600m;    
    }
    else if( freq < 2000 )
    {
        res = RF_Band_160m;    
    }
    else if( freq < 3800 )
    {
        res = RF_Band_80m;    
    }
    else if( freq < 5450 )
    {
        res = RF_Band_60m;    
    }
    else if( freq < 7300 )
    {
        res = RF_Band_40m;    
    }
    else if( freq < 10150 )
    {
        res = RF_Band_30m;    
    }
    else if( freq < 14350 )
    {
        res = RF_Band_20m;    
    }
    else if( freq < 18168 )
    {
        res = RF_Band_17m;    
    }
    else if( freq < 21450 )
    {
        res = RF_Band_15m;    
    }
    else if( freq < 24990 )
    {
        res = RF_Band_12m;    
    }
    else if( freq < 29700 )
    {
        res = RF_Band_10m;    
    }
    else if( freq < 54000 )
    {
        res = RF_Band_6m;    
    }
    else if( freq < 146000 )
    {
        res = RF_Band_2m;    
    }
    else
    {
        res = RF_Band_Unknown;
    }

    return res;
}



//--------------------------------------------------------------------------------
JTMode JTBandParams::getJTMode() const
{
    return static_cast<JTMode>( _jt_mode );
}


<|start_filename|>src/time/gps_datetime_extract_base.cpp<|end_filename|>
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include "gps_datetime_extract_base.h"
#include <string.h>

//---------------------------------------------------------------------------------
GpsDatetimeExtractBase::GpsDatetimeExtractBase()
    : m_isDateTimePresent(false)
{
}

//---------------------------------------------------------------------------------
void GpsDatetimeExtractBase::invalidate()
{
    m_isDateTimePresent = false;
}

//---------------------------------------------------------------------------------
bool GpsDatetimeExtractBase::isDateTimePresent() const
{
    return m_isDateTimePresent;
}

//---------------------------------------------------------------------------------
const char* GpsDatetimeExtractBase::getDateTimeAsShortString() const
{
    return m_datetime;
}

//---------------------------------------------------------------------------------
void GpsDatetimeExtractBase::putDateTime(char * datetime)
{
    // validation
    bool valid = true;
    for (size_t idx = 0; idx < 12; idx++)
    {
        if (datetime[idx]<'0' || datetime[idx]>'9')
        {
            valid = false;
            break;
        }
    }

    if (!valid)
    {
        m_isDateTimePresent = false;
        return;
    }

    m_isDateTimePresent = true;
    memcpy(m_datetime, datetime, 12);
    m_datetime[12] = 0; // ASCIIZ null terminated 

    m_isEventPresent = true; // set flag of event logic
}

//---------------------------------------------------------------------------------
bool GpsDatetimeExtractBase::isEventPresent() const
{
    return m_isEventPresent;
}

//---------------------------------------------------------------------------------
void GpsDatetimeExtractBase::resetEvent()
{
    m_isEventPresent = false;
}


<|start_filename|>src/time/gprmc_datetime_extract.cpp<|end_filename|>
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include "gprmc_datetime_extract.h"
#include <string.h>

#define MYSWAP(A, B) do{char tmp=A; A=B; B=tmp;}while(0)

//---------------------------------------------------------------------------------
GPRMCDatetimeExtract::GPRMCDatetimeExtract()
    : GpsDatetimeExtractBase()
    , m_state(stWaitLine)
    , m_numCommas(0)
    , m_pos(0)
{
}

//---------------------------------------------------------------------------------
void GPRMCDatetimeExtract::onCharReceived(char ch)
{
    for (size_t idx = 1; idx < 6; idx++)
    {
        m_rmc[idx - 1] = m_rmc[idx];
    }
    m_rmc[5] = ch;

    if (strncmp(m_rmc, "$GPRMC", 6) == 0)
    {
        m_state = stWaitTime;
        m_numCommas = 0;
        memset(m_tmpDatetime, '0', 12);
        return;
    }

    // "$GPRMC,225446,A,5321.60231,N,08440.82524,W,000.5,054.7,191194,020.3,E*68";

    if (m_state == stWaitTime)
    {
        if (ch != ',')
        {
            invalidate();
            m_state = stWaitLine;
            return;
        }
        m_numCommas++;
        m_state = stGrabTime;
        m_pos = 0;
    }
    else if (m_state == stGrabTime)
    {
        if (ch == ',')
        {
            // unexpected
            invalidate();
            m_state = stWaitLine;
            return;
        }

        m_tmpDatetime[6 + m_pos] = ch;
        m_pos++;
        if (m_pos == 6)
        {
            m_state = stWaitDate;
        }
    }
    else if (m_state == stWaitDate)
    {
        if (ch == ',')
            m_numCommas++;

        if (m_numCommas == 9)
        {
            m_state = stGrabDate;
            m_pos = 0;
        }
    }
    else if (m_state == stGrabDate)
    {
        if (ch == ',')
        {
            // unexpected
            invalidate();
            m_state = stWaitLine;
            return;
        }

        m_tmpDatetime[m_pos] = ch;
        m_pos++;
        if (m_pos == 6)
        {
            // convert date. example 311216 -> 161231
            MYSWAP(m_tmpDatetime[0], m_tmpDatetime[4]);
            MYSWAP(m_tmpDatetime[1], m_tmpDatetime[5]);

            // put catched datetiome to base class
            putDateTime(m_tmpDatetime);
            m_state = stWaitLine;
        }
    }

}

<|start_filename|>src/time/gps_datetime_extract_base.h<|end_filename|>
#pragma once
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

class GpsDatetimeExtractBase
{
public:
    GpsDatetimeExtractBase();
    bool isDateTimePresent() const;
    const char* getDateTimeAsShortString() const;

    virtual void onCharReceived(char ch) = 0;

    bool isEventPresent() const;
    void resetEvent();

protected:
    void invalidate();
    void putDateTime(char * datetime);

private:
    bool m_isDateTimePresent;
    bool m_isEventPresent;
    char m_datetime[6 + 6 + 1];

};


<|start_filename|>src/jt_encoder/jt_wspr_encoder.cpp<|end_filename|>
//
//
// Original fortran code by <NAME>, K1JT
//
// Ported to C/C++ by <NAME>, RA9YER <<EMAIL>>
//
// License: MIT
//

#include <string.h>
#include <stdlib.h>

#include "jt_wspr_encoder.h"
#include "pack_util.h"


#ifndef UINT32_MAX
#define UINT32_MAX (0xffffffff)
#endif


#define INRANGE(X, L, R)  (X >= L && X <= R)
#define EXACT(X, VAL) (X == VAL)


const unsigned char JTWSPREncoder::_sync[] = {
    0xc0, 0x8e, // 0b11000000, 0b10001110,
    0x25, 0xe0, // 0b00100101, 0b11100000,
    0x25, 0x02, // 0b00100101, 0b00000010,
    0xcd, 0x1a, // 0b11001101, 0b00011010,
    0x1a, 0xa9, // 0b00011010, 0b10101001,
    0x2c, 0x6a, // 0b00101100, 0b01101010,
    0x20, 0x93, // 0b00100000, 0b10010011,
    0xb3, 0x47, // 0b10110011, 0b01000111,
    0x05, 0x30, // 0b00000101, 0b00110000,
    0x1a, 0xc6, // 0b00011010, 0b11000110,
    0x0  //0b00000000
};

//-----------------------------------------------------------------------
JTWSPREncoder::JTWSPREncoder(void)
    : JTEncodeBase()
    , _ok(false)
    , _num_symbols(0)
{
}


//-----------------------------------------------------------------------
JTWSPREncoder::~JTWSPREncoder(void)
{
}

//----------------------------------------------------------------------------------------------
static int nchar(char ch)
{
    if( ch >='0' && ch <='9' )
    {
        return ch - '0';
    }
    else if( ch >='A' && ch <='Z' )
    {
        return ch - 'A' + 10;
    }
    else if( ch >='a' && ch <='z' )
    {
        return ch - 'a' + 10;
    }
    else if( ch == ' ' )
    {
        return 36;
    }

    // invalid char. should never happen
    return -1;
}

//----------------------------------------------------------------------------------------------
static int nchar2(char ch)
{
    if( ch >='0' && ch <='9' )
    {
        return ch - '0';
    }
    else if( ch >='A' && ch <='Z' )
    {
        return ch - 'A' + 10;
    }

    return 37;
}


//----------------------------------------------------------------------------------------------
static void decodeGrid6(const char* grid, int& outLon, int& outLat )
{
    // grid must be valid! like AA00aa

    // TODO: not exact fortran code due to lack of double type

    // grid4
    int nlon = 180 - 20 * ( grid[0] - 'A' ) - 2 * ( grid[2] - '0' ) ;
    int nlat = -90 + 10 * ( grid[1] - 'A' ) + ( grid[3] - '0' );

    // grid6
    nlon -= 50 * (grid[4] - 'a') / 600;
    nlat += 25 * (grid[5] - 'a') / 600;

    outLon = nlon;
    outLat = nlat;
}

//----------------------------------------------------------------------------------------------
static int parity(uint32_t data)
{
    int parity = 0;
    uint32_t ldata = data;
    while( ldata )
    {
        parity = 1 - parity;
        ldata = ldata & (ldata - 1);
    }

    return parity;
}

//----------------------------------------------------------------------------------------------
uint32_t JTWSPREncoder::packcall( const char* callsign ) const
{
    size_t len = strlen(callsign);
    if( len > 6 )
        return UINT32_MAX;

    uint32_t nbase = (uint32_t)37 * 36 * 10 * 27 * 27 * 27;

    char lcallsign[7];
    memset(lcallsign, ' ', 6);
    lcallsign[6] = 0;


    // special condition

    if( !strncmp(callsign, "3DA0", 4) )
    {
        // Work-around for Swaziland prefix
        strncpy(lcallsign, "3D0", 3);
        strncpy(&lcallsign[3], &callsign[4], len-4);
    }
    else if( !strncmp(callsign, "CQ ", 3) )
    {
        strncpy(lcallsign, callsign, len);
        uint32_t ncall = nbase + 1;
        if( lcallsign[3]>='0' && lcallsign[3]<='9'
            && lcallsign[4]>='0' && lcallsign[4]<='9'
            && lcallsign[5]>='0' && lcallsign[5]<='9' )
        {
            int nfreq = atoi( &lcallsign[3] );
            ncall = nbase + 3 + nfreq;
        }
        return ncall;
    }
    else if( !strncmp(callsign, "QRZ ", 4) )
    {
        uint32_t ncall = nbase + 2;
        return ncall;
    }
    else
    {
        strncpy(lcallsign, callsign, len);
    }

    // short callsign modification

    if( lcallsign[2] >= '0' && lcallsign[2] <= '9' )
    {
        // valid
    }
    else if( lcallsign[1] >= '0' && lcallsign[1] <= '9' )
    {
        if( lcallsign[5] == ' ' )
        {
            // move right
            for( int i=5; i>0; i-- )
            {
                lcallsign[i] = lcallsign[i-1];
            }
            lcallsign[0] = ' ';
        }
        else
        {
            // not valid
            return UINT32_MAX;
        }
    }

    // convert to uppercase
    for( size_t i=0; i<6; i++ )
    {
        if( lcallsign[i] >= 'a' && lcallsign[i] <= 'z' )
        {
            lcallsign[i] &= ~0x20;
        }
    }

    // validation
    if( !(INRANGE(lcallsign[0], 'A', 'Z') || INRANGE(lcallsign[0], '0', '9') || EXACT(lcallsign[0], ' ') ) )
        return UINT32_MAX;
    if( !(INRANGE(lcallsign[1], 'A', 'Z') || INRANGE(lcallsign[1], '0', '9') ) )
        return UINT32_MAX;
    if( !(INRANGE(lcallsign[2], '0', '9') ) )
        return UINT32_MAX;
    if( !(INRANGE(lcallsign[3], 'A', 'Z') || EXACT(lcallsign[3], ' ') ) )
        return UINT32_MAX;
    if( !(INRANGE(lcallsign[4], 'A', 'Z') || EXACT(lcallsign[4], ' ') ) )
        return UINT32_MAX;
    if( !(INRANGE(lcallsign[5], 'A', 'Z') || EXACT(lcallsign[5], ' ') ) )
        return UINT32_MAX;

    // extra validation
    for( size_t i=0; i<6; i++ )
    {
        if( nchar( lcallsign[i] ) == -1 )
            return UINT32_MAX;
    }

    // calculate result
    uint32_t res;
    res =          nchar(lcallsign[0]);
    res = 36*res + nchar(lcallsign[1]);
    res = 10*res + nchar(lcallsign[2]);
    res = 27*res + nchar(lcallsign[3]) - 10;
    res = 27*res + nchar(lcallsign[4]) - 10;
    res = 27*res + nchar(lcallsign[5]) - 10;

    return res;

}

//-----------------------------------------------------------------------
int JTWSPREncoder::packgrid( const char* grid ) const
{

    int nbase = 180*180;

    if( !strcmp(grid, "    ") )
        return nbase + 1;

    // Test for numerical signal report, etc.
    if( !strncmp(grid, "-", 1) )
    {
        int n = atoi( &grid[1] );
        return nbase + 1 + n;
    }
    else if( !strncmp(grid, "R-", 2) )
    {
        int n = atoi( &grid[2] );
        if( n == 0 )
        {
            return nbase + 1;
        }
        else
        {
            return nbase + 31 + n;
        }
    }
    else if( !strncmp(grid, "RO", 2) )
    {
        return nbase + 62;
    }
    else if( !strncmp(grid, "RRR", 3) )
    {
        return nbase + 63;
    }
    else if( !strncmp(grid, "73", 2) )
    {
        return nbase + 64;
    }

    // validation
    size_t len = strlen(grid);
    if( len!=4 )
        return -1;

    if( !(INRANGE(grid[0], 'A', 'R') ) )
        return -1;
    if( !(INRANGE(grid[1], 'A', 'R') ) )
        return -1;
    if( !(INRANGE(grid[2], '0', '9') ) )
        return -1;
    if( !(INRANGE(grid[3], '0', '9') ) )
        return -1;

    char lgrid[7];
    strncpy(lgrid, grid, len);
    strncpy(&lgrid[len], "mm\0", 3);

    int lon = 0;
    int lat = 0;
    decodeGrid6( lgrid, lon, lat );

    int res = ((lon+180)/2)*180 + (lat + 90) ;
    return res;

}

//-----------------------------------------------------------------------
int JTWSPREncoder::packdbm( int dbm ) const
{
    int res = (dbm < 0)? 0 : dbm;
    res = (res > 60)? 60 : res;

    int corr[10] = {0,-1,1,0,-1,2,1,0,-1,1};
    res = res + corr[ res % 10 ];
    return res;
}

//-----------------------------------------------------------------------
void JTWSPREncoder::pack50( uint32_t n, uint32_t m, unsigned char* out )
{
    out[0] = n >> 20;
    out[1] = n >> 12;
    out[2] = n >> 4;
    out[3] = (n & 0xf) << 4 | ((m >> 18) & 0xf);
    out[4] = m >> 10;
    out[5] = m >> 2;
    out[6] = (m & 0x3) << 6;
    out[7] = 0;
    out[8] = 0;
    out[9] = 0;
    out[10] = 0;
}


//-----------------------------------------------------------------------
void JTWSPREncoder::encode232(const unsigned char* dat, size_t nbytes, unsigned char* symbol) const
{
    const uint32_t npoly1 = -221228207;
    const uint32_t npoly2 = -463389625;
    uint32_t nstate=0;
    size_t k=0;
    for(size_t j=0; j<nbytes; j++)
    {
        for(size_t i=0; i<8; i++)
        {
            unsigned char i4 = dat[j];
            nstate = (nstate << 1) | ((i4 >> (7-i)) & 0x1);
            PackUtil::pack1b(symbol, k, parity(nstate & npoly1));
            k += 1;
            PackUtil::pack1b(symbol, k, parity(nstate & npoly2));
            k += 1;
        }
    }

}


//-----------------------------------------------------------------------
unsigned char JTWSPREncoder::interMeptMap(size_t idx) const
{
    size_t k = 0;
    for(unsigned i = 0; i<255; i++)
    {
        unsigned n=0;
        unsigned ii=i;
        for(size_t j=0; j<8; j++)
        {
            n = n + n;
            if(ii & 0x1)
            {
                n += 1;
            }
            ii = ii >> 1;
        }

        if(n < 162)
        {
            if(k == idx)
            {
                return n;
            }
            k += 1;
        }
    }

    return 0; // never happen
}


//-----------------------------------------------------------------------
void JTWSPREncoder::inter_mept(const unsigned char* dat, int direction, unsigned char* res) const
{
    if( direction )
    {
        for(size_t i=0; i<162; i++ )
        {
            PackUtil::pack1b(res, interMeptMap(i), PackUtil::unpack1b(dat, i) );
        }
    }
    else
    {
        for(size_t i=0; i<162; i++ )
        {
            PackUtil::pack1b(res, i, PackUtil::unpack1b(dat, interMeptMap(i)) );
        }
    }
}


//-----------------------------------------------------------------------
void JTWSPREncoder::make_channel_symbols(const unsigned char* dat, unsigned char* res) const
{
    size_t pos=0;
    size_t k=0;
    unsigned char s=_sync[pos];
    for(size_t i=0; i<162; i++)
    {
        unsigned char v = 2 * PackUtil::unpack1b(dat, i) + ((s & 0x80) ? 1 : 0);
        PackUtil::pack2b(res, i, v);

        s = s << 1;
        k++;
        if(k==8)
        {
            k = 0;
            pos++;
            s = _sync[pos];
        }
    }
}


//-----------------------------------------------------------------------
void JTWSPREncoder::store_as_internal_symbols(const unsigned char* channel_symbols)
{
    for (size_t i = 0; i < 162; i++)
    {
        PackUtil::pack2b(_internal_symbols, i, PackUtil::unpack2b(channel_symbols, i));
    }
}



//-----------------------------------------------------------------------
unsigned char JTWSPREncoder::get_packed_symbol( unsigned pos ) const
{
    return PackUtil::unpack2b(_internal_symbols, pos);
}

//-----------------------------------------------------------------------
void JTWSPREncoder::internalEncodeData(const unsigned char* data, int num_bits)
{
    size_t nbits = 50 + 31;
    size_t nbytes = (nbits % 8 == 0)? nbits/8 : nbits/8 + 1;

    unsigned char dat1[176 / 4];  // 11*8*2
    unsigned char dat2[162 / 8 + 1];


    memset(dat1,0, sizeof(dat1));
    encode232(data, nbytes, dat1);  // 11bytes -> 176 bits  (1bit)

    memset(dat2,0, sizeof(dat2));
    inter_mept(dat1, 1, dat2);  // 162bits (1bit) -> 162bits (1bit)

    memset(dat1,0, sizeof(dat1));
    make_channel_symbols(dat2, dat1);  //162bits (1bit) -> 162bits (2bit)

    store_as_internal_symbols(dat1); // 162bits (2bit) -> 162bits (2bit)

    _ok = true;
    _num_symbols = 162;

}


//-----------------------------------------------------------------------
bool JTWSPREncoder::encodeExtendedLocation( const char* callsign, const char* location, int dbm )
{
    // callsign - callsign with or without prefix. Callsign will be send as hash.
    // location - 6 chars example: NO13ui
    // dbm - output power. 0 .. 60

    _ok = false;
    _num_symbols = 0;

    // TODO: implement

    return _ok;
}


//-----------------------------------------------------------------------
bool JTWSPREncoder::encodeMessage( const char* callsign, const char* location, int dbm )
{
    // callsign - valid callsign without prefix/suffix. Examples: AA9AAA, A9AAA, 0A9AAA, AA0AA, A0A, A0
    // location - 4 chars example: NO13
    // dbm - output power. 0 .. 60

    _ok = false;
    _num_symbols = 0;

    uint32_t res_N = packcall( callsign );
    int res2 = packgrid( location );
    int res3 = packdbm( dbm );

    if( res_N == UINT32_MAX || res2 == -1 )
        return false;

    uint32_t res_M = (uint32_t)128 * (uint32_t)res2 + (res3 + 64);

    unsigned char data0[11];
    memset(data0,0, sizeof(data0));
    pack50(res_N, res_M, data0 );

    internalEncodeData(data0, 50);
    _ntype = res3;


    return _ok;
}


//-----------------------------------------------------------------------
bool JTWSPREncoder::encodeExtendedCallsign( const char* callsign, int dbm )
{
    // callsign - valid callsign with prefix/suffix. Examples: ABC/AA9AAA, 000/AA9AAA, 0/AA9AAA, AA9AAA/M, AA9AAA/10
    // dbm - output power. 0 .. 60

    _ok = false;
    _num_symbols = 0;

    const char* pos1 = strstr(callsign, "/");
    if( !pos1 )
        return false;

    char lcallsign[7];

    uint32_t res_N = 0;
    uint32_t ng = 0;
    int nadd = 0;

    size_t lenAfterSlash = strlen(pos1) - 1;
    size_t lenBeforeSlash = pos1 - callsign;

    if( lenAfterSlash == 1)
    {
        // single char suffix
        size_t len = lenBeforeSlash;

        strncpy(lcallsign, callsign, len);
        lcallsign[len] = 0;


        int n = nchar2( *(pos1+1) );
        ng = (uint32_t)60000 - 32768 + n; //

        nadd = 1;
    }
    else if( lenAfterSlash == 2 )
    {
        // Two-character numerical suffix, /10 to /99
        size_t len = lenBeforeSlash;

        strncpy(lcallsign, callsign, len);
        lcallsign[len] = 0;

        int n = atoi( pos1 + 1 );
        ng = (uint32_t)60000 + 26 + n;
        nadd = 1;
    }
    else if( lenBeforeSlash > 0 && lenBeforeSlash < 4 )
    {
        // Prefix of 1 to 3 characters
        size_t len = lenAfterSlash;
        strncpy(lcallsign, pos1+1, len);
        lcallsign[len] = 0;

        char pfx[3];
        memset(pfx, ' ', sizeof(pfx));
        strncpy( pfx + 3 - lenBeforeSlash, callsign, lenBeforeSlash );

        for(size_t i=0; i<3; i++ )
        {
            ng = 37 * ng + nchar( pfx[i] );
        }

        if( ng < 32768 )
        {
            nadd = 0;
        }
        else
        {
            ng = ng - 32768;
            nadd = 1;
        }
    }
    else
    {
        return false;
    }

    res_N = packcall( lcallsign ); //
    if( res_N == UINT32_MAX )
        return false;

    int res3 = packdbm( dbm );
    if( res3 == -1 )
        return false;

    int ntype = res3 + 1 + nadd;
    uint32_t n2 = (uint32_t)128 * ng + ntype + 64;
    unsigned char data0[11];
    memset(data0, 0, sizeof(data0));
    pack50(res_N, n2, data0 );

    internalEncodeData(data0, 50);
    _ntype = ntype;


    return _ok;
}


<|start_filename|>src/si5351/radio/Si5351A-RevB-Registers.h<|end_filename|>
/*
 * Si5351A Rev B Configuration Register Export Header File
 *
 * This file represents a series of Silicon Labs Si5351A Rev B 
 * register writes that can be performed to load a single configuration 
 * on a device. It was created by a Silicon Labs ClockBuilder Pro
 * export tool.
 *
 * Part:		                                       Si5351A Rev B
 * Design ID:                                          
 * Includes Pre/Post Download Control Register Writes: Yes
 * Created By:                                         ClockBuilder Pro v2.38 [2019-10-31]
 * Timestamp:                                          2019-11-17 07:19:30 GMT+07:00
 *
 * A complete design report corresponding to this export is included at the end 
 * of this header file.
 *
 */

#ifndef SI5351A_REVB_REG_CONFIG_HEADER
#define SI5351A_REVB_REG_CONFIG_HEADER

#define SI5351A_REVB_REG_CONFIG_NUM_REGS				41

typedef struct
{
	unsigned int address; /* 16-bit register address */
	unsigned char value; /* 8-bit register data */

} si5351a_revb_register_t;

si5351a_revb_register_t const code si5351a_revb_registers[SI5351A_REVB_REG_CONFIG_NUM_REGS] =
{
	{ 0x0002, 0x53 },
	{ 0x0003, 0x00 },
	{ 0x0007, 0x00 },
	{ 0x000F, 0x00 },
	{ 0x0010, 0x0F },
	{ 0x0011, 0x8C },
	{ 0x0012, 0x8C },
	{ 0x0013, 0x8C },
	{ 0x0014, 0x8C },
	{ 0x0015, 0x8C },
	{ 0x0016, 0x8C },
	{ 0x0017, 0x8C },
	{ 0x001A, 0x00 },
	{ 0x001B, 0x36 },
	{ 0x001C, 0x00 },
	{ 0x001D, 0x0E },
	{ 0x001E, 0xA8 },
	{ 0x001F, 0x00 },
	{ 0x0020, 0x00 },
	{ 0x0021, 0x10 },
	{ 0x002A, 0x00 },
	{ 0x002B, 0x02 },
	{ 0x002C, 0x00 },
	{ 0x002D, 0x3E },
	{ 0x002E, 0x40 },
	{ 0x002F, 0x00 },
	{ 0x0030, 0x00 },
	{ 0x0031, 0x00 },
	{ 0x005A, 0x00 },
	{ 0x005B, 0x00 },
	{ 0x0095, 0x00 },
	{ 0x0096, 0x00 },
	{ 0x0097, 0x00 },
	{ 0x0098, 0x00 },
	{ 0x0099, 0x00 },
	{ 0x009A, 0x00 },
	{ 0x009B, 0x00 },
	{ 0x00A2, 0x00 },
	{ 0x00A3, 0x00 },
	{ 0x00A4, 0x00 },
	{ 0x00B7, 0x92 },

};

/*
 * Design Report
 *
 * Overview
 * ========
 * Part:               Si5351A
 * Created By:         ClockBuilder Pro v2.38 [2019-10-31]
 * Timestamp:          2019-11-17 07:19:30 GMT+07:00
 * 
 * Design Rule Check
 * =================
 * Errors:
 * - No errors
 * 
 * Warnings:
 * - No warnings
 * 
 * Design
 * ======
 * Inputs:
 *     IN0: 27 MHz
 * 
 * Outputs:
 *    OUT0: 7 MHz
 *          Enabled LVCMOS 8 mA
 *          Offset 0.000 s 
 *    OUT1: Unused
 *    OUT2: Unused
 * 
 * Frequency Plan
 * ==============
 * PLL_A:
 *    Enabled Features = None
 *    Fvco             = 899.5 MHz
 *    M                = 33.3148148148148148... [ 33 + 17/54 ]
 *    Input0:
 *       Source           = Crystal
 *       Source Frequency = 27 MHz
 *       Fpfd             = 27 MHz
 *       Load Capacitance = Load_08pF
 *    Output0:
 *       Features       = None
 *       Disabled State = StopLow
 *       R              = 1  (2^0)
 *       Fout           = 7 MHz
 *       N              = 128.5
 * 
 * Settings
 * ========
 * 
 * Location      Setting Name   Decimal Value      Hex Value        
 * ------------  -------------  -----------------  -----------------
 * 0x0002[3]     XO_LOS_MASK    0                  0x0              
 * 0x0002[4]     CLK_LOS_MASK   1                  0x1              
 * 0x0002[5]     LOL_A_MASK     0                  0x0              
 * 0x0002[6]     LOL_B_MASK     1                  0x1              
 * 0x0002[7]     SYS_INIT_MASK  0                  0x0              
 * 0x0003[7:0]   CLK_OEB        0                  0x00             
 * 0x0007[7:4]   I2C_ADDR_CTRL  0                  0x0              
 * 0x000F[2]     PLLA_SRC       0                  0x0              
 * 0x000F[3]     PLLB_SRC       0                  0x0              
 * 0x000F[4]     PLLA_INSELB    0                  0x0              
 * 0x000F[5]     PLLB_INSELB    0                  0x0              
 * 0x000F[7:6]   CLKIN_DIV      0                  0x0              
 * 0x0010[1:0]   CLK0_IDRV      3                  0x3              
 * 0x0010[3:2]   CLK0_SRC       3                  0x3              
 * 0x0010[4]     CLK0_INV       0                  0x0              
 * 0x0010[5]     MS0_SRC        0                  0x0              
 * 0x0010[6]     MS0_INT        0                  0x0              
 * 0x0010[7]     CLK0_PDN       0                  0x0              
 * 0x0011[1:0]   CLK1_IDRV      0                  0x0              
 * 0x0011[3:2]   CLK1_SRC       3                  0x3              
 * 0x0011[4]     CLK1_INV       0                  0x0              
 * 0x0011[5]     MS1_SRC        0                  0x0              
 * 0x0011[6]     MS1_INT        0                  0x0              
 * 0x0011[7]     CLK1_PDN       1                  0x1              
 * 0x0012[1:0]   CLK2_IDRV      0                  0x0              
 * 0x0012[3:2]   CLK2_SRC       3                  0x3              
 * 0x0012[4]     CLK2_INV       0                  0x0              
 * 0x0012[5]     MS2_SRC        0                  0x0              
 * 0x0012[6]     MS2_INT        0                  0x0              
 * 0x0012[7]     CLK2_PDN       1                  0x1              
 * 0x0013[1:0]   CLK3_IDRV      0                  0x0              
 * 0x0013[3:2]   CLK3_SRC       3                  0x3              
 * 0x0013[4]     CLK3_INV       0                  0x0              
 * 0x0013[5]     MS3_SRC        0                  0x0              
 * 0x0013[6]     MS3_INT        0                  0x0              
 * 0x0013[7]     CLK3_PDN       1                  0x1              
 * 0x0014[1:0]   CLK4_IDRV      0                  0x0              
 * 0x0014[3:2]   CLK4_SRC       3                  0x3              
 * 0x0014[4]     CLK4_INV       0                  0x0              
 * 0x0014[5]     MS4_SRC        0                  0x0              
 * 0x0014[6]     MS4_INT        0                  0x0              
 * 0x0014[7]     CLK4_PDN       1                  0x1              
 * 0x0015[1:0]   CLK5_IDRV      0                  0x0              
 * 0x0015[3:2]   CLK5_SRC       3                  0x3              
 * 0x0015[4]     CLK5_INV       0                  0x0              
 * 0x0015[5]     MS5_SRC        0                  0x0              
 * 0x0015[6]     MS5_INT        0                  0x0              
 * 0x0015[7]     CLK5_PDN       1                  0x1              
 * 0x0016[1:0]   CLK6_IDRV      0                  0x0              
 * 0x0016[3:2]   CLK6_SRC       3                  0x3              
 * 0x0016[4]     CLK6_INV       0                  0x0              
 * 0x0016[5]     MS6_SRC        0                  0x0              
 * 0x0016[6]     FBA_INT        0                  0x0              
 * 0x0016[7]     CLK6_PDN       1                  0x1              
 * 0x0017[1:0]   CLK7_IDRV      0                  0x0              
 * 0x0017[3:2]   CLK7_SRC       3                  0x3              
 * 0x0017[4]     CLK7_INV       0                  0x0              
 * 0x0017[5]     MS7_SRC        0                  0x0              
 * 0x0017[6]     FBB_INT        0                  0x0              
 * 0x0017[7]     CLK7_PDN       1                  0x1              
 * 0x001C[17:0]  MSNA_P1        3752               0x00EA8          
 * 0x001F[19:0]  MSNA_P2        16                 0x00010          
 * 0x001F[23:4]  MSNA_P3        54                 0x00036          
 * 0x002C[17:0]  MS0_P1         15936              0x03E40          
 * 0x002F[19:0]  MS0_P2         0                  0x00000          
 * 0x002F[23:4]  MS0_P4         2                  0x00002          
 * 0x005A[7:0]   MS6_P2         0                  0x00             
 * 0x005B[7:0]   MS7_P2         0                  0x00             
 * 0x0095[14:0]  SSDN_P2        0                  0x0000           
 * 0x0095[7]     SSC_EN         0                  0x0              
 * 0x0097[14:0]  SSDN_P3        0                  0x0000           
 * 0x0097[7]     SSC_MODE       0                  0x0              
 * 0x0099[11:0]  SSDN_P1        0                  0x000            
 * 0x009A[15:4]  SSUDP          0                  0x000            
 * 0x00A2[21:0]  VCXO_PARAM     0                  0x000000         
 * 0x00B7[7:6]   XTAL_CL        2                  0x2
 * 
 *
 */

#endif

<|start_filename|>src/time/gps_latlon_extract_base.cpp<|end_filename|>
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//
#include "gps_latlon_extract_base.h"

#include <string.h>
#include <inttypes.h>


#define ENSURE_COMMA do{char ch=m_tmpBuf[pos++]; if(ch!=',') return; }while(0)
#define ENSURE_POINT do{char ch=m_tmpBuf[pos++]; if(ch!='.') return; }while(0)
#define ENSURE_DIGIT_AND_CATCH(DST) do{char ch=m_tmpBuf[pos++]; if(ch<'0' || ch>'9') return; DST = ch; }while(0)
#define ENSURE_EITHER_AND_CATCH(A, B, DST) do{char ch=m_tmpBuf[pos++]; if(ch!=A && ch!=B) return; DST = ch;}while(0)

//---------------------------------------------------------------------------------
GpsLatLonExtractBase::GpsLatLonExtractBase()
    : m_validLatLonPresent(false)
{
}

//---------------------------------------------------------------------------------
void GpsLatLonExtractBase::invalidate()
{
    m_validLatLonPresent = false;
}

//---------------------------------------------------------------------------------
void GpsLatLonExtractBase::decodeLatLon()
{
    m_validLatLonPresent = false; // nonvalid so far

    char latDegree[2];
    char latMinutesInt[2];
    char latMinutesFrac[MAX_FRAC_CHARS];
    char northORSouth;

    char lonDegree[3];
    char lonMinutesInt[2];
    char lonMinutesFrac[MAX_FRAC_CHARS];
    char westOREast;


    // Validate and catch. Buffer should look like "4916.45,N,12311.12,W"

    size_t pos = 0; // 
    ENSURE_DIGIT_AND_CATCH(latDegree[0]);
    ENSURE_DIGIT_AND_CATCH(latDegree[1]);
    ENSURE_DIGIT_AND_CATCH(latMinutesInt[0]);
    ENSURE_DIGIT_AND_CATCH(latMinutesInt[1]);
    ENSURE_POINT;
    memset(latMinutesFrac, '0', sizeof(latMinutesFrac));
    for (size_t idx = 0; idx < sizeof(latMinutesFrac); idx++)
    {
        if (m_tmpBuf[pos] == ',')
            break;
        ENSURE_DIGIT_AND_CATCH(latMinutesFrac[idx]);
    }
    ENSURE_COMMA;
    ENSURE_EITHER_AND_CATCH('N', 'S', northORSouth);
    ENSURE_COMMA;
    ENSURE_DIGIT_AND_CATCH(lonDegree[0]);
    ENSURE_DIGIT_AND_CATCH(lonDegree[1]);
    ENSURE_DIGIT_AND_CATCH(lonDegree[2]);
    ENSURE_DIGIT_AND_CATCH(lonMinutesInt[0]);
    ENSURE_DIGIT_AND_CATCH(lonMinutesInt[1]);
    ENSURE_POINT;
    memset(lonMinutesFrac, '0', sizeof(lonMinutesFrac));
    for (size_t idx = 0; idx < sizeof(lonMinutesFrac); idx++)
    {
        if (m_tmpBuf[pos] == ',')
            break;
        ENSURE_DIGIT_AND_CATCH(lonMinutesFrac[idx]);
    }
    ENSURE_COMMA;
    ENSURE_EITHER_AND_CATCH('W', 'E', westOREast);

    // copy to ready data
    memcpy(m_latDegree, latDegree, sizeof(m_latDegree));
    memcpy(m_latMinutesInt, latMinutesInt, sizeof(m_latMinutesInt));
    memcpy(m_latMinutesFrac, latMinutesFrac, sizeof(m_latMinutesFrac));
    m_northORSouth = northORSouth;

    memcpy(m_lonDegree, lonDegree, sizeof(m_lonDegree));
    memcpy(m_lonMinutesInt, lonMinutesInt, sizeof(m_lonMinutesInt));
    memcpy(m_lonMinutesFrac, lonMinutesFrac, sizeof(m_lonMinutesFrac));
    m_westOREast = westOREast;

    // mark as valid
    m_validLatLonPresent = true;
}


//---------------------------------------------------------------------------------
static void hlpConvertLocation(char* dst, unsigned degree, uint32_t secondsX100, bool isLongitude, bool isNegative)
{
    //
    // Convert degree+seconds (no minutes) into Maidenhead Locator. One coordinate at time.
    // Code utilizes integer arithmetic only. Suitable for Arduino.
    // isLongitude=false - latitude
    // isLongitude=true - longitude).
    // isNegative=false - north or east
    // isNegative=true - south or west
    //
    // by RA9YER


    int actualDegree = 0;
    uint32_t actualSecondsX100 = secondsX100;
    const uint32_t secondsX100InOneDegree = 360000L;

    if (isLongitude)
    {
        actualDegree = (isNegative) ? 180 - degree : 180 + degree;
    }
    else
    {
        actualDegree = (isNegative) ? 90 - degree : 90 + degree;
    }

    if (isNegative)
    {
        if (secondsX100 != 0)
        {
            actualDegree--;
            actualSecondsX100 = secondsX100InOneDegree - secondsX100;
        }
    }

    const unsigned N = (isLongitude) ? 20 : 10;
    const unsigned K = (isLongitude) ? 2 : 1;
    unsigned M = 15000 * K;

    // 10 or 20 degree / 18 parts
    unsigned v1 = actualDegree / N;

    // 1 or 2 degree / 10 parts
    unsigned v2 = (actualDegree - v1 * N) / K;
    unsigned v2_rest = actualDegree - v1 * N - v2 * K; // 0 or 1

    actualSecondsX100 += (v2_rest * secondsX100InOneDegree); // add rest from degree to seconds if present. 0 or 1 degree in secondsX100 units

    // fit into 24 parts
    unsigned v3 = actualSecondsX100 / M;  // 1 degree = 3600 seconds = 360000 in secondsX100 units,  fit into 24 parts -> M = 15000
    uint32_t v3_rest = actualSecondsX100 - (uint32_t)v3 * M;

    // fit into 10 parts
    M /= 10;
    unsigned v4 = v3_rest / M;
    uint32_t v4_rest = v3_rest - (uint32_t)v4 * M;

    // fit into 24 parts  (Note: this 5th pair maybe not very accurate)
    M /= 24;
    unsigned v5 = v4_rest / M;
    // uint32_t v5_rest = v4_rest - (uint32_t)v5 * M;

    dst[0] = 'A' + v1;
    dst[1] = '0' + v2;
    dst[2] = 'a' + v3;
    dst[3] = '0' + v4;
    dst[4] = 'a' + v5;
}

//---------------------------------------------------------------------------------
static unsigned myatoi(const char* ptr, size_t num)
{
    unsigned res = 0;
    unsigned m = 1;
    for (size_t idx = 0; idx < num; idx++)
    {
        int v = ptr[num - idx - 1] - '0';
        res += v * m;
        m *= 10;
    }
    return res;
}

//---------------------------------------------------------------------------------
const char *GpsLatLonExtractBase::calcMaidenheadLocator(unsigned numChars)
{
    size_t numPairs = numChars / 2;

    // correct input argument if need
    if (numPairs < 1)
    {
        numPairs = 1; // like "AA"
    }
    else if (numPairs > 5)
    {
        numPairs = 5; // like "AA00aa00aa"
    }

    // Note: We use only 3 most significant digits from seconds' frac part.
    // This is enough for 8 digits (4 pairs) locator and quite acceptable for 10 digits (5 parts) locator.

    // --- lat ---
    unsigned latDegree = myatoi(m_latDegree, sizeof(m_latDegree));
    uint32_t latSecondsX100 = (uint32_t)(myatoi(m_latMinutesInt, sizeof(m_latMinutesInt))) * 60 * 100 + (uint32_t)(myatoi(m_latMinutesFrac, 3)) * 6;
    bool isLatNegative = (m_northORSouth == 'S');

    char latBuf[5];
    hlpConvertLocation(latBuf, latDegree, latSecondsX100, false, isLatNegative);

    // --- lon ---
    unsigned lonDegree = myatoi(m_lonDegree, sizeof(m_lonDegree));
    uint32_t lonSecondsX100 = (uint32_t)(myatoi(m_lonMinutesInt, sizeof(m_lonMinutesInt))) * 60 * 100 + (uint32_t)(myatoi(m_lonMinutesFrac, 3)) * 6;
    bool isLonNegative = (m_westOREast == 'W');

    char lonBuf[5];
    hlpConvertLocation(lonBuf, lonDegree, lonSecondsX100, true, isLonNegative);


    // --- produce result ---
    size_t pos = 0;
    for (size_t idx = 0; idx < numPairs; idx++)
    {
        m_maidenheadLocator[pos++] = lonBuf[idx];
        m_maidenheadLocator[pos++] = latBuf[idx];
    }

    m_maidenheadLocator[pos] = 0; // null terminated char's string

    return m_maidenheadLocator;
}


<|start_filename|>src/time/gps_config.h<|end_filename|>
#pragma once

// #define ENABLE_MAIDENHEAD_LOCATOR

#define USE_GPRMC
// #define USE_GNGLL


<|start_filename|>src/time/gprmc_latlon_extract.cpp<|end_filename|>
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include "gprmc_latlon_extract.h"
#include <string.h>

//---------------------------------------------------------------------------------
GPRMCLatLonExtract::GPRMCLatLonExtract()
    : GpsLatLonExtractBase()
    , m_state(stWaitLine)
    , m_numCommas(0)
    , m_pos(0)
{
}

//---------------------------------------------------------------------------------
void GPRMCLatLonExtract::onCharReceived(char ch)
{
    for (size_t idx = 1; idx < 6; idx++)
    {
        m_rmc[idx - 1] = m_rmc[idx];
    }
    m_rmc[5] = ch;

    if (strncmp(m_rmc, "$GPRMC", 6) == 0)
    {
        m_state = stWaitStatusChar;
        m_numCommas = 0;
        return;
    }

    if (m_state == stWaitStatusChar)
    {
        if (ch == ',')
        {
            m_numCommas++;
            if (m_numCommas == 2)
            {
                m_state = stGrabStatusChar;
                m_pos = 0;
            }
        }
    }
    else if (m_state == stGrabStatusChar)
    {
        // expect active record only
        if (ch == 'A')
        {
            m_state = stWaitLatLonInfo;
        }
        else
        {
            // unexpected
            invalidate(); // mark already catched data as no more valid
            m_state = stWaitLine;
            return;
        }
    }
    else if (m_state == stWaitLatLonInfo)
    {
        if (ch == ',')
        {
            // clear tmp buffer
            memset(m_tmpBuf, 0, MAX_TMP_BUFFER);
            m_pos = 0;
            m_numCommas = 0;
            m_state = stGrabLatLonInfo;
        }
        else
        {
            // should never happen
            m_state = stWaitLine;
            return;
        }
    }
    else if (m_state == stGrabLatLonInfo)
    {
        // fill m_tmpBuf by incoming chars

        m_tmpBuf[m_pos] = ch;
        m_pos++;
        if (m_pos >= MAX_TMP_BUFFER)
        {
            // should never happen
            m_state = stWaitLine;
            return;
        }

        if (ch == ',')
        {
            m_numCommas++;
        }

        if (m_numCommas == 4)
        {
            decodeLatLon();
            m_state = stWaitLine;
        }
    }

}


<|start_filename|>src/time/gngll_datetime_extract.cpp<|end_filename|>
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include "gngll_datetime_extract.h"
#include <string.h>


//---------------------------------------------------------------------------------
GNGLLDatetimeExtract::GNGLLDatetimeExtract()
    : GpsDatetimeExtractBase()
    , m_state(stWaitLine)
    , m_numCommas(0)
    , m_pos(0)
{
}

//---------------------------------------------------------------------------------
void GNGLLDatetimeExtract::onCharReceived(char ch)
{
    for (size_t idx = 1; idx < 6; idx++)
    {
        m_rmc[idx - 1] = m_rmc[idx];
    }
    m_rmc[5] = ch;

    if (strncmp(m_rmc, "$GNGLL", 6) == 0)
    {
        m_state = stWaitTime;
        m_numCommas = 0;
        memset(m_tmpDatetime, '0', 12);
        return;
    }

    // "$GNGLL,5046.4661,N,03044.4404,E,104232.000,A,A*11";

    if (m_state == stWaitTime)
    {
        if (ch == ',')
            m_numCommas++;

        if(m_numCommas == 5)
        {
            m_state = stGrabTime;
            m_pos = 0;
        }
    }
    else if (m_state == stGrabTime)
    {
        if (ch == ',')
        {
            // unexpected
            invalidate();
            m_state = stWaitLine;
            return;
        }

        m_tmpDatetime[6 + m_pos] = ch;
        m_pos++;
        if (m_pos == 6)
        {
            // fill date as January 1'st of 2020
            memcpy(m_tmpDatetime, "200101", 6);

            // put catched datetime to base class
            putDateTime(m_tmpDatetime);
            m_state = stWaitLine;
        }
    }
}


<|start_filename|>src/time/time_slice_gps.cpp<|end_filename|>
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//
#include "time_slice_gps.h"
#include "rtc_datetime.h"
#include <Stream.h>

//---------------------------------------------------------------------------------
TimeSliceGPS::TimeSliceGPS()
    : m_1ppsValid(false)
{
}

//---------------------------------------------------------------------------------
void TimeSliceGPS::initialize()
{
}

//---------------------------------------------------------------------------------
bool TimeSliceGPS::getTime(RtcDatetime& outTime)
{
    if (!m_gpsDatatimeExtract.isDateTimePresent())
        return false;

    outTime.initFromShortString(m_gpsDatatimeExtract.getDateTimeAsShortString());

    return true;
}

//---------------------------------------------------------------------------------
int TimeSliceGPS::get1PPS()
{
    // emulate 1pps signal using m_timer and event logic.
    // 000 ms - 499 ms - 0
    // 500 ms - 999+ ms - 1

    int v = (m_1ppsValid && m_timer.millisecondsElapsed() < 500)? 0 : 1;
    return v;
}

//---------------------------------------------------------------------------------
void TimeSliceGPS::putNMEAChar(char ch)
{
    m_gpsDatatimeExtract.onCharReceived(ch);
#ifdef ENABLE_MAIDENHEAD_LOCATOR
    m_gpsLatLotExtract.onCharReceived(ch);
#endif
}

//---------------------------------------------------------------------------------
void TimeSliceGPS::doWork()
{
    // We expect event raises every second, not faster!!!
    if (m_gpsDatatimeExtract.isEventPresent())
    {
        m_gpsDatatimeExtract.resetEvent(); // prevent reenter
        m_1ppsValid = true;
        m_timer.resetToNow();
    }
    else
    {
        // if GPS does not provide fresh time within 2 seconds then mark as not valid.
        if (m_1ppsValid && m_timer.millisecondsElapsed() > 2000)
        {
            m_1ppsValid = false;
        }
    }

}



<|start_filename|>src/utils/timerange.cpp<|end_filename|>
//
// Developed by <NAME> <<EMAIL>>
//
#include "timerange.h"
#include <Arduino.h>

//--------------------------------------------------------
CTimeRange::CTimeRange()
{
    resetToNow();
}

//--------------------------------------------------------
void CTimeRange::resetToNow()
{
    _stamp = millis();
}


//--------------------------------------------------------
long CTimeRange::millisecondsElapsed() const
{
    unsigned long current = millis();
    return current - _stamp;
}

//--------------------------------------------------------
long CTimeRange::secondsElapsed() const
{
    return millisecondsElapsed() / 1000;
}


<|start_filename|>src/time/time_slice_gps.h<|end_filename|>
#pragma once
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include "gps_config.h"
#include "time_slice_base.h"

#ifdef ENABLE_MAIDENHEAD_LOCATOR
#include "gngll_latlon_extract.h"
#include "gprmc_latlon_extract.h"
#endif

#include "gngll_datetime_extract.h"
#include "gprmc_datetime_extract.h"

#include "../utils/timerange.h"

class RtcDatetime;
class Stream;

class TimeSliceGPS : public TimeSliceBase
{
public:
    TimeSliceGPS();
    void initialize();
    bool getTime(RtcDatetime& outTime);
    int get1PPS();
    void doWork();
    void putNMEAChar(char ch);

#ifdef ENABLE_MAIDENHEAD_LOCATOR
    const char* calcMaidenheadLocator(unsigned numChars) { return m_gpsLatLotExtract.calcMaidenheadLocator(numChars); }
    bool isValidLatLonPresent() const { return m_gpsLatLotExtract.isValidLatLonPresent(); }
#endif

private:
    bool m_1ppsValid;
    CTimeRange m_timer;

    // Arduino-stype factory
#ifdef USE_GPRMC
    GPRMCDatetimeExtract m_gpsDatatimeExtract;
#endif // USE_GPRMC
#ifdef USE_GNGLL
    GNGLLDatetimeExtract m_gpsDatatimeExtract;
#endif // USE_GNGLL

#ifdef ENABLE_MAIDENHEAD_LOCATOR
#ifdef USE_GPRMC
    GPRMCLatLonExtract m_gpsLatLotExtract;
#endif // USE_GPRMC
#ifdef USE_GNGLL
    GNGLLLatLonExtract m_gpsLatLotExtract;
#endif // USE_GNGLL
#endif // ENABLE_MAIDENHEAD_LOCATOR

};


<|start_filename|>src/time/gngll_latlon_extract.h<|end_filename|>
#pragma once
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//

#include "gps_latlon_extract_base.h"


class GNGLLLatLonExtract : public GpsLatLonExtractBase
{
public:
    GNGLLLatLonExtract();

    void onCharReceived(char ch) override;
    // void onCharReceived(char ch);

private:
    enum State {
        stWaitLine,
        stWaitStatusChar,
        stGrabStatusChar,
        stWaitLatLonInfo,
        stGrabLatLonInfo,
        stGrabTime,
    };

    State m_state;
    int m_numCommas;
    int m_pos;
    char m_rmc[6];
};


<|start_filename|>src/time/gps_latlon_extract_base.h<|end_filename|>
#pragma once
//
// Author: <NAME> <<EMAIL>>
//
// License: MIT
//


class GpsLatLonExtractBase
{

public:
    GpsLatLonExtractBase();
    bool isValidLatLonPresent() const { return m_validLatLonPresent; }
    const char *calcMaidenheadLocator(unsigned numChars);
    virtual void onCharReceived(char ch) = 0;

protected:
    enum {
        MAX_TMP_BUFFER = 30,
        MAX_FRAC_CHARS = 5,
    };
    char m_tmpBuf[MAX_TMP_BUFFER]; // store string like "4916.45000,N,12311.12000,W,"

    void decodeLatLon();
    void invalidate();

private:
    bool m_validLatLonPresent;
    char m_maidenheadLocator[11]; // AA00aa00aa + \0

    // ready to use data
    char m_latDegree[2]; // for latitude 2 chars +-90
    char m_latMinutesInt[2]; // 00-59
    char m_latMinutesFrac[MAX_FRAC_CHARS]; // 00000-99999
    char m_northORSouth; // N/S

    char m_lonDegree[3]; // for longitude 3 chars +-180
    char m_lonMinutesInt[2]; // 00-59
    char m_lonMinutesFrac[MAX_FRAC_CHARS]; // 00000-99999
    char m_westOREast; // W/E

};
