Pular para o conteúdo principal

ChirpStack Middleware

Visão Geral

A Valeiot fornece um ChirpStack Middleware que permite a integração entre o ChirpStack e a plataforma Valeiot por meio de HTTP integrations.

Este middleware atua como uma ponte, recebendo eventos do ChirpStack e encaminhando de forma segura os dados dos dispositivos para a Valeiot utilizando o Network Token.


 


 

Passo 1: Obtendo o token de rede

Você precisará do Network Token da network à qual o dispositivo pertence.
Acesse o Valeiot Console:

https://console.valeiot.com/

Em seguida, navegue até:

Networks > Selecione a network correta > Tokens

Caso ainda não exista nenhum token criado, crie um token com permissão FULL.
Copie o token com permissão FULL e guarde-o, pois ele será necessário posteriormente para fins de autenticação.

Exemplo de token:
us1:Fe1l4K3UUm379kwlVhUn7saOwLmsYMS0RmRmXGH5yZcfSRQYsikQnuEGFV3c60

Network-valeiot


Passo 2: Configure a integração do ChirpStack

No ChirpStack, crie uma Application.
Em seguida, configure uma HTTP Integration para essa application.

Navegue até:

Applications > Integrations > HTTP

Chirpstack-gif


 

Preencha o formulário Update HTTP Integration com as seguintes informações:

Payload encoding: JSON

Event endpoint URL(s): https://chirpstack.middleware.valeiot.com:9090/v4

Headers
Authorization: Bearer YOUR_NETWORK_TOKEN
Content-Type: application/json

Exemplo:

Chirpstack-integration

atenção

O header Authorization é obrigatório.
Confirme se o Network Token é válido e possui permissão FULL.
Certifique-se de que o token pertence à mesma network do dispositivo.


 

Etapa 3: Verificar a ingestão do Payload e criar um Payload Parser.

Após a configuração, você já deverá começar a visualizar dados chegando à Valeiot.

Na visualização do dispositivo, verifique se o campo payload está sendo ingerido como Data Points.

Chirpstack-integration

Nesta etapa, o payload normalmente estará em formato hexadecimal (por exemplo: 010A1703E807D06401).

Para converter esse payload bruto em variáveis legíveis (como temperatura, nível de bateria, localização, etc.), é necessário criar um Payload Parser.

Consulte a seção Payload Parser para saber mais.


 

  1. Os dispositivos finais enviam uplinks para o ChirpStack
  2. O ChirpStack dispara a HTTP Integration
  3. O ChirpStack Middleware da Valeiot recebe o evento
  4. Os dados são validados e autenticados utilizando o Network Token
  5. O payload é ingerido na Valeiot como Data Points
  6. Um Payload Parser decodifica o payload em múltiplas variáveis


 

Para realizar comandos de downlink utilizando o ChirpStack, utilize a seguinte rota da API:

http://${chirpstackServer}/api/devices/${datasource.dnid}/queue
atenção

Para realizar um downlink, o dispositivo deve ter se comunicado ao menos uma vez.

Abaixo está um exemplo de como realizar um downlink utilizando o SDK da Valeiot.

import {
DatasourceDetails,
EventContext,
ScriptEvent,
WorkspaceConn,
} from "@sibis/valeiot-sdk";

interface Downlink {
type: string;
uplinkTime: string;
alarmRange: string;
}

const COMMANDS = {
ALARM_TYPE: "A1",
UPLINK_TIME: "B1",
};

const alarmRangeMap: Record<string, string> = {
"ON": "00",
"OFF": "01",
};

const uplinkTimeMap: Record<string, string> = {
"5 min": "00",
"30 min": "08",
"1 hour": "0C",
};

export async function downlinkHandler(ctx: EventContext, ev: ScriptEvent) {
const conn: WorkspaceConn = ctx.get("conn");
const downlink: Downlink = ev.content.formData.data;
const datasource: DatasourceDetails = ev.content.fullRowData;

const object = datasource.objects.find(
(obj: any) => obj.key === "info"
);

if (!object) {
ctx.result.set("Failure!", "Objeto 'info' ausente no datasource.");
ctx.result.setFailed();
return;
}

const objectPromise = conn.datasources.objects.updateValue({
datasourceId: datasource.id,
objectId: object.id,
body: {
...object.value,
...downlink,
},
});

const datapoints = datasource.datapoints || [];
if (datapoints.length === 0) {
ctx.result.set("Failure!", "Nenhum datapoint encontrado no datasource.");
ctx.result.setFailed();
return;
}

try {
const port =
datapoints.find((dp: any) => dp.variable === "port")?.value ?? 2;

const base64Data = parseDownlinkData(downlink);

const queueItem = {
confirmed: true,
fPort: port,
data: base64Data,
};

if (!process.env.CHIRPSTACK_API_KEY) {
ctx.result.set(
"Failure!",
"CHIRPSTACK_API_KEY não está configurada."
);
ctx.result.setFailed();
return;
}

const chirpstackServer =
process.env.CHIRPSTACK_SERVER ||
process.env.MIDDLEWARE_ENDPOINT ||
"chirpstack.valeiot.com:8090";

const apiUrl = `http://${chirpstackServer}/api/devices/${datasource.dnid}/queue`;

const response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Grpc-Metadata-Authorization": `Bearer ${process.env.CHIRPSTACK_API_KEY}`,
},
body: JSON.stringify({ queueItem }),
});

if (!response.ok) {
const errorData = await response.json().catch(() => ({
message: "Erro desconhecido",
}));

ctx.result.set(
"Failure!",
`Falha ao enviar downlink. Motivo: "${
errorData?.message || JSON.stringify(errorData)
}"`
);
ctx.result.setFailed();
return;
}

ctx.result.set("Success!", "Downlink enfileirado com sucesso.");
} catch (error: any) {
ctx.result.set(
"Failure!",
`Erro ao processar downlink: ${
error?.message || JSON.stringify(error)
}`
);
ctx.result.setFailed();
return;
}

await objectPromise;
ctx.result.set("Success!", "Downlink processado com sucesso.");
}

/**
* Converte uma string hexadecimal para Uint8Array
*/
function hexToUint8Array(hex: string): Uint8Array {
const length = hex.length / 2;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return bytes;
}

/**
* Converte Uint8Array para base64
*/
function uint8ArrayToBase64(bytes: Uint8Array): string {
return Buffer.from(bytes).toString("base64");
}

/**
* Codificação de downlink baseada em comandos
*/
function parseDownlinkData(downlink: Downlink): string {
let payloadHex: string;

if (downlink.type === "Configure alarm state") {
const value = alarmRangeMap[downlink.alarmRange];
if (!value) {
throw new Error(`Faixa de alarme inválida: ${downlink.alarmRange}`);
}
payloadHex = COMMANDS.ALARM_TYPE + value;
} else if (downlink.type === "Configure uplink frequency") {
const value = uplinkTimeMap[downlink.uplinkTime];
if (!value) {
throw new Error(`Intervalo de uplink inválido: ${downlink.uplinkTime}`);
}
payloadHex = COMMANDS.UPLINK_TIME + value;
}

return uint8ArrayToBase64(hexToUint8Array(payloadHex));
}