diff --git a/Makefile b/Makefile
index 03826de..6dacfb4 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
 # project subdirectory.
 #
 
-DUMMY_VAR = 1
+DUMMY_VAR = 2
 PROJECT_NAME := meteo
 
 include $(IDF_PATH)/make/project.mk
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index a2fc61a..288cc5e 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,4 +1,8 @@
 set(COMPONENT_SRCS "cmd_wifi.c"
+                   "cmd_ip.c"
+                   "cmd_mqtt.c"
+                   "mqttpub.c"
+                   "nvsconfig.c"
                    "meteo_task.c"
                    "meteo_main.c")
 set(COMPONENT_ADD_INCLUDEDIRS ".")
diff --git a/main/NVS.txt b/main/NVS.txt
new file mode 100644
index 0000000..15a24c7
--- /dev/null
+++ b/main/NVS.txt
@@ -0,0 +1,9 @@
+wifi.use_dhcpc - int 0/1 - use dynamic IP
+wifi.static_ip - str
+wifi.static_gw - str
+wifi.static_mask - str
+
+mqtt.topic - str, base topic, ends with slash
+mqtt.broker - str, mqtt broker
+
+/* wifi ssid/pw are stored in system nvs */
diff --git a/main/cmd_decl.h b/main/cmd_decl.h
index efdda4e..618f693 100644
--- a/main/cmd_decl.h
+++ b/main/cmd_decl.h
@@ -14,6 +14,8 @@ extern "C" {
 
 #include "cmd_system.h"
 #include "cmd_wifi.h"
+#include "cmd_ip.h"
+#include "cmd_mqtt.h"
 
 #ifdef __cplusplus
 }
diff --git a/main/cmd_ip.c b/main/cmd_ip.c
new file mode 100644
index 0000000..afdc43c
--- /dev/null
+++ b/main/cmd_ip.c
@@ -0,0 +1,127 @@
+/* Console example — WiFi commands
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "esp_log.h"
+#include "esp_console.h"
+#include "argtable3/argtable3.h"
+#include "esp_event_loop.h"
+#include "cmd_ip.h"
+
+#include "nvs.h"
+
+/** Arguments used by 'join' function */
+static struct {
+    struct arg_lit *dhcp_on;
+    struct arg_lit *dhcp_off;
+    struct arg_str *static_ip;
+    struct arg_str *static_gw;
+    struct arg_str *static_mask;
+    struct arg_str *static_dns1;
+    struct arg_str *static_dns2;
+    struct arg_end *end;
+} ip_args;
+
+static int ipcmd(int argc, char** argv)
+{
+    esp_err_t rv;
+
+    int nerrors = arg_parse(argc, argv, (void**) &ip_args);
+    if (nerrors != 0) {
+        arg_print_errors(stderr, ip_args.end, argv[0]);
+        return 1;
+    }
+
+    nvs_handle_t hnvs;
+    ESP_ERROR_CHECK( nvs_open("wifi", NVS_READWRITE, &hnvs) );
+
+    if (ip_args.dhcp_on->count) {
+        nvs_set_u8(hnvs, "use_dhcpc", 1);
+    } else if (ip_args.dhcp_off->count) {
+        nvs_set_u8(hnvs, "use_dhcpc", 0);
+    }
+
+    if (ip_args.static_ip->count) {
+        nvs_set_str(hnvs, "static_ip", ip_args.static_ip->sval[0]);
+    }
+
+    if (ip_args.static_gw->count) {
+        nvs_set_str(hnvs, "static_gw", ip_args.static_gw->sval[0]);
+    }
+
+    if (ip_args.static_mask->count) {
+        nvs_set_str(hnvs, "static_mask", ip_args.static_mask->sval[0]);
+    }
+
+    if (ip_args.static_dns1->count) {
+        nvs_set_str(hnvs, "static_dns1", ip_args.static_dns1->sval[0]);
+    }
+
+    if (ip_args.static_dns2->count) {
+        nvs_set_str(hnvs, "static_dns2", ip_args.static_dns2->sval[0]);
+    }
+
+    // read current config
+
+    uint8_t use_dhcpc;
+    rv = nvs_get_u8(hnvs, "use_dhcpc", &use_dhcpc);
+    if (rv != ESP_OK) {
+        use_dhcpc = 1;
+    }
+
+    char ip[16], gw[16], mask[16], dns1[16], dns2[16];
+    size_t iplen = 16;
+    size_t gwlen = 16;
+    size_t masklen = 16;
+    size_t dns1len = 16;
+    size_t dns2len = 16;
+    ip[0] = gw[0] = mask[0] = dns1[0] = dns2[0] = 0;
+
+    nvs_get_str(hnvs, "static_ip", ip, &iplen);
+    nvs_get_str(hnvs, "static_gw", gw, &gwlen);
+    nvs_get_str(hnvs, "static_mask", mask, &masklen);
+    nvs_get_str(hnvs, "static_dns1", dns1, &dns1len);
+    nvs_get_str(hnvs, "static_dns2", dns2, &dns2len);
+
+    printf("DHCP = %s\n", use_dhcpc ? "Yes (dynamic IP)" : "No (static IP)");
+    printf("Saved static IP = %s\n", ip);
+    printf("Saved static GW = %s\n", gw);
+    printf("Saved static MASK = %s\n", mask);
+    printf("Saved static DNS1 = %s\n", dns1);
+    printf("Saved static DNS2 = %s\n", dns2);
+
+    nvs_close(hnvs);
+
+    printf("Any changes are applied after restart.\n");
+
+    return 0;
+}
+
+void console_register_ip()
+{
+    ip_args.dhcp_on = arg_lit0("d", "dynamic", "Enable DHCP");
+    ip_args.dhcp_off = arg_lit0("s", "static", "Disable DHCP (use static)");
+    ip_args.static_ip = arg_str0("a", "ip", "<IP>", "Set static IP");
+    ip_args.static_gw = arg_str0("g", "gw", "<IP>", "Set static GW");
+    ip_args.static_mask = arg_str0("m", "mask", "<IP>", "Set static MASK (e.g. 255.255.255.0)");
+    ip_args.static_dns1 = arg_str0("n", "dns1", "<DNS>", "Set static nameserver1 (e.g. 8.8.8.8)");
+    ip_args.static_dns2 = arg_str0("N", "dns2", "<DNS>", "Set static nameserver2 (e.g. 8.8.4.4)");
+    ip_args.end = arg_end(5);
+
+    const esp_console_cmd_t ip_cmd = {
+        .command = "ip",
+        .help = "Configure TCP/IP",
+        .hint = NULL,
+        .func = &ipcmd,
+        .argtable = &ip_args
+    };
+
+    ESP_ERROR_CHECK( esp_console_cmd_register(&ip_cmd) );
+}
diff --git a/main/cmd_ip.h b/main/cmd_ip.h
new file mode 100644
index 0000000..bd814f6
--- /dev/null
+++ b/main/cmd_ip.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void console_register_ip();
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/main/cmd_mqtt.c b/main/cmd_mqtt.c
new file mode 100644
index 0000000..7b3b9e6
--- /dev/null
+++ b/main/cmd_mqtt.c
@@ -0,0 +1,70 @@
+#include <stdio.h>
+#include <string.h>
+#include "esp_log.h"
+#include "esp_console.h"
+#include "argtable3/argtable3.h"
+#include "esp_event_loop.h"
+#include "cmd_mqtt.h"
+
+#include "nvs.h"
+
+/** Arguments used by 'join' function */
+static struct {
+    struct arg_str *broker;
+    struct arg_str *topic;
+    struct arg_end *end;
+} mqtt_args;
+
+static int mqttcmd(int argc, char** argv)
+{
+    int nerrors = arg_parse(argc, argv, (void**) &mqtt_args);
+    if (nerrors != 0) {
+        arg_print_errors(stderr, mqtt_args.end, argv[0]);
+        return 1;
+    }
+
+    nvs_handle_t hnvs;
+    ESP_ERROR_CHECK( nvs_open("mqtt", NVS_READWRITE, &hnvs) );
+
+    if (mqtt_args.broker->count) {
+        nvs_set_str(hnvs, "broker", mqtt_args.broker->sval[0]);
+    }
+    if (mqtt_args.topic->count) {
+        nvs_set_str(hnvs, "topic", mqtt_args.topic->sval[0]);
+    }
+
+    // read current config
+
+    size_t topic_len = 128;
+    char topic[128] = "";
+    nvs_get_str(hnvs, "topic", topic, &topic_len);
+
+    size_t broker_len = 128;
+    char broker[128] = "";
+    nvs_get_str(hnvs, "broker", broker, &broker_len);
+
+    printf("Topic = %s\n", topic);
+    printf("Broker = %s\n", broker);
+
+    nvs_close(hnvs);
+
+    printf("Any changes are applied after restart.\n");
+    return 0;
+}
+
+void console_register_mqtt()
+{
+    mqtt_args.topic = arg_str0("t", "topic", "<TOPIC>", "Set base of the MQTT topic (including slash)");
+    mqtt_args.broker = arg_str0("b", "broker", "<BROKER>", "Set MQTT broker IP addr");
+    mqtt_args.end = arg_end(2);
+
+    const esp_console_cmd_t mqtt_cmd = {
+        .command = "mqtt",
+        .help = "Configure MQTT",
+        .hint = NULL,
+        .func = &mqttcmd,
+        .argtable = &mqtt_args
+    };
+
+    ESP_ERROR_CHECK( esp_console_cmd_register(&mqtt_cmd) );
+}
diff --git a/main/cmd_mqtt.h b/main/cmd_mqtt.h
new file mode 100644
index 0000000..8830a57
--- /dev/null
+++ b/main/cmd_mqtt.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void console_register_mqtt();
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/main/cmd_wifi.c b/main/cmd_wifi.c
index 56ca129..7858b56 100644
--- a/main/cmd_wifi.c
+++ b/main/cmd_wifi.c
@@ -77,10 +77,12 @@ void initialise_wifi(void)
     }
 
     if (!use_dhcpc) {
-        char ip[16], gw[16], mask[16];
+        char ip[16], gw[16], mask[16], dns1[16], dns2[16];
         size_t iplen = 16;
         size_t gwlen = 16;
         size_t masklen = 16;
+        size_t dns1len = 16;
+        size_t dns2len = 16;
 
         rv = nvs_get_str(hnvs, "static_ip", ip, &iplen);
         if (rv != ESP_OK) {
@@ -100,14 +102,33 @@ void initialise_wifi(void)
             ESP_LOGW(__func__, "Fail to load 'static_mask'");
         }
 
-        printf("Static IP config: IP: %s, GW: %s, Mask: %s\n", ip, gw, mask);
+        rv = nvs_get_str(hnvs, "static_dns1", dns1, &dns1len);
+        if (rv != ESP_OK) {
+            ESP_LOGW(__func__, "Fail to load 'static_dns1'");
+        }
+
+        rv = nvs_get_str(hnvs, "static_dns2", dns2, &dns2len); // can be empty
+        if (rv != ESP_OK) {
+            ESP_LOGW(__func__, "Fail to load 'static_dns2'");
+        }
+
+        printf("Static IP config: IP: %s, GW: %s, Mask: %s, DNS1: %s, DNS2: %s\n", ip, gw, mask, dns1, dns2);
 
         tcpip_adapter_ip_info_t ip_info;
+        tcpip_adapter_dns_info_t dns1_info;
+        tcpip_adapter_dns_info_t dns2_info;
         if (!use_dhcpc) {
             ip_info.ip.addr = ipaddr_addr(ip);
             ip_info.gw.addr = ipaddr_addr(gw);
             ip_info.netmask.addr = ipaddr_addr(mask);
 
+            dns1_info.ip.addr = ipaddr_addr(dns1);
+            if (dns2[0] != 0) {
+                dns2_info.ip.addr = ipaddr_addr(dns2);
+            } else {
+                dns2_info.ip.addr = IPADDR_NONE;
+            }
+
             if (ip_info.ip.addr == IPADDR_NONE || ip_info.gw.addr == IPADDR_NONE || ip_info.netmask.addr == IPADDR_NONE) {
                 use_dhcpc = 1;
                 ESP_LOGW(__func__, "Fail to parse static IP config");
@@ -117,6 +138,12 @@ void initialise_wifi(void)
         if (!use_dhcpc) {
             tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
             tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);
+            if (dns1_info.ip.addr != IPADDR_NONE) {
+                tcpip_adapter_set_dns_info(TCPIP_ADAPTER_IF_STA, TCPIP_ADAPTER_DNS_MAIN, &dns1_info);
+            }
+            if (dns2_info.ip.addr != IPADDR_NONE) {
+                tcpip_adapter_set_dns_info(TCPIP_ADAPTER_IF_STA, TCPIP_ADAPTER_DNS_FALLBACK, &dns2_info);
+            }
         }
     }
     nvs_commit(hnvs);
@@ -192,94 +219,3 @@ void console_register_wifi()
 
     ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) );
 }
-
-/** Arguments used by 'join' function */
-static struct {
-    struct arg_lit *dhcp_on;
-    struct arg_lit *dhcp_off;
-    struct arg_str *static_ip;
-    struct arg_str *static_gw;
-    struct arg_str *static_mask;
-    struct arg_end *end;
-} ip_args;
-
-static int ipcmd(int argc, char** argv)
-{
-    esp_err_t rv;
-
-    int nerrors = arg_parse(argc, argv, (void**) &ip_args);
-    if (nerrors != 0) {
-        arg_print_errors(stderr, ip_args.end, argv[0]);
-        return 1;
-    }
-
-    nvs_handle_t hnvs;
-    ESP_ERROR_CHECK( nvs_open("wifi", NVS_READWRITE, &hnvs) );
-
-    if (ip_args.dhcp_on->count) {
-        nvs_set_u8(hnvs, "use_dhcpc", 1);
-    } else if (ip_args.dhcp_off->count) {
-        nvs_set_u8(hnvs, "use_dhcpc", 0);
-    }
-
-    if (ip_args.static_ip->count) {
-        nvs_set_str(hnvs, "static_ip", ip_args.static_ip->sval[0]);
-    }
-
-    if (ip_args.static_gw->count) {
-        nvs_set_str(hnvs, "static_gw", ip_args.static_gw->sval[0]);
-    }
-
-    if (ip_args.static_mask->count) {
-        nvs_set_str(hnvs, "static_mask", ip_args.static_mask->sval[0]);
-    }
-
-    // read current config
-
-    uint8_t use_dhcpc;
-    rv = nvs_get_u8(hnvs, "use_dhcpc", &use_dhcpc);
-    if (rv != ESP_OK) {
-        use_dhcpc = 1;
-    }
-
-    char ip[16], gw[16], mask[16];
-    size_t iplen = 16;
-    size_t gwlen = 16;
-    size_t masklen = 16;
-    ip[0] = gw[0] = mask[0] = 0;
-
-    nvs_get_str(hnvs, "static_ip", ip, &iplen);
-    nvs_get_str(hnvs, "static_gw", gw, &gwlen);
-    nvs_get_str(hnvs, "static_mask", mask, &masklen);
-
-    printf("DHCP = %s\n", use_dhcpc ? "Yes (dynamic IP)" : "No (static IP)");
-    printf("Saved static IP = %s\n", ip);
-    printf("Saved static GW = %s\n", gw);
-    printf("Saved static MASK = %s\n", mask);
-
-    nvs_close(hnvs);
-
-    printf("Any changes are applied after restart.\n");
-
-    return 0;
-}
-
-void console_register_ip()
-{
-    ip_args.dhcp_on = arg_lit0("d", "dhcp", "Enable DHCP");
-    ip_args.dhcp_off = arg_lit0("s", "no-dhcp", "Disable DHCP (use static)");
-    ip_args.static_ip = arg_str0("a", NULL, "<IP>", "Set static IP");
-    ip_args.static_gw = arg_str0("g", NULL, "<IP>", "Set static GW");
-    ip_args.static_mask = arg_str0("m", NULL, "<IP>", "Set static MASK (e.g. 255.255.255.0)");
-    ip_args.end = arg_end(5);
-
-    const esp_console_cmd_t ip_cmd = {
-        .command = "ip",
-        .help = "Configure TCP/IP",
-        .hint = NULL,
-        .func = &ipcmd,
-        .argtable = &ip_args
-    };
-
-    ESP_ERROR_CHECK( esp_console_cmd_register(&ip_cmd) );
-}
diff --git a/main/cmd_wifi.h b/main/cmd_wifi.h
index 145c313..7baec01 100644
--- a/main/cmd_wifi.h
+++ b/main/cmd_wifi.h
@@ -1,11 +1,3 @@
-/* Console example — declarations of command registration functions.
-
-   This example code is in the Public Domain (or CC0 licensed, at your option.)
-
-   Unless required by applicable law or agreed to in writing, this
-   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
-   CONDITIONS OF ANY KIND, either express or implied.
-*/
 #pragma once
 
 #ifdef __cplusplus
diff --git a/main/meteo_main.c b/main/meteo_main.c
index 97f2562..943ddb8 100644
--- a/main/meteo_main.c
+++ b/main/meteo_main.c
@@ -1,5 +1,6 @@
 #include <stdio.h>
 #include <string.h>
+#include <lwip/ip4_addr.h>
 #include "esp_system.h"
 #include "esp_log.h"
 #include "esp_console.h"
@@ -13,24 +14,11 @@
 #include "nvs.h"
 #include "nvs_flash.h"
 #include "meteo_task.h"
+#include "mqttpub.h"
+#include "nvsconfig.h"
 
 #define TAG "example"
 
-static void initialize_nvs()
-{
-    esp_err_t err = nvs_flash_init();
-    if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
-        ESP_ERROR_CHECK( nvs_flash_erase() );
-        err = nvs_flash_init();
-    }
-    ESP_ERROR_CHECK(err);
-
-    // ensure the namespaces we need exist
-    nvs_handle_t hnvs;
-    ESP_ERROR_CHECK( nvs_open("wifi", NVS_READWRITE, &hnvs) );
-    nvs_close(hnvs);
-}
-
 static void initialize_console()
 {
     /* Disable buffering on stdin */
@@ -81,18 +69,28 @@ static void initialize_console()
 
     /* Set command history size */
     linenoiseHistorySetMaxLen(100);
+
+
+    /* Register commands */
+    esp_console_register_help_command();
+    console_register_system();
+    console_register_wifi();
+    console_register_ip();
+    console_register_mqtt();
 }
 
+
 void app_main()
 {
     initialize_nvs();
+    initialise_wifi();
 
     initialize_console();
-    initialise_wifi();
-    console_register_ip();
+    initialize_mqttpub();
 
     xTaskCreate(meteo_task, "meteo_task", 2048, NULL, 10, NULL);
 
+
     /* Print chip information */
     esp_chip_info_t chip_info;
     esp_chip_info(&chip_info);
@@ -105,12 +103,6 @@ void app_main()
             (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
 
 
-
-    /* Register commands */
-    esp_console_register_help_command();
-    console_register_system();
-    console_register_wifi();
-
     /* Prompt to be printed before each line.
      * This can be customized, made dynamic, etc.
      */
diff --git a/main/meteo_task.c b/main/meteo_task.c
index 3c5709b..dfbdf31 100644
--- a/main/meteo_task.c
+++ b/main/meteo_task.c
@@ -7,6 +7,8 @@
 #include <task.h>
 #include <math.h>
 #include <driver/hw_timer.h>
+#include <esp_log.h>
+#include <inttypes.h>
 #include "meteo_task.h"
 #include "ds18b20.h"
 #include "dht.h"
@@ -14,18 +16,26 @@
 #include "driver/i2c.h"
 #include "circbuf.h"
 #include "bmp280.h"
+#include "mqttpub.h"
+#include "esp_wifi.h"
 
 static volatile uint32_t timestamp = 0;
+static volatile uint32_t last_telemetry_ts = 0;
 
 #define RPS_BUFFER_LEN (60*10)
 static volatile uint16_t history[RPS_BUFFER_LEN] = {};
 static CircBuf rps_cb;
 
 static volatile float rpm_average = 0;
+static volatile float rpm_inst = 0;
 static volatile float rpm_gust = 0;
 
+#define MAGNET_COUNT 3
+
 static volatile uint16_t cycle_count = 0;
 
+#define TAG "meteo"
+
 void calculate_wind();
 
 static void gpio_isr_handler(void *arg)
@@ -57,9 +67,10 @@ void calculate_wind()
     float max_gust = 0;
 
     uint32_t tenmin_sum = 0;
-    uint32_t numsecs = cbuf_count(&rps_cb);
+    int numsecs = (int) cbuf_count(&rps_cb);
     uint16_t threesec1 = 0, threesec2 = 0;
-    for (size_t i = 0; i < numsecs; i++) {
+    uint32_t tensec_sum = 0, tensec_cnt = 0;
+    for (int i = 0; i < numsecs; i++) {
         uint16_t *slot = cbuf_ptr_nth(&rps_cb, i);
         if (!slot) {
             continue;
@@ -68,6 +79,11 @@ void calculate_wind()
         uint16_t slotval = *slot;
         tenmin_sum += (uint32_t) slotval;
 
+        if (i >= numsecs - 10) {
+            tensec_sum += slotval;
+            tensec_cnt++;
+        }
+
         // gust is max avg from 3 seconds within the 10 minutes
         uint32_t gust_sum = (uint32_t) threesec1 + (uint32_t) threesec2 + (uint32_t) slotval;
         threesec1 = threesec2;
@@ -80,11 +96,24 @@ void calculate_wind()
     }
     rpm_gust = max_gust;
     rpm_average = ((float) tenmin_sum / (float) numsecs) * 60.0f;
+    rpm_inst = ((float) tensec_sum / (float) tensec_cnt) * 60.0f;
 }
 
+bool is_nan(float f) {
+    return f != f;
+}
+
+float nan_fallback(float f, float fallback) {
+    if (is_nan(f)) {
+        return fallback;
+    }
+    return f;
+}
 
 void meteo_task(void *pvParameters)
 {
+    static char pldbuf[512];
+
     cbuf_init(&rps_cb, (void *) history, RPS_BUFFER_LEN, 2); // uint16 fields
 
     // Try to unfuck GPIOs
@@ -131,7 +160,8 @@ void meteo_task(void *pvParameters)
     };
     bmp280_init(&bmp_dev, &bmp_conf);
 
-    vTaskDelay(pdMS_TO_TICKS(500));
+
+    vTaskDelay(pdMS_TO_TICKS(5000));
 
     float dht_hum, dht_temp, ds_temp, bmp_temp, bmp_hum, bmp_press;
     while (1) {
@@ -144,24 +174,45 @@ void meteo_task(void *pvParameters)
         // this works ...
         ds_temp = ds18b20_measure_and_read(0, DS18B20_ANY);
         if (ds_temp != ds_temp) {
-            printf("DS failed\n");
+            ESP_LOGE(TAG, "DS failed");
         }
 
         if (!dht_read_float_data(DHT_TYPE_DHT22, 12, &dht_hum, &dht_temp)) {
             dht_hum = dht_temp = NAN;
-            printf("DHT failed\n");
+            ESP_LOGE(TAG, "DHT failed");
         }
 
         if(!bmp280_read_float(&bmp_dev, &bmp_temp, &bmp_press, &bmp_hum)) {
-            printf("BMP failed\n");
+            ESP_LOGE(TAG, "BMP failed");
         }
 
-        printf("Dallas: %.2f °C, ** DHT %.2f °C, %.1f %%r.H, ** WIND avg %.1f, gust %.1f RPM, ** BMP %.2f °C, %f Pa \n",
+        /*
+        {"uptime":125,"heap_free":44472,"heap_lwm":44028,"wifi_rssi":-48}
+        {"ds18b20":21.500000,"dht22_t":22.000000,"dht22_h":42.500000,"wind_avg":0.000000,"wind_inst":0.000000,"wind_gust":0.000000,"bmp280_t":22.540001,"bmp280_p":97578.734375}
+         */
+
+        ESP_LOGI(TAG, "Dallas: %.2f °C, ** DHT %.2f °C, %.1f %%r.H, ** WIND avg %.1f, gust %.1f RPM, ** BMP %.2f °C, %f Pa",
                ds_temp, dht_temp, dht_hum,
                rpm_average, rpm_gust,
                bmp_temp, bmp_press);
 
-        vTaskDelay(pdMS_TO_TICKS(500));
+        snprintf(pldbuf, 512, "{\"ds18b20\":%.2f,\"dht22_t\":%.1f,\"dht22_h\":%.1f,\"wind_avg\":%.1f,\"wind_inst\":%.0f,\"wind_gust\":%.0f,\"bmp280_t\":%.2f,\"bmp280_p\":%.1f}",
+                 nan_fallback(ds_temp, -300), nan_fallback(dht_temp, -300), nan_fallback(dht_hum, -1),
+                 nan_fallback(rpm_average, 0)/MAGNET_COUNT, nan_fallback(rpm_inst, 0)/MAGNET_COUNT, nan_fallback(rpm_gust, 0)/MAGNET_COUNT,
+                 nan_fallback(bmp_temp, -300), nan_fallback(bmp_press, -1));
+
+        mqtt_publish("measurement", pldbuf);
+
+        if (timestamp - last_telemetry_ts >= 5*60 || last_telemetry_ts == 0) {
+            wifi_ap_record_t ap_info = {};
+            esp_wifi_sta_get_ap_info(&ap_info);
+            snprintf(pldbuf, 512, "{\"uptime\":%"PRIu32",\"heap_free\":%d,\"heap_lwm\":%d,\"wifi_rssi\":%d}",
+                     timestamp, heap_caps_get_free_size(0), heap_caps_get_minimum_free_size(0), ap_info.rssi);
+            mqtt_publish("telemetry", pldbuf);
+            last_telemetry_ts = timestamp;
+        }
+
+        vTaskDelay(pdMS_TO_TICKS(15000));
     }
 
     vTaskDelete(NULL);
diff --git a/main/mqttpub.c b/main/mqttpub.c
new file mode 100644
index 0000000..d6fad67
--- /dev/null
+++ b/main/mqttpub.c
@@ -0,0 +1,128 @@
+/**
+ * TODO file description
+ */
+
+#include <mqtt_client.h>
+#include <nvs.h>
+#include <esp_log.h>
+#include "mqttpub.h"
+
+#define TAG "mq"
+
+static bool mqtt_inited = false;
+
+esp_mqtt_client_handle_t s_client;
+static char s_basetopic[128];
+static char s_topicbuf[256];
+
+static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
+{
+    esp_mqtt_client_handle_t client = event->client;
+    int msg_id;
+    // your_context_t *context = event->context;
+    switch (event->event_id) {
+        case MQTT_EVENT_CONNECTED:
+            ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
+
+//            msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
+
+//            ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
+//
+//            msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
+//            ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
+//
+//            msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
+//            ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
+//
+//            msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
+//            ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
+            break;
+        case MQTT_EVENT_DISCONNECTED:
+            ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
+            break;
+
+        case MQTT_EVENT_SUBSCRIBED:
+            ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
+//            msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
+//            ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
+            break;
+        case MQTT_EVENT_UNSUBSCRIBED:
+            ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
+            break;
+        case MQTT_EVENT_PUBLISHED:
+            ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
+            break;
+        case MQTT_EVENT_DATA:
+            ESP_LOGI(TAG, "MQTT_EVENT_DATA");
+            printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
+            printf("DATA=%.*s\r\n", event->data_len, event->data);
+            break;
+        case MQTT_EVENT_ERROR:
+            ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
+            break;
+        default:
+            ESP_LOGI(TAG, "Other event id:%d", event->event_id);
+            break;
+    }
+    return ESP_OK;
+}
+
+
+static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
+    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
+    mqtt_event_handler_cb(event_data);
+}
+
+void initialize_mqttpub()
+{
+    char brokerurl[128];
+    size_t len;
+    esp_err_t rv;
+    nvs_handle_t hnvs;
+
+    ESP_ERROR_CHECK( nvs_open("mqtt", NVS_READWRITE, &hnvs) );
+
+    len = 128;
+    brokerurl[0] = 0;
+    rv = nvs_get_str(hnvs, "broker", brokerurl, &len);
+    if (rv != ESP_OK || brokerurl[0] == 0) {
+        ESP_LOGW(TAG, "Missing MQTT broker!");
+        return;
+    }
+
+    len = 128;
+    s_basetopic[0] = 0;
+    rv = nvs_get_str(hnvs, "topic", s_basetopic, &len);
+    if (rv != ESP_OK) {
+        ESP_LOGW(TAG, "Bad MQTT topic!");
+        return;
+    }
+    ESP_LOGI(TAG, "mqtt.topic = %s", s_basetopic);
+
+    nvs_close(hnvs);
+
+    esp_mqtt_client_config_t mqtt_cfg = {
+        .uri = brokerurl,
+    };
+
+    s_client = esp_mqtt_client_init(&mqtt_cfg);
+    ESP_ERROR_CHECK(esp_mqtt_client_register_event(s_client, ESP_EVENT_ANY_ID, mqtt_event_handler, s_client));
+    ESP_ERROR_CHECK(esp_mqtt_client_start(s_client));
+
+    mqtt_inited = true;
+}
+
+void mqtt_publish(const char *topic, const char *payload)
+{
+    if (!mqtt_inited) {
+        ESP_LOGE(TAG, "MQTT not inited");
+        return;
+    }
+
+    snprintf(s_topicbuf, 256, "%s%s", s_basetopic, topic);
+
+    ESP_LOGI(TAG, "MQTT pub to %s: %s", s_topicbuf, payload);
+
+
+    esp_mqtt_client_publish(s_client, s_topicbuf, payload, (int) strlen(payload), 1, 0);
+}
diff --git a/main/mqttpub.h b/main/mqttpub.h
new file mode 100644
index 0000000..dcdb929
--- /dev/null
+++ b/main/mqttpub.h
@@ -0,0 +1,9 @@
+/**
+ * TODO file description
+ */
+
+#pragma once
+
+void initialize_mqttpub();
+
+void mqtt_publish(const char *topic, const char *payload);
diff --git a/main/nvsconfig.c b/main/nvsconfig.c
new file mode 100644
index 0000000..84aa878
--- /dev/null
+++ b/main/nvsconfig.c
@@ -0,0 +1,125 @@
+/**
+ * TODO file description
+ */
+
+#include <esp_err.h>
+#include <nvs.h>
+#include <esp_log.h>
+#include <string.h>
+#include <lwip/ip4_addr.h>
+#include <nvs_flash.h>
+#include "nvsconfig.h"
+
+#define TAG_CONFIG "config"
+
+void initialize_nvs()
+{
+    esp_err_t rv;
+    nvs_handle_t hnvs;
+
+    char tmps[128];
+    size_t len;
+    uint8_t u8val;
+
+
+    esp_err_t err = nvs_flash_init();
+    if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
+        ESP_ERROR_CHECK( nvs_flash_erase() );
+        err = nvs_flash_init();
+    }
+    ESP_ERROR_CHECK(err);
+
+
+
+    /* Wifi namespace */
+
+    ESP_ERROR_CHECK( nvs_open("wifi", NVS_READWRITE, &hnvs) );
+
+    u8val = 99;
+    rv = nvs_get_u8(hnvs, "use_dhcpc", &u8val);
+    if (rv != ESP_OK || (u8val != 0 && u8val != 1)) {
+        ESP_LOGW(TAG_CONFIG, "Invalid dhcp option - set to default");
+        u8val = 1;
+        nvs_set_u8(hnvs, "use_dhcpc", u8val);
+    }
+    ESP_LOGI(TAG_CONFIG, "wifi.use_dhcpc = %d", u8val);
+
+    len = 16;
+    tmps[0] = 0;
+    rv = nvs_get_str(hnvs, "static_ip", tmps, &len);
+    if (rv != ESP_OK || IPADDR_NONE == ipaddr_addr(tmps)) {
+        strcpy(tmps, "192.168.56.2");
+        ESP_LOGW(TAG_CONFIG, "Invalid static ip - set to default");
+        ESP_ERROR_CHECK(nvs_set_str(hnvs, "static_ip", tmps));
+    }
+    ESP_LOGI(TAG_CONFIG, "wifi.static_ip = %s", tmps);
+
+    len = 16;
+    tmps[0] = 0;
+    rv = nvs_get_str(hnvs, "static_gw", tmps, &len);
+    if (rv != ESP_OK || IPADDR_NONE == ipaddr_addr(tmps)) {
+        strcpy(tmps, "192.168.0.1");
+        ESP_LOGW(TAG_CONFIG, "Invalid static gw - set to default");
+        ESP_ERROR_CHECK(nvs_set_str(hnvs, "static_gw", tmps));
+    }
+    ESP_LOGI(TAG_CONFIG, "wifi.static_gw = %s", tmps);
+
+    len = 16;
+    tmps[0] = 0;
+    rv = nvs_get_str(hnvs, "static_mask", tmps, &len);
+    if (rv != ESP_OK || IPADDR_NONE == ipaddr_addr(tmps)) {
+        strcpy(tmps, "255.255.255.0");
+        ESP_LOGW(TAG_CONFIG, "Invalid static mask - set to default");
+        ESP_ERROR_CHECK(nvs_set_str(hnvs, "static_mask", tmps));
+    }
+    ESP_LOGI(TAG_CONFIG, "wifi.static_mask = %s", tmps);
+
+    len = 16;
+    tmps[0] = 0;
+    rv = nvs_get_str(hnvs, "static_dns1", tmps, &len);
+    if (rv != ESP_OK || IPADDR_NONE == ipaddr_addr(tmps)) {
+        strcpy(tmps, "8.8.8.8");
+        ESP_LOGW(TAG_CONFIG, "Invalid static DNS1 - set to default");
+        ESP_ERROR_CHECK(nvs_set_str(hnvs, "static_dns1", tmps));
+    }
+    ESP_LOGI(TAG_CONFIG, "wifi.static_dns1 = %s", tmps);
+
+    len = 16;
+    tmps[0] = 0;
+    rv = nvs_get_str(hnvs, "static_dns2", tmps, &len);
+    if (rv != ESP_OK) {
+        strcpy(tmps, "");
+        ESP_LOGW(TAG_CONFIG, "Invalid static DNS2 - set to empty");
+        ESP_ERROR_CHECK(nvs_set_str(hnvs, "static_dns2", tmps));
+    }
+    ESP_LOGI(TAG_CONFIG, "wifi.static_dns2 = %s", tmps);
+
+    nvs_close(hnvs);
+
+
+    /* MQTT namespace */
+
+    ESP_ERROR_CHECK( nvs_open("mqtt", NVS_READWRITE, &hnvs) );
+
+    len = 128;
+    tmps[0] = 0;
+    rv = nvs_get_str(hnvs, "broker", tmps, &len);
+    if (rv != ESP_OK) {
+        strcpy(tmps, "");
+        ESP_LOGW(TAG_CONFIG, "Invalid broker addr - set to empty");
+        ESP_ERROR_CHECK(nvs_set_str(hnvs, "broker", tmps));
+    }
+    ESP_LOGI(TAG_CONFIG, "mqtt.broker = %s", tmps);
+
+    len = 128;
+    tmps[0] = 0;
+    rv = nvs_get_str(hnvs, "topic", tmps, &len);
+    if (rv != ESP_OK || tmps[0] == 0) {
+        strcpy(tmps, "/meteo/");
+        ESP_LOGW(TAG_CONFIG, "Invalid mqtt topic - set to default");
+        ESP_ERROR_CHECK(nvs_set_str(hnvs, "topic", tmps)); // default
+    }
+    ESP_LOGI(TAG_CONFIG, "mqtt.topic = %s", tmps);
+
+    nvs_close(hnvs);
+}
diff --git a/main/nvsconfig.h b/main/nvsconfig.h
new file mode 100644
index 0000000..8843f59
--- /dev/null
+++ b/main/nvsconfig.h
@@ -0,0 +1,7 @@
+/**
+ * TODO file description
+ */
+
+#pragma once
+
+void initialize_nvs();
diff --git a/sdkconfig b/sdkconfig
index cefc2cb..a77d5c8 100644
--- a/sdkconfig
+++ b/sdkconfig
@@ -76,8 +76,8 @@ CONFIG_PARTITION_TABLE_SINGLE_APP=y
 CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
 CONFIG_PARTITION_TABLE_OFFSET=0x8000
 CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
-CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y
-# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set
+# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set
+CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
 # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
 # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
@@ -463,8 +463,8 @@ CONFIG_MONITOR_BAUD_74880B=y
 # CONFIG_MONITOR_BAUD_OTHER is not set
 CONFIG_MONITOR_BAUD_OTHER_VAL=74880
 CONFIG_MONITOR_BAUD=74880
-CONFIG_OPTIMIZATION_LEVEL_DEBUG=y
-# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set
+# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set
+CONFIG_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
 # CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
 # CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
diff --git a/sdkconfig.old b/sdkconfig.old
index d948584..cefc2cb 100644
--- a/sdkconfig.old
+++ b/sdkconfig.old
@@ -82,11 +82,11 @@ CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
 # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
 # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
 # CONFIG_COMPILER_CXX_EXCEPTIONS is not set
-CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
-# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
+# CONFIG_COMPILER_STACK_CHECK_MODE_NONE is not set
+CONFIG_COMPILER_STACK_CHECK_MODE_NORM=y
 # CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set
 # CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set
-# CONFIG_COMPILER_STACK_CHECK is not set
+CONFIG_COMPILER_STACK_CHECK=y
 # CONFIG_COMPILER_WARN_WRITE_STRINGS is not set
 CONFIG_APP_UPDATE_CHECK_APP_SUM=y
 # CONFIG_APP_UPDATE_CHECK_APP_HASH is not set
@@ -401,7 +401,7 @@ CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y
 CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y
 # CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set
 # CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set
-CONFIG_NEWLIB_NANO_FORMAT=y
+# CONFIG_NEWLIB_NANO_FORMAT is not set
 # CONFIG_OPENSSL_DEBUG is not set
 CONFIG_OPENSSL_ASSERT_DO_NOTHING=y
 # CONFIG_OPENSSL_ASSERT_EXIT is not set
@@ -469,11 +469,11 @@ CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
 # CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
 # CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
 # CONFIG_CXX_EXCEPTIONS is not set
-CONFIG_STACK_CHECK_NONE=y
-# CONFIG_STACK_CHECK_NORM is not set
+# CONFIG_STACK_CHECK_NONE is not set
+CONFIG_STACK_CHECK_NORM=y
 # CONFIG_STACK_CHECK_STRONG is not set
 # CONFIG_STACK_CHECK_ALL is not set
-# CONFIG_STACK_CHECK is not set
+CONFIG_STACK_CHECK=y
 # CONFIG_WARN_WRITE_STRINGS is not set
 CONFIG_MAIN_TASK_STACK_SIZE=3584
 CONFIG_CONSOLE_UART_DEFAULT=y