Send your first reading
From a device token to a value on a dashboard in five minutes, with HTTP or MQTT.
This page takes you from a new device to a value on its dashboard. Pick HTTP or MQTT. Both accept the same reading.
What you need#
You need a device in Logable, with its id and its token. Create the device, then copy both from its Connection tab. See Connect a device.
Over HTTP#
Send one POST request with the reading in the body.
# Raw Meal Silo - Quick Test (curl)
# Device Type: silo | Device ID: <DEVICE_ID>
#
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
curl -X POST "https://ingest.logable.app/ingest/<DEVICE_ID>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <DEVICE_TOKEN>" \
-d '{"timestamp":"2026-07-01T10:00:00.000Z","status":"online","data":{"weight":18750.5,"status":"filling"}}'"""
Raw Meal Silo - Logable HTTP Integration (Python)
Device Type: silo
Device ID: <DEVICE_ID>
Installation: pip install requests
"""
import requests
from datetime import datetime, timezone
API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>"
API_TOKEN = "<DEVICE_TOKEN>"
def send_data():
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
payload = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "online",
"data": {
"weight": 18750.5,
"status": "filling"
}
}
response = requests.post(
API_ENDPOINT,
json=payload,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_TOKEN}"
},
timeout=10
)
response.raise_for_status()
print(f"Data sent successfully: {response.status_code}")
if __name__ == "__main__":
send_data()/**
* Raw Meal Silo - Logable HTTP Integration (TypeScript)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Runtime: Node.js 18+ or modern browser (uses built-in fetch)
*/
const API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>";
const API_TOKEN = "<DEVICE_TOKEN>";
async function sendData(): Promise<void> {
// The "data" fields shown are this device type's registered metrics.
// Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
// Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
// Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
// Custom values appear on dashboards via a widget's "Show Custom Data" option.
const payload = {
timestamp: new Date().toISOString(),
status: "online",
data: {
"weight": 18750.5,
"status": "filling"
},
};
const response = await fetch(API_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_TOKEN}`,
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
console.log("Data sent successfully:", response.status);
}
sendData();#!/bin/bash
# Raw Meal Silo - Logable HTTP Integration (Bash)
# Device Type: silo
# Device ID: <DEVICE_ID>
API_ENDPOINT="https://ingest.logable.app/ingest/<DEVICE_ID>"
API_TOKEN="<DEVICE_TOKEN>"
# NOTE: the heredoc below is pure JSON, so it cannot contain comments.
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
# Quoted heredoc delimiter: '$' in metric values is taken literally.
# "timestamp" is optional - the platform stamps arrival time when omitted.
read -r -d '' PAYLOAD <<'EOF'
{
"status": "online",
"data": {
"weight": 18750.5,
"status": "filling"
}
}
EOF
HTTP_CODE=$(curl -s -w "%{http_code}" -o /dev/null \
-X POST "${API_ENDPOINT}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_TOKEN}" \
-d "${PAYLOAD}")
if [ "${HTTP_CODE}" -ge 200 ] && [ "${HTTP_CODE}" -lt 300 ]; then
echo "Data sent successfully (HTTP ${HTTP_CODE})"
exit 0
else
echo "Error sending data (HTTP ${HTTP_CODE})"
exit 1
fi<#
.SYNOPSIS
Raw Meal Silo - Logable HTTP Integration (PowerShell)
Device Type: silo
Device ID: <DEVICE_ID>
#>
$ApiEndpoint = "https://ingest.logable.app/ingest/<DEVICE_ID>"
$ApiToken = "<DEVICE_TOKEN>"
function Send-DeviceData {
$Timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
$Payload = @{
timestamp = $Timestamp
status = "online"
data = @{
weight = 18750.5
status = 'filling'
}
} | ConvertTo-Json -Depth 3
$Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $ApiToken"
}
try {
$Response = Invoke-RestMethod `
-Uri $ApiEndpoint `
-Method Post `
-Headers $Headers `
-Body $Payload `
-TimeoutSec 10
Write-Host "Data sent successfully" -ForegroundColor Green
return $Response
} catch {
Write-Error "Error: $($_.Exception.Message)"
throw
}
}
Send-DeviceData/*
* Raw Meal Silo - Logable HTTP Integration (C / libcurl)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Install: sudo apt-get install libcurl4-openssl-dev
* Compile: gcc -o device_client device_client.c -lcurl
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>
#define API_ENDPOINT "https://ingest.logable.app/ingest/<DEVICE_ID>"
#define API_TOKEN "<DEVICE_TOKEN>"
/* Example metric payload - CUSTOMIZE DATA HERE */
static const char *DATA_JSON =
"{\"weight\":18750.5,\"status\":\"filling\"}";
int main(void) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
time_t now = time(NULL);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
/* Build JSON payload - CUSTOMIZE DATA HERE */
/*
* The "data" fields shown are this device type's registered metrics.
* Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
* Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
* Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
* Custom values appear on dashboards via a widget's "Show Custom Data" option.
*/
/* Metric data is passed as a %s argument (never spliced into the
* format string), so '%' characters in values stay intact. */
char payload[1024];
snprintf(payload, sizeof(payload),
"{\"timestamp\": \"%s\", \"status\": \"online\", \"data\": %s}",
timestamp, DATA_JSON);
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, API_ENDPOINT);
headers = curl_slist_append(headers, "Content-Type: application/json");
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", API_TOKEN);
headers = curl_slist_append(headers, auth_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "Request failed: %s\n", curl_easy_strerror(res));
} else {
printf("Data sent successfully\n");
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}/*
* Raw Meal Silo - Logable HTTP Integration (Arduino / ESP32)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Libraries: WiFi, HTTPClient, ArduinoJson
* Board: ESP32 or ESP8266
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>";
const char* API_TOKEN = "<DEVICE_TOKEN>";
void setup() {
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" Connected!");
}
void sendMetrics() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected");
return;
}
HTTPClient http;
http.begin(API_ENDPOINT);
http.addHeader("Content-Type", "application/json");
char authHeader[256];
snprintf(authHeader, sizeof(authHeader), "Bearer %s", API_TOKEN);
http.addHeader("Authorization", authHeader);
/* Build JSON payload - CUSTOMIZE DATA HERE */
JsonDocument doc;
doc["status"] = "online";
doc.createNestedObject("data");
doc["data"]["weight"] = 18750.5;
doc["data"]["status"] = "filling";
// The "data" fields shown are this device type's registered metrics.
// Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
// Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
// Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
// Custom values appear on dashboards via a widget's "Show Custom Data" option.
String payload;
serializeJson(doc, payload);
int httpCode = http.POST(payload);
if (httpCode > 0) {
Serial.printf("Data sent (HTTP %d)\n", httpCode);
} else {
Serial.printf("HTTP error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
void loop() {
sendMetrics();
delay(10000); /* Send every 10 seconds */
}A successful call returns this body:
{
"acknowledged": true,
"timestamp": "2026-07-01T10:00:00.123Z",
"deviceId": "<DEVICE_ID>"
}Open the device's Overview tab. The value appears without a refresh.
Over MQTT#
Connect to the broker and publish on the device's own topic.
| Setting | Value | Required | Description |
|---|---|---|---|
| Host | mqtt.logable.app | Yes | The public broker, over TLS on port 8883. |
| WebSocket | wss://mqtt.logable.app/mqtt | No | Use this where port 8883 is blocked, or from a browser tool. |
| Username | <DEVICE_ID> | Yes | The device's id. |
| Password | <DEVICE_TOKEN> | Yes | The device's token. |
| Topic | devices/<DEVICE_ID>/data | Yes | Publish at QoS 1. |
"""
Raw Meal Silo - Logable MQTT Integration (Python)
Device Type: silo
Device ID: <DEVICE_ID>
Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
Client ID must be unique per connection - a duplicate ID disconnects the other client.
Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
Installation: pip install paho-mqtt
"""
import paho.mqtt.client as mqtt
import json
from datetime import datetime, timezone
import time
MQTT_HOST = "mqtt.logable.app"
MQTT_PORT = 8883
MQTT_USERNAME = "<DEVICE_ID>"
MQTT_PASSWORD = "<DEVICE_TOKEN>"
MQTT_CLIENT_ID = "logable-<DEVICE_ID>"
MQTT_TOPIC = "devices/<DEVICE_ID>/data"
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connection failed: {reason_code}")
else:
print(f"Connected to MQTT broker at {MQTT_HOST}:{MQTT_PORT}")
def send_data(client):
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
payload = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "online",
"data": {
"weight": 18750.5,
"status": "filling"
}
}
result = client.publish(MQTT_TOPIC, json.dumps(payload), qos=1)
if result.rc == mqtt.MQTT_ERR_SUCCESS:
print(f"Data sent to {MQTT_TOPIC}")
def main():
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=MQTT_CLIENT_ID)
client.on_connect = on_connect
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
# TLS with the default system CA store - validates the broker's
# Let's Encrypt certificate out of the box.
client.tls_set()
try:
client.connect(MQTT_HOST, MQTT_PORT, keepalive=60)
client.loop_start()
time.sleep(1)
send_data(client)
time.sleep(1)
client.loop_stop()
client.disconnect()
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()/**
* Raw Meal Silo - Logable MQTT Integration (TypeScript)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
* Client ID must be unique per connection - a duplicate ID disconnects the other client.
* Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
*
* Installation: npm install mqtt
*/
import mqtt from 'mqtt';
// mqtts:// - the mqtt package handles TLS and CA validation automatically.
const MQTT_URL = "mqtts://mqtt.logable.app:8883";
const MQTT_USERNAME = "<DEVICE_ID>";
const MQTT_PASSWORD = "<DEVICE_TOKEN>";
const MQTT_CLIENT_ID = "logable-<DEVICE_ID>";
const MQTT_TOPIC = "devices/<DEVICE_ID>/data";
async function sendData(): Promise<void> {
const client = mqtt.connect(MQTT_URL, {
clientId: MQTT_CLIENT_ID,
username: MQTT_USERNAME,
password: MQTT_PASSWORD,
clean: true,
});
client.on('connect', () => {
console.log(`Connected to MQTT broker at ${MQTT_URL}`);
// The "data" fields shown are this device type's registered metrics.
// Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
// Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
// Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
// Custom values appear on dashboards via a widget's "Show Custom Data" option.
const payload = {
timestamp: new Date().toISOString(),
status: "online",
data: {
"weight": 18750.5,
"status": "filling"
},
};
client.publish(
MQTT_TOPIC,
JSON.stringify(payload),
{ qos: 1 },
(error) => {
if (error) console.error('Failed to publish:', error);
else console.log(`Data sent to ${MQTT_TOPIC}`);
client.end();
}
);
});
client.on('error', (error) => {
console.error('MQTT error:', error);
client.end();
});
}
sendData();#!/bin/bash
# Raw Meal Silo - Logable MQTT Integration (Bash)
# Device Type: silo
# Device ID: <DEVICE_ID>
#
# Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
# Client ID must be unique per connection - a duplicate ID disconnects the other client.
# Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
#
# Requirements: mosquitto-clients
MQTT_HOST="mqtt.logable.app"
MQTT_PORT=8883
MQTT_USERNAME="<DEVICE_ID>"
MQTT_PASSWORD="<DEVICE_TOKEN>"
MQTT_CLIENT_ID="logable-<DEVICE_ID>"
MQTT_TOPIC="devices/<DEVICE_ID>/data"
# NOTE: the heredoc below is pure JSON, so it cannot contain comments.
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
# Quoted heredoc delimiter: '$' in metric values is taken literally.
# "timestamp" is optional - the platform stamps arrival time when omitted.
read -r -d '' PAYLOAD <<'EOF'
{
"status": "online",
"data": {
"weight": 18750.5,
"status": "filling"
}
}
EOF
# TLS: --capath works on Linux; on macOS use --cafile /etc/ssl/cert.pem instead.
mosquitto_pub \
-h "${MQTT_HOST}" \
-p "${MQTT_PORT}" \
--capath /etc/ssl/certs \
-i "${MQTT_CLIENT_ID}" \
-u "${MQTT_USERNAME}" \
-P "${MQTT_PASSWORD}" \
-t "${MQTT_TOPIC}" \
-m "${PAYLOAD}" \
-q 1
if [ $? -eq 0 ]; then
echo "Data published to ${MQTT_TOPIC}"
exit 0
else
echo "Failed to publish"
exit 1
fi<#
.SYNOPSIS
Raw Meal Silo - Logable MQTT Integration (PowerShell)
Device Type: silo
Device ID: <DEVICE_ID>
Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
Client ID must be unique per connection - a duplicate ID disconnects the other client.
Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
Requirements: the M2Mqtt .NET library (M2Mqtt.Net.dll).
Install via NuGet: Install-Package M2Mqtt
(or download the package and extract lib/net45/M2Mqtt.Net.dll,
then adjust the Add-Type path below)
#>
$MqttHost = "mqtt.logable.app"
$MqttPort = 8883
$MqttUsername = "<DEVICE_ID>"
$MqttPassword = "<DEVICE_TOKEN>"
$MqttClientId = "logable-<DEVICE_ID>"
$MqttTopic = "devices/<DEVICE_ID>/data"
Add-Type -Path "M2Mqtt.Net.dll"
function Send-DeviceDataMqtt {
$Timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
$Payload = @{
timestamp = $Timestamp
status = "online"
data = @{
weight = 18750.5
status = 'filling'
}
} | ConvertTo-Json -Depth 3
# TLS on the standard MQTT-over-TLS port; .NET validates the
# broker's Let's Encrypt certificate against the system store.
$Client = [uPLibrary.Networking.M2Mqtt.MqttClient]::new(
$MqttHost, $MqttPort, $true,
[uPLibrary.Networking.M2Mqtt.MqttSslProtocols]::TLSv1_2,
$null, $null
)
try {
$ReturnCode = $Client.Connect($MqttClientId, $MqttUsername, $MqttPassword)
if ($ReturnCode -ne 0) {
throw "MQTT connect failed (code $ReturnCode)"
}
$Client.Publish(
$MqttTopic,
[System.Text.Encoding]::UTF8.GetBytes($Payload),
[uPLibrary.Networking.M2Mqtt.Messages.MqttMsgBase]::QOS_LEVEL_AT_LEAST_ONCE,
$false
) | Out-Null
Write-Host "Data published to $MqttTopic" -ForegroundColor Green
} catch {
Write-Error "Error: $($_.Exception.Message)"
throw
} finally {
if ($Client.IsConnected) {
$Client.Disconnect()
}
}
}
Send-DeviceDataMqtt/*
* Raw Meal Silo - Logable MQTT Integration (C / Eclipse Paho)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
* Client ID must be unique per connection - a duplicate ID disconnects the other client.
* Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
*
* Install: sudo apt-get install libpaho-mqtt-dev
* Compile: gcc -o mqtt_client mqtt_client.c -lpaho-mqtt3cs
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <MQTTClient.h>
#define MQTT_ADDRESS "ssl://mqtt.logable.app:8883"
#define MQTT_CLIENT_ID "logable-<DEVICE_ID>"
#define MQTT_USERNAME "<DEVICE_ID>"
#define MQTT_PASSWORD "<DEVICE_TOKEN>"
#define MQTT_TOPIC "devices/<DEVICE_ID>/data"
#define MQTT_QOS 1
#define MQTT_TIMEOUT_MS 10000L
/* Example metric payload - CUSTOMIZE DATA HERE */
static const char *DATA_JSON =
"{\"weight\":18750.5,\"status\":\"filling\"}";
int main(void) {
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer;
MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
char payload[1024];
char timestamp[64];
time_t now = time(NULL);
int rc;
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
/* Build JSON payload - CUSTOMIZE DATA HERE */
/*
* The "data" fields shown are this device type's registered metrics.
* Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
* Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
* Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
* Custom values appear on dashboards via a widget's "Show Custom Data" option.
*/
/* Metric data is passed as a %s argument (never spliced into the
* format string), so '%' characters in values stay intact. */
snprintf(payload, sizeof(payload),
"{\"timestamp\": \"%s\", \"status\": \"online\", \"data\": %s}",
timestamp, DATA_JSON);
rc = MQTTClient_create(&client, MQTT_ADDRESS, MQTT_CLIENT_ID,
MQTTCLIENT_PERSISTENCE_NONE, NULL);
if (rc != MQTTCLIENT_SUCCESS) {
fprintf(stderr, "Failed to create client (rc=%d)\n", rc);
return 1;
}
conn_opts.keepAliveInterval = 60;
conn_opts.cleansession = 1;
conn_opts.username = MQTT_USERNAME;
conn_opts.password = MQTT_PASSWORD;
/* TLS: validate the broker's Let's Encrypt certificate against the
* system CA store (works out of the box on Linux/macOS). */
ssl_opts.enableServerCertAuth = 1;
conn_opts.ssl = &ssl_opts;
rc = MQTTClient_connect(client, &conn_opts);
if (rc != MQTTCLIENT_SUCCESS) {
fprintf(stderr, "Failed to connect (rc=%d)\n", rc);
MQTTClient_destroy(&client);
return 1;
}
printf("Connected to MQTT broker\n");
pubmsg.payload = payload;
pubmsg.payloadlen = (int)strlen(payload);
pubmsg.qos = MQTT_QOS;
pubmsg.retained = 0;
rc = MQTTClient_publishMessage(client, MQTT_TOPIC, &pubmsg, &token);
if (rc == MQTTCLIENT_SUCCESS) {
rc = MQTTClient_waitForCompletion(client, token, MQTT_TIMEOUT_MS);
printf("Data published to %s\n", MQTT_TOPIC);
} else {
fprintf(stderr, "Failed to publish (rc=%d)\n", rc);
}
MQTTClient_disconnect(client, 1000);
MQTTClient_destroy(&client);
return 0;
}/*
* Raw Meal Silo - Logable MQTT Integration (Arduino / ESP32)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Broker: MQTT over TLS on port 8883 (Let's Encrypt certificate; standard system CAs validate it). WebSocket: wss://mqtt.logable.app/mqtt
* Client ID must be unique per connection - a duplicate ID disconnects the other client.
* Access: this device may publish/subscribe only under devices/<DEVICE_ID>/* - everything else is denied.
*
* Libraries: WiFi, WiFiClientSecure, PubSubClient, ArduinoJson
* Board: ESP32 or ESP8266
*/
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "mqtt.logable.app";
const int MQTT_PORT = 8883;
const char* MQTT_USERNAME = "<DEVICE_ID>";
const char* MQTT_PASSWORD = "<DEVICE_TOKEN>";
const char* MQTT_CLIENT_ID = "logable-<DEVICE_ID>";
const char* MQTT_TOPIC = "devices/<DEVICE_ID>/data";
WiFiClientSecure espClient;
PubSubClient mqttClient(espClient);
void setupWifi() {
Serial.print("Connecting to WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" Connected!");
}
void reconnectMqtt() {
while (!mqttClient.connected()) {
Serial.print("Connecting to MQTT...");
if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
Serial.println(" Connected!");
} else {
Serial.printf(" Failed (rc=%d). Retrying in 5s...\n", mqttClient.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
setupWifi();
/* TLS: setInsecure() skips certificate validation - acceptable for a
* quick test, but production firmware should pin the ISRG Root X1 CA
* (Let's Encrypt root) instead:
*
* static const char ISRG_ROOT_X1[] PROGMEM = R"CERT(
* -----BEGIN CERTIFICATE-----
* ...paste the ISRG Root X1 PEM from https://letsencrypt.org/certificates/
* -----END CERTIFICATE-----
* )CERT";
* espClient.setCACert(ISRG_ROOT_X1);
*/
espClient.setInsecure();
mqttClient.setServer(MQTT_HOST, MQTT_PORT);
/* Must be set BEFORE connecting - PubSubClient's 256-byte default
* truncates typical Logable payloads. Keep payloads under 512 bytes. */
mqttClient.setBufferSize(512);
}
void sendMetrics() {
if (!mqttClient.connected()) {
reconnectMqtt();
}
mqttClient.loop();
/* Build JSON payload - CUSTOMIZE DATA HERE (keep under 512 bytes) */
JsonDocument doc;
doc["status"] = "online";
doc.createNestedObject("data");
doc["data"]["weight"] = 18750.5;
doc["data"]["status"] = "filling";
// The "data" fields shown are this device type's registered metrics.
// Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
// Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
// Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
// Custom values appear on dashboards via a widget's "Show Custom Data" option.
char payload[512];
serializeJson(doc, payload, sizeof(payload));
if (mqttClient.publish(MQTT_TOPIC, payload)) {
Serial.println("Data published via MQTT");
} else {
Serial.println("MQTT publish failed");
}
}
void loop() {
sendMetrics();
delay(10000); /* Send every 10 seconds */
}MQTT gives no response body. Open the device's Overview tab to confirm the value arrived.
If nothing appears#
A 401 means the token is wrong or revoked. A 400 means something in the body failed validation. See Error codes for the full list.
Over MQTT, a rejected message is dropped with no reply to the device. Open the device's Connection tab and check the Device log instead. It shows the rejection with the reason.