microcontrolleruartat-commandmicropythoncurrent-time

Get current time via AT Cmds and UART


I'm working on a small microcontroller project with the Raspberry Pi pico. Since the Pico doesn't support WiFi I added the ESP8266-01. In MicroPython, I cannot access the sockets or ntptime libraries.

How to I get the current time just with AT commands and UART?

I have a working internet connection and AT commands work.

utime.localtime() and time.localtime() just gave me the time of the Pico and not the actual time. rtc.datetime() needs to be restarted after I disconnect to Pico from my computer. That's why i would like to get the current time via the internet.

After successfully connecting to the internet, I have tried running this:

import machine
import utime

uart0 = machine.UART(0, baudrate=115200)
uart0.init(115200, bits=8, parity=None, stop=1)

def send_at_cmd(cmd, uart=uart0):
    print("CMD: " + cmd)
    uart.write(cmd + "\r\n")

    utime.sleep(3)

    print(uart.read().decode())
    print()


def connect_to_ntp():
    send_at_cmd('AT+CIPCLOSE') #Close the previous open UDP connection
    send_at_cmd('AT+CIPSTART="UDP","pool.ntp.org",123') #Connect to pool.ntp.org"

def get_ntp_time(uart=uart0):
    send_at_cmd('AT+CIPSEND=48') 
    ntp_request = b'\x1b' + 47 * b'\0'  
    uart.write(ntp_request) #Request 48 Bytes from the server. Trying the get the time here
    
    utime.sleep(3)

    response = uart.read() #Read the 48 Byte response
    print("RAW response:", response) #The whole repsonse
    print()
    print("Decoded response:", int.from_bytes(response[40:44], 'big')) #The decoded part where the current time should be
        


connect_to_ntp()
get_ntp_time()

Then i get:

CMD: AT+CIPCLOSE

AT+CIPCLOSE  
CLOSED  
OK

CMD: AT+CIPSTART="UDP","pool.ntp.org",123

AT+CIPSTART="UDP","pool.ntp.org",123  
0,CONNECT  
OK

CMD: AT+CIPSEND=48

AT+CIPSEND=48  
OK  
 

RAW response:   
b'\\r\\nRecv 48 bytes\\r\\n\\r\\nSEND OK\\r\\n\\r\\n+IPD,48:\\x1c\\x02\\x03\\xe8\\x00\\x00\\x00\\x12\\x00\\x00\\x07\\xe2O\\x85,\\x8c\\xea\\xb3\\xa1\\xc5\\xb0\\xfa\\xc82\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xea\\xb3\\xa5\\xc2v\\x18\\x1a\\x86\\xea\\xb3\\xa5\\xc2v\\x1a\\xd3Z'

Decoded response: 65536000

But 65536000 is not my current time, it corresponds to: 29.01.1972, 13:26:40.


Solution

  • 65536000 is a suspiciously round number, 3E80000 in hex. It seems like you're probably extracting the wrong bytes from the response.

    Let's work backwards and see if we can find the correct bytes. The current Unix epoch time is 1728749694. But note that NTP uses a different epoch start from Unix time. The NTP epoch is Jan 1 1900, compared to the Unix epoch, which is Jan 1 1970.

    So we need to add 2208988800 to the Unix time to get today's NTP time (e.g. see the answer to this question).

    Adding that on to the Unix time gives 1728749694 + 2208988800 = 3937738494 which is 0xEAB51EFE. Can we see anything like that in your response data? Yes, we see:

    \\xea\\xb3\\xa1\\xc5
    

    Converting that back into Unix time to see when that was, gives 0xEAB3A1C5 - 2208988800 = 1728652101 which is pretty close to today's Unix time, and is in fact Friday, 11 October 2024 13:08:21, which is yesterday, and probably when you ran your code.

    This byte sequence is 12 bytes further into the response than you are looking at. Therefore I suggest that your code should be:

    int.from_bytes(response[52:56], 'big')
    

    Don't forget to take into account the differences in Epoch start times when you interpret this value.

    EDIT:

    It might be unwise to assume that the value you want is always at the same offset in the response, since the response is not just the contents of the UDP packet, but also some framing from the AT command protocol.

    It might be wise to search for the string +IPD,48: and then use a known offset after that. Something like:

    resp_offset = response.find(b"+IPD,")
    time = int.from_bytes(response[resp_offset + X:resp_offset + X + 4], 'big')
    

    for some value of X tbd, possibly 21 from your example.