V tomto článku popíšu měření a posílání dat pomocí virtuální privátní sítě Wireguard. Wireguard bude na IPv4, na IPv6 se mi ho na Picu nepodařilo rozchodit. Měřící sonda (Raspberry Pi PicoW) se připojí na Wifi a dostane IPv4 adresu, výchozí bránu a DNS z DHCP serveru. Měření teploty, tlaku a relativní vlhkosti je čidlem BME280.
BME280
| pin | Pico pin | popis |
|---|---|---|
VCC |
3V3 36 |
napájecí napětí 3.3V |
GND |
GND 18 |
zem 0V |
SCL |
GPIO14 19 |
hodinová linka sběrnice i2c nebo SPI |
SDA |
GPIO15 20 |
datová linka sběrnice i2c nebo SPI |
CSB |
GPIO13 17 |
výběr čipu (chip select) |
SDO |
GPIO12 16 |
MISO v režimu SPI |
Zdrojové kódy
cmake_minimum_required(VERSION 3.13)
set(PICO_BOARD pico_w)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(PicoWG4_mereni C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
pico_sdk_init()
include(pico_wireguard_import.cmake)
# BME280
add_compile_definitions(SPI_PORT=spi1)
add_compile_definitions(SPI_RX=12)
add_compile_definitions(SPI_CS=13)
add_compile_definitions(SPI_SCK=14)
add_compile_definitions(SPI_TX=15)
add_compile_definitions(ALTITUDE_AT_LOC=520)
add_compile_definitions(BME280_32BIT_ENABLE)
add_compile_definitions(UPDATE_INTERVAL=60)
add_executable( PicoWG4_mereni
main.c
hodiny.c
bme_sensor.c
disp/ssd1306.c
pico-bme280/bme280.c
pico-bme280/user.c
)
target_include_directories( PicoWG4_mereni PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${pico_wireguard_SOURCE_DIR}/src
${CMAKE_CURRENT_LIST_DIR}/disp
${CMAKE_CURRENT_LIST_DIR}/pico-bme280
)
include(arguments.cmake)
target_link_libraries( PicoWG4_mereni
pico_stdlib
pico_cyw43_arch_lwip_threadsafe_background
pico_wireguard
hardware_i2c
hardware_rtc
hardware_spi
)
pico_enable_stdio_usb(PicoWG4_mereni 1)
pico_enable_stdio_uart(PicoWG4_mereni 0)
pico_add_extra_outputs(PicoWG4_mereni)
if (DEFINED ENV{PICO_WIREGUARD_PATH} AND (NOT PICO_WIREGUARD_PATH))
set(PICO_WIREGUARD_PATH $ENV{PICO_WIREGUARD_PATH})
message("Using PICO_WIREGUARD_PATH from environment ('${PICO_WIREGUARD_PATH}')")
endif ()
if (NOT PICO_WIREGUARD_PATH)
message(FATAL_ERROR "No pico wireguard found")
endif ()
add_subdirectory(${PICO_WIREGUARD_PATH} build/lib/pico_wireguard)
if (DEFINED ENV{WIFI_SSID} AND (NOT WIFI_SSID))
set(WIFI_SSID $ENV{WIFI_SSID})
message("Using WIFI_SSID from environment ('${WIFI_SSID}')")
endif()
if (NOT WIFI_SSID)
message(FATAL_ERROR "Missing argument WIFI_SSID")
endif()
if (DEFINED ENV{WIFI_PASSWORD} AND (NOT WIFI_PASSWORD))
set(WIFI_PASSWORD $ENV{WIFI_PASSWORD})
message("Using WIFI_PASSWORD from environment ('${WIFI_PASSWORD}')")
endif()
if (NOT WIFI_PASSWORD)
message(FATAL_ERROR "Missing argument WIFI_PASSWORD")
endif()
if (DEFINED ENV{WG_PRIVATE_KEY} AND (NOT WG_PRIVATE_KEY))
set(WG_PRIVATE_KEY $ENV{WG_PRIVATE_KEY})
message("Using WG_PRIVATE_KEY from environment ('${WG_PRIVATE_KEY}')")
endif()
if (NOT WG_PRIVATE_KEY)
message(FATAL_ERROR "Missing argument WG_PRIVATE_KEY")
endif()
if (DEFINED ENV{WG_ADDRESS} AND (NOT WG_ADDRESS))
set(WG_ADDRESS $ENV{WG_ADDRESS})
message("Using WG_ADDRESS from environment ('${WG_ADDRESS}')")
endif()
if (NOT WG_ADDRESS)
message(FATAL_ERROR "Missing argument WG_ADDRESS")
endif()
if (DEFINED ENV{WG_SUBNET_MASK_IP} AND (NOT WG_SUBNET_MASK_IP))
set(WG_SUBNET_MASK_IP $ENV{WG_SUBNET_MASK_IP})
message("Using WG_SUBNET_MASK_IP from environment ('${WG_SUBNET_MASK_IP}')")
endif()
if (NOT WG_SUBNET_MASK_IP)
message(FATAL_ERROR "Missing argument WG_SUBNET_MASK_IP")
endif()
if (DEFINED ENV{WG_GATEWAY_IP} AND (NOT WG_GATEWAY_IP))
set(WG_GATEWAY_IP $ENV{WG_GATEWAY_IP})
message("Using WG_GATEWAY_IP from environment ('${WG_GATEWAY_IP}')")
endif()
if (NOT WG_GATEWAY_IP)
message(FATAL_ERROR "Missing argument WG_GATEWAY_IP")
endif()
if (DEFINED ENV{WG_PUBLIC_KEY} AND (NOT WG_PUBLIC_KEY))
set(WG_PUBLIC_KEY $ENV{WG_PUBLIC_KEY})
message("Using WG_PUBLIC_KEY from environment ('${WG_PUBLIC_KEY}')")
endif()
if (NOT WG_PUBLIC_KEY)
message(FATAL_ERROR "Missing argument WG_PUBLIC_KEY")
endif()
if (DEFINED ENV{WG_KEEPALIVE} AND (NOT WG_KEEPALIVE))
set(WG_KEEPALIVE $ENV{WG_KEEPALIVE})
message("Using WG_KEEPALIVE from environment ('${WG_KEEPALIVE}')")
endif()
if( NOT WG_KEEPALIVE)
message(FATAL_ERROR "Missing argument WG_KEEPALIVE")
endif()
if (DEFINED ENV{WG_ALLOWED_IP} AND (NOT WG_ALLOWED_IP))
set(WG_ALLOWED_IP $ENV{WG_ALLOWED_IP})
message("Using WG_ALLOWED_IP from environment ('${WG_ALLOWED_IP}')")
endif()
if (NOT WG_ALLOWED_IP)
message(FATAL_ERROR "Missing argument WG_ALLOWED_IP")
endif()
if (DEFINED ENV{WG_ALLOWED_IP_MASK_IP} AND (NOT WG_ALLOWED_IP_MASK_IP))
set(WG_ALLOWED_IP_MASK_IP $ENV{WG_ALLOWED_IP_MASK_IP})
message("Using WG_ALLOWED_IP_MASK_IP from environment ('${WG_ALLOWED_IP_MASK_IP}')")
endif()
if (NOT WG_ALLOWED_IP_MASK_IP)
message(FATAL_ERROR "Missing argument WG_ALLOWED_IP_MASK_IP")
endif()
if (DEFINED ENV{WG_ENDPOINT_IP} AND (NOT WG_ENDPOINT_IP))
set(WG_ENDPOINT_IP $ENV{WG_ENDPOINT_IP})
message("Using WG_ENDPOINT_IP from environment ('${WG_ENDPOINT_IP}')")
endif()
if (NOT WG_ENDPOINT_IP)
message(FATAL_ERROR "Missing argument WG_ENDPOINT_IP")
endif()
if (DEFINED ENV{WG_ENDPOINT_PORT} AND (NOT WG_ENDPOINT_PORT))
set(WG_ENDPOINT_PORT $ENV{WG_ENDPOINT_PORT})
message("Using WG_ENDPOINT_PORT from environment ('${WG_ENDPOINT_PORT}')")
endif()
if (NOT WG_ENDPOINT_PORT)
message(FATAL_ERROR "Missing argument WG_ENDPOINT_PORT")
endif()
if (DEFINED ENV{NTP_SERVER} AND (NOT NTP_SERVER))
set(NTP_SERVER $ENV{NTP_SERVER})
message("Using NTP_SERVER from environment ('${NTP_SERVER}')")
endif()
if (NOT NTP_SERVER)
message(FATAL_ERROR "Missing argument NTP_SERVER")
endif()
if (DEFINED ENV{MOJE_HOSTNAME} AND (NOT MOJE_HOSTNAME))
set(MOJE_HOSTNAME $ENV{MOJE_HOSTNAME})
message("Using MOJE_HOSTNAME from environment ('${MOJE_HOSTNAME}')")
endif()
if (NOT MOJE_HOSTNAME)
message(FATAL_ERROR "Missing argument MOJE_HOSTNAME")
endif()
if (DEFINED ENV{MERENI_PORT} AND (NOT MERENI_PORT))
set(MERENI_PORT $ENV{MERENI_PORT})
message("Using MERENI_PORT from environment ('${MERENI_PORT}')")
endif()
if (NOT MERENI_PORT)
message(FATAL_ERROR "Missing argument MERENI_PORT")
endif()
target_compile_definitions(PicoWG4_mereni PRIVATE
WIFI_SSID=${WIFI_SSID}
WIFI_PASSWORD=${WIFI_PASSWORD}
WG_PRIVATE_KEY=${WG_PRIVATE_KEY}
WG_ADDRESS=${WG_ADDRESS}
WG_SUBNET_MASK_IP=${WG_SUBNET_MASK_IP}
WG_GATEWAY_IP=${WG_GATEWAY_IP}
WG_PUBLIC_KEY=${WG_PUBLIC_KEY}
WG_KEEPALIVE=${WG_KEEPALIVE}
WG_ALLOWED_IP=${WG_ALLOWED_IP}
WG_ALLOWED_IP_MASK_IP=${WG_ALLOWED_IP_MASK_IP}
WG_ENDPOINT_IP=${WG_ENDPOINT_IP}
WG_ENDPOINT_PORT=${WG_ENDPOINT_PORT}
NTP_SERVER=${NTP_SERVER}
MOJE_HOSTNAME=${MOJE_HOSTNAME}
MERENI_PORT=${MERENI_PORT}
)
#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 1
#define LWIP_IPV4 1
#define LWIP_TCP 1
#define LWIP_UDP 1
#define LWIP_DNS 1
#define LWIP_TCP_KEEPALIVE 1
#define LWIP_NETIF_TX_SINGLE_PBUF 1
#define DHCP_DOES_ARP_CHECK 0
#define LWIP_DHCP_DOES_ACD_CHECK 0
#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__ */
/* Nastavení Wifi karty */
int setup(uint32_t country, const char *ssid, const char *pass,
uint32_t auth, const char *hostname, ip_addr_t *ip,
ip_addr_t *mask, ip_addr_t *gw)
{
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;
while (status >= 0 && 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í k Wifi: %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);
if (ip != NULL)
{
netif_set_ipaddr(netif_default, ip);
}
if (mask != NULL)
{
netif_set_netmask(netif_default, mask);
}
if (gw != NULL)
{
netif_set_gw(netif_default, gw);
}
printf("IP: %s\n", ip4addr_ntoa(netif_ip_addr4(netif_default)));
printf("Maska: %s\n", ip4addr_ntoa(netif_ip_netmask4(netif_default)));
printf("Brána: %s\n", ip4addr_ntoa(netif_ip_gw4(netif_default)));
printf("Hostname: %s\n", netif_get_hostname(netif_default));
}
return status;
}
Nastavení RTE hodin pomocí SNTP protokolu:
/* hodiny.h
*
*/
struct timeStatus
{
bool ready;
struct udp_pcb *pcb;
};
// získání času z hodin Pica
bool getDateNow(struct tm *t);
bool pollSNTP(struct timeStatus *tstatus);
struct timeStatus *getSNTP();
void cancelSNTP(struct timeStatus *tstatus);
void dns_found(const char *name, const ip_addr_t *ip, void *arg);
/* hodiny.c
* Nastavení hodin pomocí SNTP protokolu
* (c) Jirka Chráska 2025, <jirka@lixis.cz>
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/pbuf.h"
#include "lwip/udp.h"
#include "hardware/rtc.h"
#include "time.h"
#include "lwip/dns.h"
#define STRINGIFY(x) #x
#define TO_STRING(x) STRINGIFY(x)
// nastavení časové zóny Europe/Prague
// středoevropský čas (v létě to nebude fungovat)
#define TIMEZONE 3600
//#define NTP_SERVER "tik.cesnet.cz"
// 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;
};
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 + TIMEZONE;
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;
}
void dns_found(const char *name, const ip_addr_t *ip, void *arg)
{
struct timeStatus *tstatus = (struct timeStatus *)arg;
printf("IP NTP serveru z DNS %s\n", ipaddr_ntoa(ip));
struct udp_pcb *pcb = udp_new();
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;
ip_addr_t ip;
cyw43_arch_lwip_begin();
err_t err = dns_gethostbyname(TO_STRING(NTP_SERVER), &ip, dns_found, tstatus);
cyw43_arch_lwip_end();
if (err == ERR_OK)
{
printf("DNS cache %s\n", ipaddr_ntoa(&ip));
dns_found("", &ip, tstatus);
}
return tstatus;
}
void cancelSNTP(struct timeStatus *tstatus)
{
if (tstatus != NULL)
{
udp_remove(tstatus->pcb);
free(tstatus);
tstatus = NULL;
printf("čekám na DNS\n");
}
}
Sensor teploty, tlaku a relativní vlkosti BME280:
/* bme.h */
// hardware-specific intialization
// SPI_* constants from CMakeLists.txt or user.h
void init_bme280();
// initialize sensor
int8_t init_sensor(struct bme280_dev *dev, uint32_t *delay);
// read sensor values
int8_t read_sensor(struct bme280_dev *dev, uint32_t *delay, struct bme280_data *data);
// print sensor data to console
void print_data(struct bme280_data *data);
/* bme280 */
#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "user.h"
#include "bme280.h"
// ---------------------------------------------------------------------------
// hardware-specific intialization
// SPI_* constants from CMakeLists.txt or user.h
void init_bme280()
{
spi_init(SPI_PORT, 1000000); // SPI with 1Mhz
gpio_set_function(SPI_RX, GPIO_FUNC_SPI);
gpio_set_function(SPI_SCK,GPIO_FUNC_SPI);
gpio_set_function(SPI_TX, GPIO_FUNC_SPI);
gpio_init(SPI_CS);
gpio_set_dir(SPI_CS, GPIO_OUT);
gpio_put(SPI_CS, 1); // Chip select is active-low
}
// ---------------------------------------------------------------------------
// initialize sensor
int8_t init_sensor(struct bme280_dev *dev, uint32_t *delay)
{
int8_t rslt = BME280_OK;
uint8_t settings;
// give sensor time to startup
sleep_ms(5); // datasheet: 2ms
// basic initialization
dev->intf_ptr = SPI_PORT; // SPI_PORT is an address
dev->intf = BME280_SPI_INTF;
dev->read = user_spi_read;
dev->write = user_spi_write;
dev->delay_us = user_delay_us;
rslt = bme280_init(dev);
#ifdef DEBUG
printf("[DEBUG] chip-id: 0x%x\n",dev->chip_id);
#endif
if (rslt != BME280_OK) {
return rslt;
}
// configure for forced-mode, indoor navigation
dev->settings.osr_h = BME280_OVERSAMPLING_1X;
dev->settings.osr_p = BME280_OVERSAMPLING_16X;
dev->settings.osr_t = BME280_OVERSAMPLING_2X;
dev->settings.filter = BME280_FILTER_COEFF_16;
settings = BME280_OSR_PRESS_SEL | BME280_OSR_TEMP_SEL | BME280_OSR_HUM_SEL | BME280_FILTER_SEL;
if (rslt != BME280_OK) {
return rslt;
}
rslt = bme280_set_sensor_settings(settings,dev);
*delay = bme280_cal_meas_delay(&dev->settings);
#ifdef DEBUG
printf("[DEBUG] delay: %u µs\n",*delay);
#endif
return rslt;
}
// ---------------------------------------------------------------------------
// read sensor values
int8_t read_sensor(struct bme280_dev *dev, uint32_t *delay, struct bme280_data *data)
{
int8_t rslt;
rslt = bme280_set_sensor_mode(BME280_FORCED_MODE, dev);
if (rslt != BME280_OK) {
return rslt;
}
dev->delay_us(*delay,dev->intf_ptr);
return bme280_get_sensor_data(BME280_ALL,data,dev);
}
// ---------------------------------------------------------------------------
// print sensor data to console
void print_data(struct bme280_data *data)
{
float temp, press, hum;
float alt_fac = pow(1.0-ALTITUDE_AT_LOC/44330.0, 5.255);
temp = 0.01f * data->temperature;
press = 0.01f * data->pressure/alt_fac;
hum = 1.0f / 1024.0f * data->humidity;
printf("%0.1f deg C, %0.0f hPa, %0.0f%%\n", temp, press, hum);
}
// ---------------------------------------------------------------------------
/* Posílání hodnot teploty, tlaku a relativní vlhkosti pomocí wireguardu.
* (c) Jirka Chráska 2026, <jirka@lixis.cz
* Verze 1.0
* Problém k vyřešení:
* Někdy se wireguard připojuje hodně dlouho.
* Stačí na serveru udělat: wg setconf wg0 /etc/wireguard/wg0.conf
* a připojí se to téměř okamžitě.
* Čidlo měření: Bosch BME280
*/
#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "pico/cyw43_arch.h"
#include <lwip/ip.h>
#include <lwip/ip_addr.h>
#include <lwip/netif.h>
#include <lwip/pbuf.h>
#include <lwip/udp.h>
#include "wireguardif.h"
#include "setupWifi.h"
#include "time.h"
#include "hodiny.h"
#include "user.h"
#include "bme280.h"
#include "bme.h"
//#include "argument_definitions.h"
#define STRINGIFY(x) #x
#define TO_STRING(x) STRINGIFY(x)
#define WG_LISTEN_PORT 51820
#define KAM_POSLAT TO_STRING(WG_GATEWAY_IP)
#define UMISTENI "kancelář Jaroměř"
static const char *net_errors[] = { "OK",
"ERR_MEM",
"ERR_BUF",
"ERR_TIMEOUT",
"ERR_RTE",
"ERR_INPROGRESS",
"ERR_VAL",
"ERR_WOULDBLOCK",
"ERR_USE",
"ERR_ALREADY",
"ERR_ISCONN",
"ERR_CONN",
"ERR_IF",
"ERR_ABRT",
"ERR_RST",
"ERR_CLSD",
"ERR_ARG",
"ERR_UNKNOWN" };
// ---------------------------------------------------------------------------------
// chybová hlášení převedená na řetězce
const char * chyba( int err )
{
const char *err_str;
switch(err) {
case 0: err_str = net_errors[0]; break; /** No error, everything OK. */
case -1: err_str = net_errors[1]; break; /** Out of memory error. */
case -2: err_str = net_errors[2]; break; /** Out . */
case -3: err_str = net_errors[3]; break; /** Buffer error. */
case -4: err_str = net_errors[4]; break;
case -5: err_str = net_errors[5]; break;
case -6: err_str = net_errors[6]; break;
case -7: err_str = net_errors[7]; break;
case -8: err_str = net_errors[8]; break;
case -9: err_str = net_errors[9]; break;
case -10: err_str = net_errors[10]; break;
case -11: err_str = net_errors[11]; break;
case -12: err_str = net_errors[12]; break;
case -13: err_str = net_errors[13]; break;
case -14: err_str = net_errors[14]; break;
case -15: err_str = net_errors[15]; break;
case -16: err_str = net_errors[16]; break;
default: err_str = net_errors[17]; break;
}
return err_str;
}
// ---------------------------------------------------------------------------------
static struct netif wg_netif_struct = {0};
static struct netif *wg_netif = NULL;
static uint8_t wireguard_peer_index = WIREGUARDIF_INVALID_INDEX;
static void wireguard_setup()
{
struct wireguardif_init_data wg;
struct wireguardif_peer peer;
memset(&wg,0,sizeof(wg));
// nastavení wireguard sítě
ip_addr_t ipaddr ;
ipaddr_aton(TO_STRING(WG_ADDRESS), &ipaddr);
ip_addr_t netmask ;
ipaddr_aton(TO_STRING(WG_SUBNET_MASK_IP), &netmask);
ip_addr_t gateway ;
ipaddr_aton(TO_STRING(WG_GATEWAY_IP), &gateway);
// Nastavení struktury zařízení WireGuard
wg.private_key = TO_STRING(WG_PRIVATE_KEY);
wg.listen_port = WG_LISTEN_PORT ;
printf("WG listen port: %d\n", wg.listen_port);
wg.bind_netif = NULL;
// Registrace nové WireGuard síťové karty do lwIP
wg_netif = netif_add(&wg_netif_struct, &ipaddr, &netmask, &gateway, &wg, &wireguardif_init, &ip_input);
// Nastavení rozhraní wireguard
// Mark the interface as administratively up,
// link up flag is set automatically when peer connects
netif_set_up(wg_netif);
// Inicializace struktury prvního konce WireGuard
wireguardif_peer_init(&peer);
peer.public_key = TO_STRING(WG_PUBLIC_KEY);
printf("WG public_key: %s\n", peer.public_key);
peer.preshared_key = NULL;
peer.keep_alive = WG_KEEPALIVE ;
printf("WG keep_alive: %d\n", peer.keep_alive);
peer.allowed_ip = gateway;
peer.allowed_mask = netmask;
// IP adresa a port endpointu
ipaddr_aton(TO_STRING(WG_ENDPOINT_IP), &peer.endpoint_ip);
peer.endport_port = WG_ENDPOINT_PORT ;
printf("WG enpoint: port: %d\n",peer.endport_port);
// Register the new WireGuard peer with the netwok interface
err_t e = wireguardif_add_peer(wg_netif, &peer, &wireguard_peer_index);
printf("wireguardif_add_peer: %s\n", chyba(e));
if ((wireguard_peer_index != WIREGUARDIF_INVALID_INDEX) && !ip_addr_isany(&peer.endpoint_ip)) {
// Start outbound connection to peer
err_t err = wireguardif_connect(wg_netif, wireguard_peer_index);
printf("wireguardif_connect: %s\n", chyba(err));
}
}
// ------------------------------------------------------------------------------------------
// OLED displej
#include "disp/ssd1306.h"
#include "disp/font_spleen_8x5.h"
#include "disp/font_spleen_16x8a.h"
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 32
#define I2C_ADDRESS 0x3C
#define I2C_FREQ 400000
uint SDA_PIN=4;
uint SCL_PIN=5;
ssd1306_t disp;
// nastavení I2C sběrnice
void setup_i2c(void)
{
i2c_init(i2c0, I2C_FREQ);
gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(SCL_PIN, GPIO_FUNC_I2C);
// Pull-up rezistory jsou v displeji, netřeba nastavovat
}
void zapnuti_displeje( void )
{
ssd1306_init(&disp, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, i2c0); // inicializace
ssd1306_poweron(&disp); // zapnutí displeje
ssd1306_clear(&disp); // vymazání displeje
ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, TO_STRING( MOJE_HOSTNAME ));
ssd1306_show(&disp); // zobrazíme písmenka na displeji
}
// --------------------------------------------------------------------------
// Wifi připojení
int connect()
{
char ssid[] = TO_STRING( WIFI_SSID );
char pass[] = TO_STRING( WIFI_PASSWORD );
uint32_t country = CYW43_COUNTRY_CZECH_REPUBLIC;
uint32_t auth = CYW43_AUTH_WPA2_MIXED_PSK;
return setup(country, ssid, pass, auth, TO_STRING( MOJE_HOSTNAME ), NULL, NULL, NULL);
}
// ------------------------------------------------------------------------------------------
ip_addr_t moje_ip;
ip_addr_t ip_kam;
static struct udp_pcb *pcb;
void setup_posilani()
{
pcb = udp_new();
}
uint16_t moje_mac[NETIF_MAX_HWADDR_LEN];
#define PAYLOAD_MAX 1024
err_t send_measured_data(ip_addr_t *ip, uint16_t port, float temperature, float pressure, float humidity, struct tm *t)
{
char buf1[128];
// zpráva
// {
// "sonda":"jmenosondy", // jméno sondy
// "mac":"f8:b1:56:d3:a6:48", // MAC adresa sondy
// "localtime": "yyyy-mm-dd hh:MM:ss", // lokální čas na sondě
// "location": "umisteni", // kde je sonda umístěna
// "data":{
// ["temperature_celsius", 25.3], // naměřená teplota ve ˚C
// ["atmospheric_pressure_hPa", 1023.1], // naměřený atmosférický tlak v hPa
// ["humidity_percent", 34] // relativní vlhkost v %
// }
// }
struct pbuf *packet = pbuf_alloc(PBUF_TRANSPORT,PAYLOAD_MAX,PBUF_RAM);
char *payload = (char *) packet->payload;
memset(payload,0,PAYLOAD_MAX);
strftime(buf1, sizeof(buf1), "%Y-%b-%d %k:%M:%S CET", t);
sprintf(payload,"{\n"
"\"sonda\":\"%s\",\n"
"\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\",\n"
"\"localtime\":\"%s\",\n"
"\"location\":\"%s\",\n"
"\"data\"{\n"
" [\"temperature_celsius\", %4.1f],\n"
" [\"athmospheric_pressure_hPa\", %5.1f]\n"
" [\"humidity_percent\", %4.1f]\n"
" }\n"
"}\n",
TO_STRING( MOJE_HOSTNAME ),
moje_mac[0],moje_mac[1],moje_mac[2],moje_mac[3],moje_mac[4],moje_mac[5],
buf1,
UMISTENI,
temperature, pressure, humidity);
err_t error = udp_sendto(pcb, packet, ip, port);
pbuf_free(packet);
return error;
}
// ------------------------------------------------------------------------------------------
#define RESET_PIN 22
// reset v případě neřešitelné chyby
void software_reset()
{
// *((volatile uint32_t*)(PPB_BASE + 0x0ED0C)) = 0x5FA0004;
// watchdog_reboot (0, SRAM_END, 10)
gpio_put(RESET_PIN,1);
}
// ------------------------------------------------------------------------------------------
int main()
{
int connection_status = 0;
uint16_t result;
char buf1[128];
int len;
char message[256];
ip_addr_t wg_endpoint;
struct tm t;
struct bme280_dev dev;
struct bme280_data sensor_data;
int8_t rslt;
uint32_t delay;
memset(message,'\0',256);
memset(buf1,'\0',128);
// sériová linka pře USB
stdio_init_all();
sleep_ms(1000); // pro připojení minicomu
// nastavení resetovacího pinu
gpio_init(RESET_PIN);
gpio_set_dir(RESET_PIN,true);
// inicializace a zapnutí displeje
setup_i2c();
zapnuti_displeje();
printf("\nMěření teploty, tlaku a relativní vlhkosti a posílání na server Wireguardem\n");
printf("(c) Jirka Chráska 2026, <jirka@lixis.cz>\n");
printf("Verze 1.0\n\n");
// inicializace sensoru teploty, tlaku a vlhkosti BME280
init_bme280();
rslt = init_sensor(&dev,&delay);
if (rslt != BME280_OK) {
printf("Nemohu inicializovat BME280 sensor. RC: %d\n", rslt);
} else {
printf("BME280 inicializováno, prodleva mezi měřeními %d us.\n", delay);
}
// připojení Wifi
connect();
// MAC adresa
printf("\nMAC: ");
for(int i=0; i<NETIF_MAX_HWADDR_LEN; i++) {
moje_mac[i] = netif_default->hwaddr[i];
if( i < NETIF_MAX_HWADDR_LEN-1 ) {
printf("%02x:", moje_mac[i]);
} else {
printf("%02x\n",moje_mac[i]);
}
}
// nastavení času
while (true)
{
ssd1306_clear(&disp);
ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_8x5, "Hodiny...");
ssd1306_show(&disp); // zobrazíme písmenka na displeji
struct timeStatus *tstatus = getSNTP();
sleep_ms(500);
if (pollSNTP(tstatus))
break;
cancelSNTP(tstatus);
}
// nastavení Wireguardu
wireguard_setup();
setup_posilani();
ip4addr_aton(KAM_POSLAT,&ip_kam);
ip4addr_aton(TO_STRING( WG_ENDPOINT_IP ),&wg_endpoint);
err_t er;
char *err_str;
u16_t wgport = WG_ENDPOINT_PORT;
do {
getDateNow(&t);
er = wireguardif_peer_is_up(wg_netif, wireguard_peer_index, &wg_endpoint, &wgport);
err_str = (char *) chyba(er);
strftime(buf1, sizeof(buf1), "%a, %d %b %Y %k:%M:%S", &t);
printf("Peer %s:%d %s: čas: %s\n",TO_STRING( WG_ENDPOINT_IP ), wgport, err_str, buf1);
ssd1306_clear(&disp);
sprintf(buf1,"WG peer %s", err_str);
ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_8x5, buf1);
sprintf(buf1,"%s:%d",TO_STRING( WG_ENDPOINT_IP ), wgport);
ssd1306_draw_string_with_font(&disp, 0, 8, 1, font_spleen_8x5, buf1);
strftime(buf1, sizeof(buf1), "%a, %d %b %Y", &t);
ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_8x5, buf1);
strftime(buf1, sizeof(buf1), "%k:%M:%S %Z", &t);
ssd1306_draw_string_with_font(&disp, 0, 24, 1, font_spleen_8x5, buf1);
ssd1306_show(&disp); // zobrazíme písmenka na displeji
sleep_ms(500);
} while (er != 0 );
printf("Peer připojen. Aktualizace: %d s\n\n",UPDATE_INTERVAL);
// měření
float temp, press, hum;
float alt_fac = pow(1.0-ALTITUDE_AT_LOC/44330.0, 5.255);
while( true ){
rslt = read_sensor(&dev,&delay,&sensor_data);
if( rslt == BME280_OK ) {
temp = 0.01f * sensor_data.temperature;
press = 0.01f * sensor_data.pressure/alt_fac;
hum = 1.0f / 1024.0f * sensor_data.humidity;
sprintf(buf1,"%4.1f%cC %3.0f %%", temp, 0xb0, hum);
ssd1306_clear(&disp);
ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, buf1);
sprintf(buf1,"%5.1f hPa", press );
ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_8x5, buf1);
getDateNow(&t);
strftime(buf1, sizeof(buf1), "%d %b %Y %k:%M:%S %Z", &t);
ssd1306_draw_string_with_font(&disp, 0, 25, 1, font_spleen_8x5, buf1);
ssd1306_show(&disp); // zobrazíme písmenka na displeji
er = send_measured_data(&ip_kam,MERENI_PORT,temp, press, hum, &t);
err_str = (char *) chyba(er);
printf("Poslal jsem %s: 'Teplota: %5.1f ˚C, tlak: %5.1f hPa, vlhkost: %4.1f %%', Vzdálená IP: %s %s %d\r",
buf1, temp, press, hum, KAM_POSLAT, err_str, rslt);
} else {
printf("Chyba čtení sensoru BME280 %d\n",rslt);
}
// test připojení wireguardu
er = wireguardif_peer_is_up(wg_netif, wireguard_peer_index, &wg_endpoint, &wgport);
if( er != 0 ) { // pokud spojení upadlo, resetujeme Pico
software_reset();
}
sleep_ms(1000*UPDATE_INTERVAL);
}
return 0;
}
Program na serveru
import asyncio
class ClientDatagramProtocol(asyncio.DatagramProtocol):
def datagram_received(self, data, addr):
message = data.decode("utf8")
print("Received", message, "from", addr)
async def main():
loop = asyncio.get_running_loop()
transport, protocol = await loop.create_datagram_endpoint(
lambda: ClientDatagramProtocol(),
local_addr=('10.205.40.1', 4848))
await asyncio.sleep(100000)
transport.close()
asyncio.run(main())
git clone https://github.com/Mr-Pine/pi-pico-wireguard-lwip
git clone https://github.com/bablokb/pico-bme280
# kde je knihovna pro Wireguard
export PICO_WIREGUARD_PATH=/home/jirka/vyuka_sspvc/pmp/wifi/pi-pico-wireguard-lwip/
# konfigurační proměnné
export WIFI_SSID="ssid"
export WIFI_PASSWORD="heslokwifi"
export WG_PRIVATE_KEY="muj privatni klic"
export WG_ADDRESS="10.205.40.2"
export WG_SUBNET_MASK_IP="255.255.255.0"
export WG_GATEWAY_IP="10.205.40.1"
export WG_PUBLIC_KEY="IFmzhnnuVCbTqRAPAu0PD8GS3JauSptI/5rKfJTuozM="
export WG_KEEPALIVE=10
export WG_ALLOWED_IP="10.205.40.1"
export WG_ALLOWED_IP_MASK_IP="255.255.255.255"
export WG_ENDPOINT_IP="46.253.99.44"
export WG_ENDPOINT_PORT=48484
export NTP_SERVER="tik.cesnet.cz"
export MOJE_HOSTNAME="wg4-cidlo2"
export MERENI_PORT=4848
mkdir build
cd build
cmake ..
make -j8
cp PicoWG4_mereni.uf2 /media/jirka/RPI-RP2/
# wireguard server mereni sit 10.205.40.0/24
# IP wg server 10.205.40.1/24
[Interface]
PrivateKey = privatni klic serveru
ListenPort = 48484
# cidlo1 IP 10.205.40.2/24
[Peer]
PublicKey = DyhXCHh+L9dwRsiCFM+W7KOQEkgQneUz+aJleUrTXV0=
AllowedIPs = 10.205.40.2
inet 10.205.40.1 255.255.255.0 NONE
up
!/usr/local/bin/wg setconf wg0 /etc/wireguard/wg0.conf
Měření teploty, tlahu a relativní vlhkosti a posílání na server Wireguardem
(c) Jirka Chráska 2026, <jirka@lixis.cz>
Verze 1.0
BME280 inicializováno, prodleva mezi měřeními 46100 us.
stav připojení k Wifi: 1 500
stav připojení k Wifi: 2 166
stav připojení k Wifi: 3 41
IP: 192.168.120.183
Maska: 255.255.255.0
Brána: 192.168.120.2
Hostname: wg4-cidlo2
MAC: 28:cd:c1:14:8e:55
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
čekám na DNS
IP NTP serveru z DNS 195.113.144.201
IP NTP serveru z DNS 195.113.144.201
IP NTP serveru z DNS 195.113.144.201
IP NTP serveru z DNS 195.113.144.201
SNTP odpovídá
SNTP odpovídá
WG listen port: 51820
Device init took 36ms
WG public_key: IFmzhnnuVCbTqRAPAu0PD8GS3JauSptI/5rKfJTuozM=
WG keep_alive: 10
WG enpoint: port: 48484
Adding peer took 35ms
wireguardif_add_peer: OK
wireguardif_connect: OK
Peer 46.253.99.44:48484 ERR_CONN: čas: Sat, 03 Jan 2026 0:40:09
Peer 46.253.99.44:48484 OK: čas: Sat, 03 Jan 2026 0:40:09
Peer připojen. Aktualizace: 60 s
Poslal jsem: 'Teplota: 24.7 ˚C, tlak: 1028.3 hPa, vlhkost: 28.2 %', Vzdálená IP: 10.205.40.1 OK 0
mereni# wg
interface: wg0
public key: IFmzhnnuVCbTqRAPAu0PD8GS3JauSptI/5rKfJTuozM=
private key: (hidden)
listening port: 48484
peer: DyhXCHh+L9dwRsiCFM+W7KOQEkgQneUz+aJleUrTXV0=
endpoint: 109.81.118.47:11497
allowed ips: 10.205.40.2/32
latest handshake: 59 seconds ago
transfer: 482.70 KiB received, 14.99 KiB sent
mereni#
mereni# python3 udp_server.py
Received {
"sonda":"wg4-cidlo2",
"mac":"28:cd:c1:14:8e:55",
"localtime":"2026-Jan-03 0:49:11 CET",
"location":"kancelář Jaroměř",
"data"{
["temperature_celsius", 24.7],
["athmospheric_pressure_hPa", 1028.3]
["humidity_percent", 28.3]
}
}
from ('10.205.40.2', 56693)
Received {
"sonda":"wg4-cidlo2",
"mac":"28:cd:c1:14:8e:55",
"localtime":"2026-Jan-03 0:51:11 CET",
"location":"kancelář Jaroměř",
"data"{
["temperature_celsius", 24.7],
["athmospheric_pressure_hPa", 1028.3]
["humidity_percent", 28.4]
}
}
from ('10.205.40.2', 56693)
Received {
"sonda":"wg4-cidlo2",
"mac":"28:cd:c1:14:8e:55",
"localtime":"2026-Jan-03 0:52:11 CET",
"location":"kancelář Jaroměř",
"data"{
["temperature_celsius", 24.7],
["athmospheric_pressure_hPa", 1028.3]
["humidity_percent", 27.9]
}
}
from ('10.205.40.2', 56693)
Received {
"sonda":"wg4-cidlo2",
"mac":"28:cd:c1:14:8e:55",
"localtime":"2026-Jan-03 0:53:11 CET",
"location":"kancelář Jaroměř",
"data"{
["temperature_celsius", 24.7],
["atmospheric_pressure_hPa", 1028.3]
["humidity_percent", 28.3]
}
}
from ('10.205.40.2', 56693)
Konstrukce prototypu


Zdroje a odkazy
Celý projekt ke stažení: ../picow_wireguard4_mereni3.tar.gz
pico-bme280 knihovna pro Bosch sensor BME280
BME280 - čidlo vlhkosti, teploty a tlaku 110kPa I2C / SPI - 3,3V kde se dá sensor koupit