Image uploading to Ubidots using MQTT-HTTP

Hi,
Following this link ([SOLVED] Uploading an image to Ubidots?) I’ve been trying to upload a picture via HTTP, encoding it in base64 as context, but I’m receiving the error that the payload is too large. I’m sending a 112Kb image. Is there any configuration required in Ubidots to update?

See attached flow used:
[
{
“id”: “inject-node”,
“type”: “inject”,
“z”: “flow-id”,
“name”: “Trigger”,
“props”: {
“payload”: true,
“topic”: “”
},
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“x”: 100,
“y”: 100,
“wires”: [[“file-in-node”]]
},
{
“id”: “file-in-node”,
“type”: “file in”,
“z”: “flow-id”,
“name”: “Read Image”,
“filename”: “/path/to/your/image.jpg”,
“format”: “base64”,
“sendError”: true,
“x”: 250,
“y”: 100,
“wires”: [[“function-node”]]
},
{
“id”: “function-node”,
“type”: “function”,
“z”: “flow-id”,
“name”: “Prepare Payload”,
“func”: “msg.headers = {\n ‘Content-Type’: ‘application/json’,\n ‘X-Auth-Token’: ‘YOUR_UBIDOTS_TOKEN’\n};\n\nmsg.payload = {\n "image_variable": {\n "value": 1,\n "context": { "image": msg.payload }\n }\n};\n\nreturn msg;”,
“outputs”: 1,
“noerr”: 0,
“initialize”: “”,
“finalize”: “”,
“x”: 450,
“y”: 100,
“wires”: [[“http-request-node”]]
},
{
“id”: “http-request-node”,
“type”: “http request”,
“z”: “flow-id”,
“name”: “Send to Ubidots”,
“method”: “POST”,
“ret”: “txt”,
“paytoqs”: “ignore”,
“url”: “https://industrial.api.ubidots.com/api/v1.6/devices/your-device-name/”,
“tls”: “”,
“persist”: false,
“proxy”: “”,
“authType”: “”,
“x”: 650,
“y”: 100,
“wires”: [[“debug-node”]]
},
{
“id”: “debug-node”,
“type”: “debug”,
“z”: “flow-id”,
“name”: “Debug”,
“active”: true,
“tosidebar”: true,
“console”: false,
“tostatus”: false,
“complete”: “true”,
“x”: 850,
“y”: 100,
“wires”:
}
]

Thanks for your help.

Hello @gmiro

Could you please send me the image in base64? I want to validate something.

Alejandro

Base64.txt (149.3 KB)

This is the result of the base64 node, then some headers are appended and it’s sent as msg.context.

This is the final message to ubidots:
{
_msgid: ‘a640cd529c818587’,
payload: {
image_variable: {
value: 1,
context: {
image: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 01 2c 01 2c 00 00 ff db 00 43 00 0d 09 0a 0b 0a 08 0d 0b 0a 0b 0e 0e 0d 0f 13 20 15 13 12 12 13 27 1c 1e 17 … 114600 more bytes>
}
}
},
topic: ‘’,
filename: ‘C:\192.168.7.3_2019102909-55_Prueba.jpg’,
headers: {
‘Content-Type’: ‘application/json’,
‘X-Auth-Token’: ‘TOKEN’
}
}

Hola @gmiro

Creo que podemos seguir en español. Descargué el archivo que enviaste e hice un simple POST a una variable de mi cuenta, así es como se ve el contexto de dicha variable:

Mi pregunta es: ¿Por qué en el código que me envías, el contexto de image lo tienes configurado como un Buffer y envías bytes en hexadecimal? Mira que enviando el string en base64 no hay problema. El tamaño de 112 kb no es inconveniente.

Luego, hice un código simple en un HTML Canvas para visualizar la imagen, usando el mismo string en base64 y este es el resultado:

En conclusión, revisa dos cosas:

  1. La forma en que envías la imagen en base64 en el contexto del POST.
  2. En un HTML Canvas, con este endpoint puedes obtener el valor de la variable que contiene el contexto, y luego mostrarla en el dashboard.

Me cuentas si tienes alguna duda.

Alejandro

Hola Alejandro,

Agradezco tu ayuda, ya tengo el base64 en Ubidots como tú mostraste:

Ahora, parece que cuando hago el GET desde el HTML canvas me carga el valor pero no el contexto:

también he probado a leer el contexto con el metric widget pero sale vacío.
Adjunto el código HTML por si ayuda:

Display Image and Context from Ubidots #debug { margin-top: 20px; padding: 10px; border: 1px solid #ccc; background-color: #f9f9f9; }
Latest Image
async function fetchImage() { const deviceLabel = 'image_test'; // Replace with your Ubidots device label const variableLabel = 'image_variable'; // Replace with your Ubidots variable label const token = 'TOKEN'; // Replace with your Ubidots token
        const debugDiv = document.getElementById('debug');

        function addDebugMessage(message) {
            debugDiv.innerHTML += `<p>${message}</p>`;
        }

        try {
            addDebugMessage('Fetching image and context from Ubidots...');

            const response = await fetch(`https://industrial.api.ubidots.com/api/v1.6/devices/${deviceLabel}/${variableLabel}/lv`, {
                method: 'GET',
                headers: {
                    'X-Auth-Token': token
                }
            });

            if (!response.ok) {
                throw new Error('Network response was not ok');
            }

            const data = await response.json();
            addDebugMessage('Data received from Ubidots:');
            addDebugMessage(`<pre>${JSON.stringify(data, null, 2)}</pre>`); // Display the entire response for debugging

            // Check if data.context is available and not empty
            if (data.context && Object.keys(data.context).length > 0) {
                // Display the context data
                const contextDiv = document.getElementById('context');
                contextDiv.innerHTML = `<pre>${JSON.stringify(data.context, null, 2)}</pre>`;

                if (data.context.image_base64) {
                    // Display the image if image_base64 is present
                    const base64Image = data.context.image_base64;
                    document.getElementById('image').src = `data:image/jpeg;base64,${base64Image}`;
                    addDebugMessage('Image displayed successfully.');
                } else {
                    addDebugMessage('No image_base64 in context');
                    document.getElementById('image').alt = 'No image found in context';
                }
            } else {
                addDebugMessage('No context found or empty context');
                document.getElementById('image').alt = 'No context found';
            }
        } catch (error) {
            addDebugMessage(`Fetching image failed: ${error.message}`);
            document.getElementById('image').alt = 'Error fetching image';
        }
    }

    // Fetch the image and context when the page loads
    window.onload = fetchImage;
</script>

Hola @gmiro

El problema es que veo que estas usando un Endpoint que justamente solo trae el último valor, al utilizar al final “lv”.

Utiliza mejor el siguiente:

https://industrial.api.ubidots.com/api/v2.0/variables/<variable_id>

Vas a obtener un JSON como respuesta y una llave llamada lastValue, ahí encontrarás el contexto que buscas.

Me cuentas si te funciona.

Gracias Alejandro, ya funciona.
Pongo aquí el código para futuros usuarios:

Node-red

[
    {
        "id": "587cada3c775303e",
        "type": "tab",
        "label": "Flow 5",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "inject-node",
        "type": "inject",
        "z": "587cada3c775303e",
        "name": "Trigger",
        "props": {
            "payload": true,
            "topic": ""
        },
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 870,
        "y": 260,
        "wires": [
            [
                "file-in-node"
            ]
        ]
    },
    {
        "id": "file-in-node",
        "type": "file in",
        "z": "587cada3c775303e",
        "name": "Read Image",
        "filename": "PATH TO THE IMAGE",
        "filenameType": "str",
        "format": "",
        "sendError": true,
        "allProps": false,
        "x": 1050,
        "y": 320,
        "wires": [
            [
                "c65279328c2ca7c8"
            ]
        ]
    },
    {
        "id": "function-node",
        "type": "function",
        "z": "587cada3c775303e",
        "name": "Prepare Payload",
        "func": "msg.headers = {\n    'Content-Type': 'application/json',\n    'X-Auth-Token': 'TOKEN'\n};\n\nmsg.payload = {\n    \"VARIABLE\": {\n        \"value\": 1234,\n        \"context\": { \"image\": msg.payload }\n    }\n};\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1430,
        "y": 320,
        "wires": [
            [
                "debug-node",
                "http-request-node"
            ]
        ]
    },
    {
        "id": "http-request-node",
        "type": "http request",
        "z": "587cada3c775303e",
        "name": "Send to Ubidots",
        "method": "POST",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "https://industrial.api.ubidots.com/api/v1.6/devices/DEVICE/",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 1740,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "debug-node",
        "type": "debug",
        "z": "587cada3c775303e",
        "name": "Debug",
        "active": true,
        "tosidebar": true,
        "console": true,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1780,
        "y": 380,
        "wires": []
    },
    {
        "id": "c65279328c2ca7c8",
        "type": "function",
        "z": "587cada3c775303e",
        "name": "function 4",
        "func": "// Assuming msg.payload contains the file buffer (e.g., from a previous node that reads the file)\n\n// Check if msg.payload exists and is a Buffer\nif (msg.payload instanceof Buffer) {\n    // Convert Buffer to base64\n    let base64Image = msg.payload.toString('base64');\n\n    // Optionally, you can attach the base64 string to the message payload\n    msg.payload = base64Image;\n\n    // Set the content type of the message if needed\n    msg.headers = {\n        'Content-Type': 'text/plain' // Adjust content type as per your requirement\n    };\n\n    return msg;\n} else {\n    // Handle error or unexpected input\n    node.warn(\"Expected msg.payload to be a Buffer containing image data\");\n    return null; // Or handle the error accordingly\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1220,
        "y": 320,
        "wires": [
            [
                "function-node"
            ]
        ]
    }
]

HTML CANVAS

<!DOCTYPE html>
<html>
<head>
    <title>Display Image, Context, and Timestamp from Ubidots</title>
    <style>
        #timestamp {
            font-size: 1.2em;
            margin-bottom: 10px;
        }
        #debug, #context {
            display: none; /* Hide the debug and context sections */
        }
    </style>
</head>
<body>
    <div id="timestamp">
        <!-- Timestamp will be displayed here -->
    </div>
    <div>
        <img id="image" src="" alt="Latest Image" style="max-width: 100%;">
    </div>
    
    <div id="context">
        <!-- Context data will be displayed here -->
    </div> 
        <div id="debug">
        <!-- Debug messages will be displayed here -->
    </div>
   
    <script>
        async function fetchImage() {
            const deviceLabel = 'image_test'; // Replace with your Ubidots device label
            const variableLabel = 'image_variable'; // Replace with your Ubidots variable label
            const token = 'token'; // Replace with your Ubidots token

            const debugDiv = document.getElementById('debug');

            function addDebugMessage(message) {
                debugDiv.innerHTML += `<p>${message}</p>`;
            }

            function formatTimestamp(timestamp) {
                const date = new Date(timestamp);
                return date.toLocaleString();
            }

            try {
                addDebugMessage('Fetching image, context, and timestamp from Ubidots...');

                const response = await fetch(`https://industrial.api.ubidots.com/api/v1.6/devices/${deviceLabel}/${variableLabel}`, {
                    method: 'GET',
                    headers: {
                        'X-Auth-Token': token
                    }
                });

                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }

                const data = await response.json();
                addDebugMessage('Data received from Ubidots:');
                addDebugMessage(`<pre>${JSON.stringify(data, null, 2)}</pre>`); // Display the entire response for debugging

                // Check if data.last_value and data.last_value.context are available and not empty
                if (data.last_value) {
                    const context = data.last_value.context;
                    const timestamp = data.last_value.created_at;

                    // Display the timestamp
                    const timestampDiv = document.getElementById('timestamp');
                    timestampDiv.innerHTML = `<p>Timestamp: ${formatTimestamp(timestamp)}</p>`;

                    // Display the context data
                    if (context) {
                        const contextDiv = document.getElementById('context');
                        contextDiv.innerHTML = `<pre>${JSON.stringify(context, null, 2)}</pre>`;

                        if (context.image) {
                            // Display the image if image is present in context
                            const base64Image = context.image;
                            document.getElementById('image').src = `data:image/jpeg;base64,${base64Image}`;
                            addDebugMessage('Image displayed successfully.');
                        } else {
                            addDebugMessage('No image in context');
                            document.getElementById('image').alt = 'No image found in context';
                        }
                    } else {
                        addDebugMessage('No context found or empty context');
                        document.getElementById('image').alt = 'No context found';
                    }
                } else {
                    addDebugMessage('No last_value found');
                    document.getElementById('image').alt = 'No last_value found';
                }
            } catch (error) {
                addDebugMessage(`Fetching image failed: ${error.message}`);
                document.getElementById('image').alt = 'Error fetching image';
            }
        }

        // Fetch the image, context, and timestamp when the page loads
        window.onload = fetchImage;
    </script>
</body>
</html>

Muchas gracias @gmiro

1 Like

Hay alguna mejora si la imagen supera el tamaño máximo de envío por HTTP, revisar el nodo de función.

Hola @gmiro, me podrías dar más detalles por favor.

¿Qué tamaño es la imagen que quieres enviar?

Hola Alejandro, si envío una imagen de más de 150Kb el POST me dice que el envío es demasiado pesado. Lo que recomiendo es recortar la imagen si es más grande de este tamaño. No necesito nada extra.

¿Por favor me puedes confirmar tu cuenta? Para validar algo.