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.
Configuração do Uplink
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

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

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:

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.

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.
Resumo Uplink
- Os dispositivos finais enviam uplinks para o ChirpStack
- O ChirpStack dispara a HTTP Integration
- O ChirpStack Middleware da Valeiot recebe o evento
- Os dados são validados e autenticados utilizando o Network Token
- O payload é ingerido na Valeiot como Data Points
- Um Payload Parser decodifica o payload em múltiplas variáveis
Comando de Downlink usando ChirpStack
Para realizar comandos de downlink utilizando o ChirpStack, utilize a seguinte rota da API:
http://${chirpstackServer}/api/devices/${datasource.dnid}/queue
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));
}