Budík s synchronizací času NTP protokolem přes WiFi.

Na letní čas se v Čechách každý rok přechází poslední neděli v březnu, kdy po 01:59:59 SEČ (středoevropského času) následuje 03:00:00 SELČ (středoevropského letního času). Letní čas končí poslední neděli v říjnu, kdy se po 02:59:59 SELČ hodiny posunou na 02:00:00 SEČ. V UTC je to poslední neděli března v 1:00 UTC dopředu 1 hodina, a poslední neděli října v 1:00 UTC dozadu jedna hodina.

git clone https://github.com/nayarsystems/posix_tz_db
cd posix_tz_db
python3 ./gen-tz.py -c | grep Europe/Prague
cd ..
git clone https://github.com/mjcross/Pico_NTP_clock

Uložení wifi

Uložení wifi bude v prvních 16 kbyte flash, 128 bytů na wifi.

Uložení budíků

Uložení budíků bude od druhých 16 kbyte do konce flash úseku (celkem 64 kbyte). Jeden budík 32 bytů. Čas budíku se bude ukládat v UTC, hodiny poběží v UTC, na displeji se bude zobrazovat středoevropský čas SEČ, nebo středoevropský letní čas SELČ.

Schéma zapojení

budik schema

Zdrojové kódy

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)

set(PICO_BOARD pico_w CACHE STRING "Board type")
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections -Wl,--gc-sections")

include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

set(PROJECT_NAME picow_budik)
project(${PROJECT_NAME} C CXX ASM)

pico_sdk_init()

add_executable(${PROJECT_NAME}
		main.c
		sh1106_spi.c
		utf8.c
		font_spleen_8x16.c
		font_spleen_12x24.c
		font_spleen_16x32.c
		font_tahoma_10.c
		font_tahoma_12.c
		font_10x20.c
		ds3231.c
)


# nastaveni vyhrazeneho mista ve XIP flash pro konfiguracni data
pico_set_linker_script(${PROJECT_NAME} ${CMAKE_SOURCE_DIR}/memmap_custom.ld)

target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR})

# Add the standard library to the build
target_link_libraries(${PROJECT_NAME}
        pico_stdlib
        pico_cyw43_arch_lwip_threadsafe_background
        hardware_gpio
        hardware_spi
        hardware_pwm
        hardware_rtc
        hardware_flash
        hardware_i2c
        hardware_sync
        )

target_link_options(${PROJECT_NAME} PRIVATE -Xlinker --print-memory-usage)

pico_enable_stdio_uart(${PROJECT_NAME} 0)
pico_enable_stdio_usb(${PROJECT_NAME} 1)
pico_add_extra_outputs(${PROJECT_NAME})

Část flash paměti využijeme na uložení údajů o známých wifi, druhou část pro uložení budíků.

memmap_custom.ld
/* Based on GCC ARM embedded samples.
   Defines the following symbols for use by code:
    __exidx_start
    __exidx_end
    __etext
    __data_start__
    __preinit_array_start
    __preinit_array_end
    __init_array_start
    __init_array_end
    __fini_array_start
    __fini_array_end
    __data_end__
    __bss_start__
    __bss_end__
    __end__
    end
    __HeapLimit
    __StackLimit
    __StackTop
    __stack (== StackTop)
*/

__PERSISTENT_STORAGE_LEN = 64k ;

MEMORY
{
    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k - __PERSISTENT_STORAGE_LEN
    FLASH_PERSISTENT(rw) : ORIGIN = 0x10000000 + (2048k - __PERSISTENT_STORAGE_LEN) , LENGTH = __PERSISTENT_STORAGE_LEN
    RAM(rwx) : ORIGIN =  0x20000000, LENGTH = 256k
    SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
    SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
}

ENTRY(_entry_point)

SECTIONS
{
    /* Second stage bootloader is prepended to the image. It must be 256 bytes big
       and checksummed. It is usually built by the boot_stage2 target
       in the Raspberry Pi Pico SDK
    */

    .flash_begin : {
        __flash_binary_start = .;
    } > FLASH

    .boot2 : {
        __boot2_start__ = .;
        KEEP (*(.boot2))
        __boot2_end__ = .;
    } > FLASH

    ASSERT(__boot2_end__ - __boot2_start__ == 256,
        "ERROR: Pico second stage bootloader must be 256 bytes in size")

    /* The second stage will always enter the image at the start of .text.
       The debugger will use the ELF entry point, which is the _entry_point
       symbol if present, otherwise defaults to start of .text.
       This can be used to transfer control back to the bootrom on debugger
       launches only, to perform proper flash setup.
    */

    .text : {
        __logical_binary_start = .;
        KEEP (*(.vectors))
        KEEP (*(.binary_info_header))
        __binary_info_header_end = .;
        KEEP (*(.reset))
        /* TODO revisit this now memset/memcpy/float in ROM */
        /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
         * FLASH ... we will include any thing excluded here in .data below by default */
        *(.init)
        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
        *(.fini)
        /* Pull all c'tors into .text */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
        *(SORT(.ctors.*))
        *(.ctors)
        /* Followed by destructors */
        *crtbegin.o(.dtors)
        *crtbegin?.o(.dtors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
        *(SORT(.dtors.*))
        *(.dtors)

        *(.eh_frame*)
        . = ALIGN(4);
    } > FLASH

    .rodata : {
        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
        . = ALIGN(4);
        *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
        . = ALIGN(4);
    } > FLASH

    .ARM.extab :
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > FLASH

    __exidx_start = .;
    .ARM.exidx :
    {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > FLASH
    __exidx_end = .;

    /* Machine inspectable binary information */
    . = ALIGN(4);
    __binary_info_start = .;
    .binary_info :
    {
        KEEP(*(.binary_info.keep.*))
        *(.binary_info.*)
    } > FLASH
    __binary_info_end = .;
    . = ALIGN(4);

    /* End of .text-like segments */
    __etext = .;


    .section_persisitent : {
        "ADDR_PERSISTENT" = .;  
    } > FLASH_PERSISTENT
    
   .ram_vector_table (COPY): {
        *(.ram_vector_table)
    } > RAM

    .data : {
        __data_start__ = .;
        *(vtable)

        *(.time_critical*)

        /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
        *(.text*)
        . = ALIGN(4);
        *(.rodata*)
        . = ALIGN(4);

        *(.data*)

        . = ALIGN(4);
        *(.after_data.*)
        . = ALIGN(4);
        /* preinit data */
        PROVIDE_HIDDEN (__mutex_array_start = .);
        KEEP(*(SORT(.mutex_array.*)))
        KEEP(*(.mutex_array))
        PROVIDE_HIDDEN (__mutex_array_end = .);

        . = ALIGN(4);
        /* preinit data */
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP(*(SORT(.preinit_array.*)))
        KEEP(*(.preinit_array))
        PROVIDE_HIDDEN (__preinit_array_end = .);

        . = ALIGN(4);
        /* init data */
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        PROVIDE_HIDDEN (__init_array_end = .);

        . = ALIGN(4);
        /* finit data */
        PROVIDE_HIDDEN (__fini_array_start = .);
        *(SORT(.fini_array.*))
        *(.fini_array)
        PROVIDE_HIDDEN (__fini_array_end = .);

        *(.jcr)
        . = ALIGN(4);
        /* All data end */
        __data_end__ = .;
    } > RAM AT> FLASH

    .uninitialized_data (COPY): {
        . = ALIGN(4);
        *(.uninitialized_data*)
    } > RAM

    /* Start and end symbols must be word-aligned */
    .scratch_x : {
        __scratch_x_start__ = .;
        *(.scratch_x.*)
        . = ALIGN(4);
        __scratch_x_end__ = .;
    } > SCRATCH_X AT > FLASH
    __scratch_x_source__ = LOADADDR(.scratch_x);

    .scratch_y : {
        __scratch_y_start__ = .;
        *(.scratch_y.*)
        . = ALIGN(4);
        __scratch_y_end__ = .;
    } > SCRATCH_Y AT > FLASH
    __scratch_y_source__ = LOADADDR(.scratch_y);

    .bss  : {
        . = ALIGN(4);
        __bss_start__ = .;
        *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
        *(COMMON)
        . = ALIGN(4);
        __bss_end__ = .;
    } > RAM

    .heap (COPY):
    {
        __end__ = .;
        end = __end__;
        *(.heap*)
        __HeapLimit = .;
    } > RAM

    /* .stack*_dummy section doesn't contains any symbols. It is only
     * used for linker to calculate size of stack sections, and assign
     * values to stack symbols later
     *
     * stack1 section may be empty/missing if platform_launch_core1 is not used */

    /* by default we put core 0 stack at the end of scratch Y, so that if core 1
     * stack is not used then all of SCRATCH_X is free.
     */
    .stack1_dummy (COPY):
    {
        *(.stack1*)
    } > SCRATCH_X
    .stack_dummy (COPY):
    {
        *(.stack*)
    } > SCRATCH_Y

    .flash_end : {
        __flash_binary_end = .;
    } > FLASH


    /* stack limit is poorly named, but historically is maximum heap ptr */
    __StackLimit = ORIGIN(RAM) + LENGTH(RAM);
    __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
    __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
    __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
    __StackBottom = __StackTop - SIZEOF(.stack_dummy);
    PROVIDE(__stack = __StackTop);

    /* Check if data + heap + stack exceeds RAM limit */
    ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")

    ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
    /* todo assert on extra code */
}
flash_utils.h
#include <stdint.h>


inline uint32_t *getAddressPersistent() 
{
extern uint32_t ADDR_PERSISTENT[];
    return ADDR_PERSISTENT;
}
setupWifi.h
/* Nastavení Wifi karty */

volatile bool timer_fired = false;

int64_t alarm_callback( alarm_id_t id, __unused void *user_data )
{
    // printf("Timer %d fired!\n", (int) id);
    timer_fired = true;
    return 0;
}

int setup(uint32_t country, const char *ssid, const char *pass, uint32_t auth, const char *hostname)
{

    if (cyw43_arch_init_with_country(country))
    {
         return 1;
    }
     
    cyw43_arch_enable_sta_mode();
    if (hostname != NULL)
    {
        netif_set_hostname(netif_default, hostname);
    }
    if (cyw43_arch_wifi_connect_async(ssid, pass, auth))
    {
        return 2;
    }
    int flashrate = 1000;
    int status = CYW43_LINK_UP + 1;
    // k připojení na Wifi nastavíme timeout 10s
    add_alarm_in_ms(10000, alarm_callback, NULL, false);
    while (status >= 0 && !timer_fired && status != CYW43_LINK_UP)
    {
        int new_status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA);
        if (new_status != status)
        {
            status = new_status;
            flashrate = flashrate / (status + 1);
            printf("Stav připojení: %d %d\n", status, flashrate);
        }
        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
        sleep_ms(flashrate);
        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
        sleep_ms(flashrate);
    }
    if (status < 0)
    {
        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
    }
    else
    {
        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
        
    }
    return status;
}
lwipopts.h
#ifndef _LWIPOPTS_EXAMPLE_COMMONH_H
#define _LWIPOPTS_EXAMPLE_COMMONH_H

// Common settings used in most of the pico_w examples
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)

// allow override in some examples
#ifndef NO_SYS
#define NO_SYS 1
#endif
// allow override in some examples
#ifndef LWIP_SOCKET
#define LWIP_SOCKET 0
#endif
#if PICO_CYW43_ARCH_POLL
#define MEM_LIBC_MALLOC 1
#else
// MEM_LIBC_MALLOC is incompatible with non polling versions
#define MEM_LIBC_MALLOC 0
#endif
#define MEM_ALIGNMENT 4
#define MEM_SIZE 4000
#define MEMP_NUM_TCP_SEG 32
#define MEMP_NUM_ARP_QUEUE 10
#define PBUF_POOL_SIZE 24
#define LWIP_ARP 1
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
#define LWIP_RAW 1
#define TCP_WND (8 * TCP_MSS)
#define TCP_MSS 1460
#define TCP_SND_BUF (8 * TCP_MSS)
#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS))
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETCONN 0
#define MEM_STATS 0
#define SYS_STATS 0
#define MEMP_STATS 0
#define LINK_STATS 0
// #define ETH_PAD_SIZE 2
#define LWIP_CHKSUM_ALGORITHM 3
#define LWIP_DHCP 0
#define LWIP_IPV4 0
#define LWIP_IPV6 1
#define LWIP_IPV6_AUTOCONFIG 1
#define LWIP_ND6_RDNSS_MAX_DNS_SERVERS 1
#define LWIP_TCP 0
#define LWIP_UDP 1
#define LWIP_DNS 1
#define LWIP_TCP_KEEPALIVE 0
#define LWIP_NETIF_TX_SINGLE_PBUF 1
#define DHCP_DOES_ARP_CHECK 0
#define LWIP_DHCP_DOES_ACD_CHECK 0
#define DNS_TABLE_SIZE 1

#ifndef NDEBUG
#define LWIP_DEBUG 1
#define LWIP_STATS 1
#define LWIP_STATS_DISPLAY 1
#endif

#define ETHARP_DEBUG        LWIP_DBG_OFF
#define NETIF_DEBUG         LWIP_DBG_OFF
#define PBUF_DEBUG          LWIP_DBG_OFF
#define API_LIB_DEBUG       LWIP_DBG_OFF
#define API_MSG_DEBUG       LWIP_DBG_OFF
#define SOCKETS_DEBUG       LWIP_DBG_OFF
#define ICMP_DEBUG          LWIP_DBG_OFF
#define INET_DEBUG          LWIP_DBG_OFF
#define IP_DEBUG            LWIP_DBG_OFF
#define IP_REASS_DEBUG      LWIP_DBG_OFF
#define RAW_DEBUG           LWIP_DBG_OFF
#define MEM_DEBUG           LWIP_DBG_OFF
#define MEMP_DEBUG          LWIP_DBG_OFF
#define SYS_DEBUG           LWIP_DBG_OFF
#define TCP_DEBUG           LWIP_DBG_OFF
#define TCP_INPUT_DEBUG     LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG    LWIP_DBG_OFF
#define TCP_RTO_DEBUG       LWIP_DBG_OFF
#define TCP_CWND_DEBUG      LWIP_DBG_OFF
#define TCP_WND_DEBUG       LWIP_DBG_OFF
#define TCP_FR_DEBUG        LWIP_DBG_OFF
#define TCP_QLEN_DEBUG      LWIP_DBG_OFF
#define TCP_RST_DEBUG       LWIP_DBG_OFF
#define UDP_DEBUG           LWIP_DBG_OFF
#define TCPIP_DEBUG         LWIP_DBG_OFF
#define PPP_DEBUG           LWIP_DBG_OFF
#define SLIP_DEBUG          LWIP_DBG_OFF
#define DHCP_DEBUG          LWIP_DBG_OFF

#endif /* __LWIPOPTS_H__ */
main.c
/* Pico W hodiny s nastavením času SNTP
 * SNTP klient a seřízení času (jenom IPv6)
 * (c) Jirka Chráska 2026; <jirka@lixis.cz
 * BSD licence 3 clause
 */


#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "pico/util/datetime.h"
#include "flash_utils.h"
#include "hardware/flash.h"
#include "hardware/sync.h"
#include "lwip/pbuf.h"
#include "lwip/ip6_addr.h"
#include "lwip/udp.h"
#include "lwip/dns.h"
#include "hardware/rtc.h"
#include "time.h"
#include "setupWifi.h"
#include <signal.h>     //signal()
#include <math.h>

#include "ds3231.h"
#include "sh1106_spi.h"
#include "font.h"

#include "hardware/pwm.h"

#define PIN_BZUCAK 15 

// ----------------------------------------------------------------------------------------
// Funkce pro zvuk sirény
uint slice_num;

void hraj_ton(uint frekvence) 
{
    if (frekvence == 0) {
        pwm_set_gpio_level(PIN_BZUCAK, 0);
        return;
    }
    slice_num = pwm_gpio_to_slice_num(PIN_BZUCAK);
    uint32_t clock = 125000000;
    uint32_t divider = clock / (frekvence * 4096) + 1;
    uint32_t top = clock / (divider * frekvence) - 1;
    pwm_set_clkdiv(slice_num, divider);
    pwm_set_wrap(slice_num, top);
    pwm_set_gpio_level(PIN_BZUCAK, top / 2);
}

// pro budík
void sirena() 
{
        hraj_ton(600);
        sleep_ms(80);
        hraj_ton(500);
        sleep_ms(80);
        hraj_ton(450);
        sleep_ms(80);
        hraj_ton(0);
}

// pro nepřipojení k wifi
void sirena2() 
{
        hraj_ton(400);
        sleep_ms(180);
        hraj_ton(800);
        sleep_ms(180);
        hraj_ton(400);
        sleep_ms(180);
        hraj_ton(0);
}
// ----------------------------------------------------------------------------------------

#define HOSTNAME    "budik"

// hodiny DS3231
ds3231_t ds3231;
// nastavení času
ds3231_data_t ds3231_data;
// piny
uint8_t ds3231_sda_pin = 4;
uint8_t ds3231_scl_pin = 5;
uint8_t ds3231_int_pin = 6;
#define ds3231_i2c  i2c0

void ds3231_nastaveni()
{
    ds3231_init(&ds3231, ds3231_i2c, DS3231_DEVICE_ADRESS, AT24C32_EEPROM_ADRESS_0);
    sleep_ms(200);
    gpio_init(ds3231_sda_pin);
    gpio_init(ds3231_scl_pin);
    gpio_set_function(ds3231_sda_pin, GPIO_FUNC_I2C);
    gpio_set_function(ds3231_scl_pin, GPIO_FUNC_I2C);
    gpio_pull_up(ds3231_sda_pin);
    gpio_pull_up(ds3231_scl_pin);
    i2c_init(ds3231_i2c, 400 * 1000);    
}

// --------------------------------------------------------------------------
// SNTP
#define NTP_SERVER  "tik.cesnet.cz"
#define TIMEZONE    3600
// ---------------------------------------------------------------------------
// známé sítě

const uint8_t *flash_target_content; 

// struktura údajů o známé síti
typedef struct wifi_zname {
        char* ssid;     // jméno sítě
        char* pass;     // heslo v otevřeném textu
        char* auth;     // způsob autorizace
        char* ipv6;     // umí síť IPv6 (y/n)
        char* ipv4;     // umí síť IPv4 (y/n)
        int   rssi;     // aktuální síla signálu
        struct wifi_zname * next; // ukazatel na další prvek seznamu
} ZNAME_WIFI;

// ukazatel na počátek seznamu známých sítí
ZNAME_WIFI* zwifi = NULL;

// vytvoří seznam známých Wifi, ke kterým se můžeme připojit 
void napln_zname_wifi(void)
{
char *wifidata; // pomocný ukazatel do XIP flash
ZNAME_WIFI *zw;

    flash_target_content = (const uint8_t *) getAddressPersistent();
    for( int i=0; i<8; i++ ){
        wifidata = (char*) flash_target_content+(i*128);
        zw = malloc(sizeof(ZNAME_WIFI));
        if(zw != NULL) {
            zw->ssid = wifidata+5;
            // printf("%p ssid='%s' ", wifidata, zw->ssid);
            int len = strlen(zw->ssid)+6;
            zw->pass = wifidata+len+5;
            // printf("%p pass='%s' ", wifidata, zw->pass);
            len += strlen(zw->pass)+6;
            zw->auth = wifidata+len+5;
            // printf("%p auth='%s' ", wifidata, zw->auth);
            len += strlen(zw->auth)+6;
            zw->ipv6 = wifidata+len+5;
            //printf("%p ipv6='%s' ", wifidata, zw->ipv6);
            len += strlen(zw->ipv6)+6;
            zw->ipv4 = wifidata+len+5;
            // počáteční signál -120 dBm 
            zw->rssi = -120;
            if( zwifi != NULL ) { // v seznamu už něco máme
                zw->next = zwifi;
            } else {              // vkládáme první prvek (v seznamu bude poslední) 
                zw->next = NULL;
            }
            zwifi = zw;
        } else {
            printf("Nedostatek paměti.\n");
            break;
        }
    }
}

// najde v seznamu známou WiFi podle ssid
ZNAME_WIFI * najdi_wifi(const char *ssid) 
{
    for( ZNAME_WIFI *zw = zwifi; zw!=NULL; zw=zw->next ) {
        if(strcmp(zw->ssid, ssid) == 0 ) {
            return zw;
        }
    }
return NULL;
}

// najde v seznamu Wifi s nejlepším signálem
ZNAME_WIFI *najdi_nejvetsi_rssi( void )
{
int rssi = -120;
ZNAME_WIFI *res = NULL;
    for( ZNAME_WIFI *zw = zwifi; zw!=NULL; zw=zw->next ) {
        if(zw->rssi > rssi) {
            res = zw;
            rssi = zw->rssi;
        }
    }
return res;
}

// uvolní seznam známých wifi
void zrus_zname_wifi( void )
{
    for( ZNAME_WIFI *ptr=zwifi; ptr!=NULL;  ) {
        ZNAME_WIFI *ptr2 = ptr;
        ptr = ptr->next;
        free(ptr2);
    }
}

// callback, výsledky skenování
static int scan_result(void *env, const cyw43_ev_scan_result_t *result)
{
    if (result)
    {
        ZNAME_WIFI *znam = najdi_wifi( result->ssid );
        if( znam != NULL) {
            if( result->rssi > znam->rssi ) {
                znam->rssi = result->rssi;
            }
            printf("ssid: %-32s rssi: %4d chan: %3d mac: %02x:%02x:%02x:%02x:%02x:%02x sec: %s known: %s\n",
               result->ssid, result->rssi, result->channel,
               result->bssid[0], result->bssid[1], result->bssid[2],
               result->bssid[3], result->bssid[4], result->bssid[5],
               (result->auth_mode == 5) ? "WPA2_MIXED_PSK" : 
               (result->auth_mode == 7) ? "WPA_TKIP_PSK" : 
               (result->auth_mode == 0) ? "OPEN" : "UNKNOWN",
               "yes" );
        } else {
            printf("ssid: %-32s rssi: %4d chan: %3d mac: %02x:%02x:%02x:%02x:%02x:%02x sec: %s known: %s\n",
               result->ssid, result->rssi, result->channel,
               result->bssid[0], result->bssid[1], result->bssid[2],
               result->bssid[3], result->bssid[4], result->bssid[5],
               (result->auth_mode == 5) ? "WPA2_MIXED_PSK" : 
               (result->auth_mode == 7) ? "WPA_TKIP_PSK" : 
               (result->auth_mode == 0) ? "OPEN" : "UNKNOWN",
               "no" );
        }
    }
    return 0;
}
// --------------------------------------------------------------------------------------------
// SNTP
// připojení k AP
int connect( ZNAME_WIFI *zw, char * hostname )
{
    uint32_t country = CYW43_COUNTRY_CZECH_REPUBLIC;
    uint32_t auth;
    if( strcmp(zw->auth,"WPA2_MIXED_PSK") == 0) auth = CYW43_AUTH_WPA2_MIXED_PSK;
    if( strcmp(zw->auth,"WPA_TKIP_PSK") == 0)   auth = CYW43_AUTH_WPA_TKIP_PSK;
    // používá se nastavení pomocí SLAAC+RD 
    return  setup(country, zw->ssid, zw->pass, auth, hostname );
}
// získání času z hodin Pica
bool getDateNow(struct tm *t)
{
datetime_t rtc;
    bool state = rtc_get_datetime(&rtc);
    if (state)
    {
        t->tm_sec   = rtc.sec;
        t->tm_min   = rtc.min;
        t->tm_hour  = rtc.hour;
        t->tm_mday  = rtc.day;
        t->tm_mon   = rtc.month - 1;
        t->tm_year  = rtc.year - 1900;
        t->tm_wday  = rtc.dotw;
        t->tm_yday  = 0;
        t->tm_isdst = -1;
    }
    return state;
}

// nastavení hodin reálného času na Picu
void setRTC(struct tm *datetime)
{
datetime_t t;
    t.year  = datetime->tm_year + 1900;
    t.month = datetime->tm_mon + 1;
    t.day   = datetime->tm_mday;
    t.dotw  = datetime->tm_wday;
    t.hour  = datetime->tm_hour;
    t.min   = datetime->tm_min;
    t.sec   = datetime->tm_sec;
    rtc_init();
    rtc_set_datetime(&t);
}

struct timeStatus
{
    bool ready;
    struct udp_pcb *pcb;
};

// příjem SNTP odpovědi
void recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
struct timeStatus *tstatus = (struct timeStatus *)arg;
    printf("SNTP odpovídá\n");
    if (p != NULL)
    {
        uint8_t seconds_buf[4];
        pbuf_copy_partial(p, seconds_buf, sizeof(seconds_buf), 40);
        uint32_t seconds_since_1900 = seconds_buf[0] << 24 | seconds_buf[1] << 16 | seconds_buf[2] << 8 | seconds_buf[3];
        time_t   seconds_since_1970 = seconds_since_1900 - 2208988800;
        struct tm *datetime = gmtime(&seconds_since_1970);
        setRTC(datetime);
        pbuf_free(p);
        udp_remove(pcb);
        tstatus->pcb=NULL;
        tstatus->ready = true;
    }
}

bool pollSNTP(struct timeStatus *tstatus)
{
    if (tstatus == NULL)
        return true;
    if (tstatus->ready)
    {
        free(tstatus);
        tstatus = NULL;
        return true;
    }
    return false;
}

// získání IP adresy NTP serveru z DNS
void dns_found(const char *name, const ip6_addr_t *ip, void *arg)
{
    struct timeStatus *tstatus = (struct timeStatus *)arg;
    printf("IP NTP serveru z DNS %s\n", ip6addr_ntoa(ip));
    struct udp_pcb *pcb = udp_new_ip_type(6);
    tstatus->pcb = pcb;
    udp_recv(pcb, recv, arg);
    udp_bind(pcb, IP_ADDR_ANY, 123);
    udp_connect(pcb, ip, 123);
    struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 48, PBUF_RAM);
    uint8_t *payload = (uint8_t *)p->payload;
    memset(payload, 0, 48);
    *payload = 0x1b;
    err_t er = udp_send(pcb, p);
    pbuf_free(p);
}

struct timeStatus *getSNTP()
{
    struct timeStatus *tstatus = malloc(sizeof(struct timeStatus));
    tstatus->ready = false;
    tstatus->pcb=NULL;
    ip6_addr_t ip;
    cyw43_arch_lwip_begin();
    // err_t err = dns_gethostbyname(xstr(NTP_SERVER), &ip, dns_found, tstatus);
    err_t err = dns_gethostbyname_addrtype(NTP_SERVER, &ip, dns_found, tstatus, LWIP_DNS_ADDRTYPE_IPV6);
    cyw43_arch_lwip_end();
    if (err == ERR_OK)
    {
        printf("DNS %s\n", ip6addr_ntoa(&ip));
        dns_found("", &ip, tstatus);
    } else {
        printf("DNS %s err=%s\n", ip6addr_ntoa(&ip), err==-5?"ERR_INPROGRESS":"ERR_ARG");
        
    }
    return tstatus;
}

void cancelSNTP(struct timeStatus *tstatus)
{
    if (tstatus != NULL)
    {
        udp_remove(tstatus->pcb);
        free(tstatus);
        tstatus = NULL;
        printf("zrušeno\n");
    }
}
// ---------------------------------------------------------------------------------------
static volatile bool budik_fired = false;
static void budik_callback(void)
{
datetime_t t = {0};
char datetime_buf[256];
char *datetime_str = &datetime_buf[0];
    rtc_get_datetime(&t);
    datetime_to_str(datetime_str, sizeof(datetime_buf), &t);
    printf("Budik spuštěn v %s\n", datetime_str);
    stdio_flush();
    budik_fired = true;
    
}
// ---------------------------------------------------------------------------------------
// struktura s údaji o displeji
sh1106_t disp;

// použité fonty
extern bitmapFONT font_spleen_8x16;
extern bitmapFONT font_spleen_12x24;
extern bitmapFONT font_spleen_16x32;
extern bitmapFONT font_spleen_32x64;
extern bitmapFONT font_tahoma_10;
extern bitmapFONT font_tahoma_12;
extern bitmapFONT font_10x20;

// zapojení displeje
#define BAUD  1000000
#define MOSI  19
#define CLK   18
#define DC    16
#define CS    17
#define RST   20
#define SIRKA 128
#define VYSKA 64

// časová zóna pro Europe/Prague
// "CEST-1CET,M3.5.0/2,M10.5.0/2"
// setenv("TZ", "CEST-1CET,M3.5.0/2,M10.5.0/2", 1);
// git clone https://github.com/mjcross/Pico_NTP_clock

// ----------------------------------------------------------------------------------------
int main()
{
char buf1[256];
char buf2[256];
char buf3[256];
struct tm t1, t2;
char Date[100];
const char *dny[7] = {"Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota" };
const char *ds3231_dny[8] = { "Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle" };
const char *mesice[12] = {"ledna", "února", "března", "dubna", "května", "června", "července", "srpna", "září", "října", "listopadu", "prosince"};

    setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
    stdio_init_all();
    memset(buf1,'\0',256);
    memset(buf2,'\0',256);
    memset(buf3,'\0',256);
    // vypínací tlačítko
    gpio_init(22);
    gpio_pull_up(22);
    SH1106_init(&disp, spi0, BAUD, MOSI, CLK, DC, CS,RST, SIRKA, VYSKA);
    SH1106_clear(&disp);
    SH1106_drawString(&disp,0,0,&font_spleen_8x16,"Budík s NTP");
    SH1106_draw(&disp);
    sleep_ms(1000);
 // nastavení bzučáku
    gpio_set_function(PIN_BZUCAK, GPIO_FUNC_PWM);
    slice_num = pwm_gpio_to_slice_num(PIN_BZUCAK);
    pwm_set_enabled(slice_num, true);
    
    ds3231_nastaveni();
	sleep_ms(100);
    printf("Skenuji AP...\n");
    napln_zname_wifi();
    
    // inicializace cyw43 chipu
    if (cyw43_arch_init())
    {
        printf("failed to initialise\n");
        return 1;
    }

    // Picow nastavíme jako stanici
    cyw43_arch_enable_sta_mode();
    cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, CYW43_COUNTRY_CZECH_REPUBLIC);
    cyw43_wifi_scan_options_t scan_options = {0};
    // začínáme skenovat
    int err = cyw43_wifi_scan(&cyw43_state, &scan_options, NULL, scan_result);

    while (true)
    {
        if (!cyw43_wifi_scan_active(&cyw43_state))
            break;
        sleep_ms(1000);
        printf("Skenujeme ... \n");
    }
    printf("Scan je hotov.\n");
    cyw43_arch_deinit();
    sirena();

    // najdeme nejlepší signál mezi známými WiFi
    ZNAME_WIFI *zw = najdi_nejvetsi_rssi();
    if( zw != NULL ) { // máme wifi k připojení
        bool error = false;
        printf("Nejlepší signál %d má ssid %s sem se připojíme.\n", zw->rssi, zw->ssid);
        printf("\nSNTP klient IPv6\n");
        int connection_status = 0;
        
        int cekani = 0;
        
        do {
            printf("Připojuji se k AP %s.\n",zw->ssid);
            connection_status = connect(zw, HOSTNAME );
            
        } while ( connection_status != CYW43_LINK_UP && !timer_fired);
        
        if( timer_fired ) {
            printf("Problém s připojením AP %s, není přidělena adresa.\n",zw->ssid); 
        } 
        if( connection_status == CYW43_LINK_UP ) {
            printf("Připojeno.\n");
            printf("Linková IPv6 adresa:  %s\n", 
                   ip6addr_ntoa(netif_ip6_addr(netif_default,0)));
            printf("Globální IPv6 adresa: %s\n", 
                   ip6addr_ntoa(netif_ip6_addr(netif_default,1)));
            printf("Host Name: %s\n", netif_get_hostname(netif_default));
            sprintf(buf1,"Připojeno AP: %s",zw->ssid );
            sprintf(buf2,"IPv6: %s",ip6addr_ntoa(netif_ip6_addr(netif_default,1)) );
                    
            // nechce-li chodit automatické nastavení DNS serveru
            // dá se nastavit ručně
            //    ip6_addr_t dnsserver_ip ;
            //    ip6addr_aton("2a0e:5340:4:1::1", &dnsserver_ip);
            //    IP_SET_TYPE_VAL(&dnsserver_ip, IPADDR_TYPE_V6);
            //    dns_setserver(0,&dnsserver_ip);
            //    OpenDNS  2620:0:ccc::2 2620:0:ccd::2
            //    Quad9 2620:fe::10, 2620:fe::fe:10
        
            const ip6_addr_t *dnsip = dns_getserver(0);
            printf("DNS_MAX_SERVERS=%d\n", DNS_MAX_SERVERS);
            printf("DNS server 1:           %s\n", ip6addr_ntoa(dnsip));
            if( DNS_MAX_SERVERS > 1 ) {
                const ip6_addr_t *dnsip2 = dns_getserver(1);
                printf("DNS server 2:           %s\n", ip6addr_ntoa(dnsip2));
            }
            printf("ERR_ARG=%d, ERR_OK=%d ERR_INPROGRESS=%d\n", 
                   ERR_ARG, ERR_OK, ERR_INPROGRESS);
            sprintf(buf3, "DNS server: %s", ip6addr_ntoa(dnsip));
            cekani = 0;
            while (cekani <= 20) {
                struct timeStatus *tstatus = getSNTP();
                sleep_ms(500);
                if (pollSNTP(tstatus))
                    break;
                cancelSNTP(tstatus);
                cekani++;
            }

            bool rtc_status = getDateNow(&t1);
            if(rtc_status) {  // nastavíme čas na hodinách DS3231 podle RTC
                printf("Čas podle struct tm: %d.%d.%d %02d:%02d:%02d wday %d \n",
                       t1.tm_mday, t1.tm_mon, t1.tm_year, 
                       t1.tm_hour, t1.tm_min, t1.tm_sec, t1.tm_wday);
                ds3231_data.seconds = t1.tm_sec;
                ds3231_data.minutes = t1.tm_min;
                ds3231_data.hours   = t1.tm_hour;
                ds3231_data.day     = t1.tm_wday; // den v týdnu se na DS3231 počítá od 1
                ds3231_data.date    = t1.tm_mday; 
                ds3231_data.month   = t1.tm_mon+1; // měsíc se počítá od jedničky
                ds3231_data.year    = t1.tm_year-100; // rok se počítá od 0 do 99
                ds3231_data.century = 1;
                ds3231_data.am_pm   = false;
                ds3231_enable_am_pm_mode( &ds3231, false);
                ds3231_configure_time( &ds3231, &ds3231_data);
            }
        } else {
            sprintf(buf1,"Wifi %s nefunguje.\n",zw->ssid);
            sirena2();
            if( ds3231_read_current_time( &ds3231, &ds3231_data) ) {
                printf("Nedostal jsem data ze záložních hodin.\n");
            } else {
                // nastavíme RTC podle záložních hodin DS3231
                sprintf(buf2,"Čas nastaven ze záložních hodin DS3231.");
                printf("%02u:%02u:%02u  %10s    %02u.%02u.20%02u\r", 
                    ds3231_data.hours, ds3231_data.minutes, ds3231_data.seconds,
                    ds3231_dny[ds3231_data.day-1], ds3231_data.date, 
                    ds3231_data.month, ds3231_data.year);
                t1.tm_sec  = ds3231_data.seconds;
                t1.tm_min  = ds3231_data.minutes;
                t1.tm_hour = ds3231_data.hours;
                t1.tm_wday = ds3231_data.day;
                t1.tm_mday = ds3231_data.date;
                t1.tm_mon  = ds3231_data.month - 1;
                t1.tm_year = ds3231_data.year + 100;
                setRTC(&t1);
            } 
            
        }
        
    } else { // žádná Wifi není v dosahu, budeme používat záložní hodiny DS3231 
             // a podle nich nastavíme RTC
        printf("Nemohu se připojit, žádná známá Wifi není v dosahu.\n");
        sprintf(buf1,"Nenalezl jsem žádnou Wifi.\n",zw->ssid);
        if( ds3231_read_current_time( &ds3231, &ds3231_data) ) {
                printf("Nedostal jsem data ze záložních hodin.\n");
        } else {
            sprintf(buf2,"Čas nastaven ze záložních hodin DS3231.");
            printf("%02u:%02u:%02u  %10s    %02u.%02u.20%02u\r", 
                    ds3231_data.hours, ds3231_data.minutes, ds3231_data.seconds,
                    ds3231_dny[ds3231_data.day-1], ds3231_data.date, ds3231_data.month, ds3231_data.year);
            t1.tm_sec  = ds3231_data.seconds;
            t1.tm_min  = ds3231_data.minutes;
            t1.tm_hour = ds3231_data.hours;
            t1.tm_wday = ds3231_data.day;
            t1.tm_mday = ds3231_data.date;
            t1.tm_mon  = ds3231_data.month - 1;
            t1.tm_year = ds3231_data.year + 100;
            setRTC(&t1);
        } 
        
    }
    // úklid seznamu (uvolnění paměti)
    zrus_zname_wifi();
    
    // budik
    datetime_t budik = {
            .year  = -1,
            .month = -1,
            .day   = -1,
            .dotw  = -1,
            .hour  = -1,
            .min   = 0,
            .sec   = 0
    };
    rtc_set_alarm(&budik, &budik_callback);
    // zápis na displej
    SH1106_clear(&disp);
    SH1106_drawString(&disp, 0, 0,  &font_tahoma_10, buf1);
    SH1106_drawString(&disp, 0, 12, &font_tahoma_10, buf2);     
    SH1106_drawString(&disp, 0, 24, &font_tahoma_10, buf3);
    SH1106_draw(&disp); 
    int budik_count = 0;
    
    while (true)
    {
        
        // nastavíme si přesný timeout 1s
        absolute_time_t cekam = make_timeout_time_ms(1000);
        memset(buf1,'\0',256);
        memset(buf2,'\0',256);
        memset(buf3,'\0',256);
        getDateNow(&t1);
        
        // timezone a letní čas
        // t1.tm_hour += TIMEZONE/3600;
        // letní čas je od 29. března 2026 1:00:00 do 25. října 2025 1:00:00
        if( t1.tm_mon >= 2 && t1.tm_mon <= 9 && t1.tm_mday ) {  // letní čas
            
        } else { // normální čas
            
        }
        strftime(Date, sizeof(Date), "%a, %d %b %Y %k:%M:%S %Z", &t1);
        sprintf(buf1,"%s %d. %s %d",dny[t1.tm_wday], t1.tm_mday, mesice[t1.tm_mon], t1.tm_year+1900);
        //strftime(buf1, sizeof(buf1), "%a, %d %b %Y", &t1);
        strftime(buf2, sizeof(buf2), "%k:%M:%S", &t1);
        printf("Čas RTC: %s DS3231: ", Date);
        if( ds3231_read_current_time( &ds3231, &ds3231_data) ) {
            printf("Nedostal jsem data ze záložních hodin.\r");
        } else {
            printf("%02u:%02u:%02u  %10s    %02u.%02u.20%02u\r", 
                   ds3231_data.hours, ds3231_data.minutes, ds3231_data.seconds,
                   ds3231_dny[ds3231_data.day-1], ds3231_data.date, ds3231_data.month, 
                   ds3231_data.year);
        }
        // aktualizace datumu
	    if(t1.tm_year != t2.tm_year || t1.tm_mon != t2.tm_mon || t1.tm_mday != t2.tm_mday ) { 
            sprintf(buf3,"%s %d.%s %d",
                    dny[t1.tm_wday], t1.tm_mday, mesice[t1.tm_mon], t1.tm_year+1900);
        }
       
        if( t1.tm_sec != t2.tm_sec) { 
            memcpy(&t2,&t1,sizeof(struct tm));
        }
        SH1106_clear(&disp);
        SH1106_drawString(&disp, 0, 0,  &font_tahoma_10, buf1); // datum
        SH1106_drawString(&disp, 0, 12, &font_tahoma_10, "UTC");     
        SH1106_drawString(&disp, 0, 20, &font_spleen_16x32, buf2); // čas
        if(budik_fired) {
            SH1106_drawChar(&disp, 10, 52, &font_spleen_8x16, 0x237e); // budík
        }
        SH1106_draw(&disp);
        if(budik_fired) { // pokud je spuštěn budík, děláme sirénu
            for(int i=0; i<4; i++) {
                sirena();
            }
            budik_count++;
        }
        if(budik_count == 20 || !gpio_get(22) ){
            budik_fired = false;
            budik_count = 0;
            if( budik.min < 50 ) { // nastavíme další budík za 10 minut
                budik.min += 10;
            } else {
                budik.min = 0;
            }
            rtc_set_alarm(&budik, &budik_callback);
        }
        // čekání do uplynutí timeoutu, celý cyklus bude trvat přesně 1s
        busy_wait_until(cekam);
    }
    return 0;
}
// --------------------------------------------------------------------------------------------