sachin42/blynk

1.3.0

Latest
uploaded 3 days ago
Blynk IOT for ESP-IDF (tested with only legecy server)

readme

# Blynk Library for ESP-IDF

A port of the Arduino Blynk legacy library to ESP-IDF (FreeRTOS) with full thread-safe API, non-blocking TCP transport, and built-in task management.

## Features

- **Thread-Safe API**: All `Blynk.*` calls are internally protected by recursive mutex — no user-side locking required
- **Dedicated Task Management**: Single `BlynkTaskStart()` call spawns a managed FreeRTOS task (core 0, priority 5)
- **Non-Blocking Transport**: Asynchronous TCP connect, read, and write using `select()` and non-blocking sockets
- **BlynkTimer Integration**: 16-slot timer driven inside the Blynk task (avoids mutex contention)
- **FreeRTOS Native**: Pure lwip/POSIX sockets, no Arduino layer — works on bare ESP32/IDF
- **Event Handlers**: `BLYNK_WRITE`, `BLYNK_CONNECTED`, `BLYNK_DISCONNECTED` macros for responsive UI control
- **Virtual Pins**: Send/receive data to/from Blynk app via `Blynk.virtualWrite()` and `BLYNK_WRITE(Vx)` handlers

## Installation

### Add to idf_component.yml

```yaml
dependencies:
  sachin42/blynk: "1.2.0"
```

### Update CMakeLists.txt

```cmake
idf_component_register(
    REQUIRES esp_http_client sachin42__blynk sachin42__print
    SRCS main.cpp
    INCLUDE_DIRS .
)
```

### Verify Component Installation

```bash
idf.py recycle
```

## Quick Start

```cpp
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "Ethernet.h"  // or WiFi.h if using WiFi
#include "BlynkEspIDF.h"

BlynkTimer timer;

// Handler for button presses on V1
BLYNK_WRITE(V1) {
    int value = param.asInt();
    ESP_LOGI("APP", "Button V1: %d", value);
}

// Timer callback — runs inside Blynk task
void send_heartbeat() {
    Blynk.virtualWrite(V2, "online");  // Thread-safe, no lock needed
}

extern "C" void app_main(void) {
    // Initialize Ethernet (or WiFi)
    Eth.begin();
    
    // Register timer callback (before BlynkTaskStart to avoid race)
    timer.setInterval(5000, send_heartbeat);
    
    // Start Blynk task: auth token, server, port, timer, stack, priority, core
    BlynkTaskStart("YourAuthToken", "blynk.cloud.blynk.io", 8080, &timer, 8192, 5, 0);
    
    // app_main completes; Blynk task runs independently
    vTaskDelete(NULL);
}
```

## API Reference

### BlynkTaskStart()

Spawns a dedicated FreeRTOS task that runs `Blynk.run()` and optionally `timer.run()`.

```cpp
TaskHandle_t BlynkTaskStart(
    const char *auth,           // Authentication token from Blynk app
    const char *domain,         // Server hostname (e.g., "blynk.cloud.blynk.io")
    uint16_t port,              // Server port (typically 8080 or 443)
    BlynkTimer *timer = nullptr,  // Optional BlynkTimer instance for callbacks
    uint32_t stack = 8192,      // FreeRTOS task stack size in bytes (default 8 KB)
    UBaseType_t prio = 5,       // FreeRTOS task priority (default 5)
    BaseType_t core = 0         // CPU core to pin task (0=PRO_CPU, 1=APP_CPU)
);

// Returns: TaskHandle to the created Blynk task, or nullptr on failure
```

**Defaults**: Stack 8 KB, priority 5, core 0. Recommended for typical IoT applications.

**Important**: Do NOT call `Blynk.run()` in your `app_main()` loop — the task handles it.

### Virtual Pin Handlers

#### BLYNK_WRITE(Vx)

Triggered when the Blynk app sends a value to virtual pin `x`. Handler runs inside the Blynk task.

```cpp
BLYNK_WRITE(V1) {
    int value = param.asInt();
    // Process value
    Blynk.virtualWrite(V2, value * 2);  // Safe to call Blynk API
}

BLYNK_WRITE(V3) {
    const char *msg = param.asString();
    // Handle string data
}
```

**Available param methods**:
- `param.asInt()` — Integer value
- `param.asDouble()` — Floating-point value
- `param.asString()` — String data (buffer)
- `param.getBuffer()` — Raw byte buffer

#### BLYNK_CONNECTED()

Triggered every time the device connects or reconnects to the Blynk server.

```cpp
static bool timers_initialized = false;

BLYNK_CONNECTED() {
    // Guard against registering duplicate timers on each reconnect
    if (!timers_initialized) {
        timer.setInterval(10000, update_sensor_data);
        timers_initialized = true;
    }
    
    // Refresh UI state
    Blynk.syncVirtual(V1, V2, V3);
    Blynk.setProperty(V1, "label", "Status: Online");
}
```

**Important**: This handler fires on every reconnect. Use a static guard variable to prevent timer slot leaks.

#### BLYNK_DISCONNECTED()

Triggered when the connection to Blynk server is lost.

```cpp
BLYNK_DISCONNECTED() {
    ESP_LOGW("BLYNK", "Disconnected from server");
    // Optional: perform cleanup or notify other tasks
}
```

### Core Blynk API Methods

All methods are thread-safe and can be called from any FreeRTOS task.

```cpp
// Virtual pin communication
Blynk.virtualWrite(int pin, const char *fmt, ...);
Blynk.virtualWrite(int pin, double value);
Blynk.virtualWrite(int pin, String str);

// Widget control (property updates)
Blynk.setProperty(int pin, const char *property, const char *value);
Blynk.setProperty(int pin, const char *property, double value);

// Sync virtual pins from server
Blynk.syncVirtual(int pin1, int pin2, ...);

// Connection status
bool Blynk.connected();

// Low-level message sending
Blynk.sendInternal(const char *cmd, ...);
```

## BlynkTimer Usage

The `BlynkTimer` class provides 16 timer slots for non-blocking interval/one-shot callbacks.

### setInterval()

Register a callback to run every `interval` milliseconds.

```cpp
BlynkTimer timer;

void my_callback() {
    Blynk.virtualWrite(V1, "tick");
}

void setup() {
    // Register BEFORE BlynkTaskStart to avoid race condition
    int timerID = timer.setInterval(1000, my_callback);
}
```

**Returns**: Timer ID (0–15) on success, `-1` if no free slots (max 16 timers).

**Important**: Register all timers before calling `BlynkTaskStart()` or use a static guard in `BLYNK_CONNECTED()`.

### setTimeout()

Register a callback to run once after `delay` milliseconds.

```cpp
timer.setTimeout(2000, []() {
    Blynk.virtualWrite(V2, "delayed");
});
```

### deleteTimer() / disable() / enable()

```cpp
int id = timer.setInterval(1000, callback);
timer.disable(id);  // Pause callback (still holds a slot)
timer.enable(id);   // Resume callback
timer.deleteTimer(id);  // Release slot
```

### Timer Slot Management

Max 16 slots. Avoid leaks by:

1. **Static guard in BLYNK_CONNECTED()**:
   ```cpp
   static bool initialized = false;
   BLYNK_CONNECTED() {
       if (!initialized) {
           timer.setInterval(1000, cb);
           initialized = true;
       }
   }
   ```

2. **Register all timers before BlynkTaskStart()**:
   ```cpp
   timer.setInterval(1000, cb1);
   timer.setInterval(2000, cb2);
   BlynkTaskStart(..., &timer, ...);
   ```

3. **Explicitly deleteTimer() on reconnect** (if needed):
   ```cpp
   // Store IDs in static variables and delete them before re-registering
   ```

## Thread Safety

### Thread-Safe Operations

All `Blynk.*` API calls are internally protected by a recursive mutex (`BlynkApiLockGuard`). You can call them from any FreeRTOS task without additional locking:

```cpp
// Any task can safely call these:
Blynk.virtualWrite(V1, 42);
Blynk.setProperty(V2, "label", "Value");
Blynk.connected();
Blynk.syncVirtual(V1, V2);
```

### NOT Safe from ISR

**Do NOT call Blynk API from interrupt handlers (ISR)**. Use FreeRTOS queues to defer work:

```cpp
// ISR context: send to queue
void gpio_isr_handler(void *arg) {
    xQueueSendFromISR(queue, &event, NULL);
}

// Task context: consume from queue and call Blynk API
static void event_handler_task(void *arg) {
    for (;;) {
        if (xQueueReceive(queue, &event, portMAX_DELAY)) {
            Blynk.virtualWrite(V1, event.value);  // Safe in task
        }
    }
}
```

### Handler Execution Context

`BLYNK_WRITE()`, `BLYNK_CONNECTED()`, and `BLYNK_DISCONNECTED()` handlers run **inside the Blynk task** (core 0, priority 5). You can safely call any Blynk API from these handlers.

```cpp
BLYNK_WRITE(V1) {
    int val = param.asInt();
    // This callback runs in Blynk task — all Blynk API calls are safe
    Blynk.virtualWrite(V2, val);
    Blynk.setProperty(V3, "label", "Updated");
}
```

### BlynkLockGuard & BLYNK_LOCK/UNLOCK

These are **no-ops** for backward compatibility:

```cpp
// These do nothing (not needed anymore)
BlynkLockGuard guard;
BLYNK_LOCK();
Blynk.virtualWrite(V1, 42);
BLYNK_UNLOCK();

// Just call the API directly:
Blynk.virtualWrite(V1, 42);
```

## Parallel Tasks

The Blynk task runs on core 0 (PRO_CPU) with priority 5. You can spawn additional tasks on core 1 (APP_CPU) for CPU-intensive work without blocking Blynk network operations.

### Example: HTTP Task on Core 1

```cpp
#include "esp_http_client.h"

static void http_task(void *arg) {
    vTaskDelay(pdMS_TO_TICKS(3000));  // Wait for Blynk to connect
    
    esp_http_client_config_t config = {};
    config.url = "http://api.example.com/data";
    config.timeout_ms = 8000;
    
    for (;;) {
        esp_http_client_handle_t client = esp_http_client_init(&config);
        
        if (esp_http_client_perform(client) == ESP_OK) {
            int status = esp_http_client_get_status_code(client);
            // Call Blynk API from this task — thread-safe!
            Blynk.virtualWrite(V7, status);
        }
        
        esp_http_client_cleanup(client);
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

extern "C" void app_main(void) {
    Eth.begin();
    timer.setInterval(1000, cb);
    BlynkTaskStart("YourAuthToken", "blynk.server.com", 8080, &timer);
    
    // Spawn HTTP task on core 1 (non-network work)
    xTaskCreatePinnedToCore(http_task, "http", 8192, NULL, 4, NULL, 1);
    
    vTaskDelete(NULL);
}
```

### Core Allocation Recommendation

| Task | Core | Priority | Notes |
|------|------|----------|-------|
| Blynk (network) | 0 | 5 | Managed by `BlynkTaskStart()` |
| HTTP / external API | 1 | 4 | CPU-intensive, parallel to Blynk |
| Sensor reading | 1 | 4 | I/O-bound work on APP_CPU |
| Real-time ISR | 0 or 1 | 15+ | Keep ISR duration < 50 µs |

## Configuration

### Compile-Time Defines

Define these in your `CMakeLists.txt` or `sdkconfig` before including BlynkEspIDF headers:

```cpp
// Connection timeout for socket connect (default: 8000 ms)
#define BLYNK_CONNECT_TIMEOUT_MS 10000

// Poll interval for incoming data (default: 50 ms)
#define BLYNK_READ_POLL_MS 50

// Poll interval for sending data (default: 2000 ms)
#define BLYNK_SEND_POLL_MS 2000

// Heartbeat interval (default: 50 s)
#define BLYNK_HEARTBEAT 50

// Max message limit per period (default: 20)
#define BLYNK_MSG_LIMIT 20

// Max incoming payload size (default: 2048 bytes)
#define BLYNK_MAX_READBYTES 2048

// Enable debug logging
#define BLYNK_DEBUG
#define BLYNK_PRINT stdout  // stdout or your_log_function
```

### Set in CMakeLists.txt

```cmake
idf_component_register(
    REQUIRES sachin42__blynk
    SRCS main.cpp
)

# Add compile flags
target_compile_options(${COMPONENT_NAME} PRIVATE -DBLYNK_DEBUG -DBLYNK_CONNECT_TIMEOUT_MS=10000)
```

## Troubleshooting

### Connection Hangs or Timeout

**Symptom**: `BlynkTaskStart()` never completes or times out after 8 seconds.

**Causes**:
- DNS resolution failure (server unreachable)
- Network not initialized (call `Eth.begin()` before `BlynkTaskStart()`)
- Firewall blocking port 8080 or 443

**Solution**:
- Enable `BLYNK_DEBUG` and check log output
- Increase `BLYNK_CONNECT_TIMEOUT_MS` if network is slow
- Verify server hostname and port are correct
- Test DNS with `getaddrinfo()` in a separate task

### Timer Slot Leaks

**Symptom**: Timers stop firing after several reconnects.

**Cause**: `BLYNK_CONNECTED()` registers duplicate timers each reconnect (max 16 slots).

**Solution**: Use static guard:
```cpp
BLYNK_CONNECTED() {
    static bool initialized = false;
    if (!initialized) {
        timer.setInterval(1000, callback);
        initialized = true;
    }
}
```

### "Invalid pin" or Handler Not Firing

**Symptom**: `BLYNK_WRITE(Vx)` handler never called when app sends value.

**Causes**:
- Macro not defined (check header includes)
- Virtual pin number doesn't match (e.g., `BLYNK_WRITE(V1)` but app uses V2)
- Connection not established

**Solution**:
```cpp
#include "BlynkEspIDF.h"  // Required!

BLYNK_WRITE(V1) { ... }  // Must match app widget pin
```

### Mutex Deadlock from ISR

**Symptom**: Task hangs or watchdog reset after ISR calls Blynk API.

**Cause**: ISR called `Blynk.virtualWrite()` while Blynk task holds recursive mutex.

**Solution**: Use FreeRTOS queue to defer work to a regular task:
```cpp
void gpio_isr_handler(void *arg) {
    xQueueSendFromISR(event_queue, &event, NULL);
}

static void event_task(void *arg) {
    for (;;) {
        if (xQueueReceive(event_queue, &event, portMAX_DELAY)) {
            Blynk.virtualWrite(V1, event.value);  // Safe
        }
    }
}
```

### Memory Issues / Stack Overflow

**Symptom**: Blynk task crashes or restarts on first message.

**Cause**: Stack too small (8 KB may not be enough if using large buffers or deep call chains).

**Solution**: Increase stack size:
```cpp
BlynkTaskStart(token, server, port, &timer, 12288, 5, 0);  // 12 KB instead of 8 KB
```

Typical usage: 8–12 KB. Monitor free stack with `uxTaskGetStackHighWaterMark(task_handle)`.

## Examples

### Send Sensor Data Every 10 Seconds

```cpp
float read_temperature() {
    // Read from sensor...
    return 25.5;
}

void send_temp() {
    Blynk.virtualWrite(V1, read_temperature());
}

extern "C" void app_main(void) {
    Eth.begin();
    timer.setInterval(10000, send_temp);
    BlynkTaskStart("YourToken", "blynk.cloud.blynk.io", 8080, &timer);
    vTaskDelete(NULL);
}
```

### Handle Button with Feedback

```cpp
BLYNK_WRITE(V2) {
    if (param.asInt() == 1) {
        // Button pressed
        Blynk.virtualWrite(V3, "Button pressed!");
    }
}
```

### Sync Device State on Reconnect

```cpp
BLYNK_CONNECTED() {
    Blynk.syncVirtual(V1, V2, V3);  // Fetch latest values from app
}

BLYNK_READ(V4) {
    Blynk.virtualWrite(V4, get_current_temperature());
}
```

## License

MIT — See LICENSE file in this repository.

## Support

- Report issues on [GitHub](https://github.com/sachin42/idfcomponents)
- Blynk legacy server documentation: [Blynk Protocol](https://docs.blynk.io/)

Links

Supports all targets

To add this component to your project, run:

idf.py add-dependency "sachin42/blynk^1.3.0"

download archive

Stats

  • Archive size
    Archive size ~ 57.69 KB
  • Downloaded in total
    Downloaded in total 26 times
  • Weekly Downloads Weekly Downloads (All Versions)
  • Downloaded this version
    This version: 1 time

Badge

sachin42/blynk version: 1.3.0
|