Blockquote
Sounds like you’re a full modern day dev team then
hahaha I would like to think that.
here are the updated segments of code that I went through
chirpstack.js the gRPC api service
// ChirpStack gRPC client service
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// Configuration for ChirpStack connection
const config = {
// Update these values based on your ChirpStack setup
host: process.env.CHIRPSTACK_HOST || '',
port: process.env.CHIRPSTACK_PORT || '',
apiToken: process.env.CHIRPSTACK_API_TOKEN || '', // Your API token
//tlsEnabled: process.env.CHIRPSTACK_TLS_ENABLED === 'true' || false,
protoDir: path.resolve(__dirname, '../../proto') // Location where you'll store the .proto files
};
// Create credentials based on configuration
const getCredentials = () => {
if (config.tlsEnabled) {
// For production, use secure connection
return grpc.credentials.createSsl();
} else {
// For development, you might use insecure connection
return grpc.credentials.createInsecure();
}
};
// Load all the proto files you need
const loadProtoDescriptors = () => {
// List of services you want to use from ChirpStack
const services = [
'device',
'device_profile',
'gateway',
'application',
'tenant',
'user',
// Add more services as needed
];
const protos = {};
services.forEach(service => {
const protoPath = path.join(config.protoDir, `${service}.proto`);
console.log(`Loading proto file: ${protoPath}`); // Debug logging
try {
const packageDefinition = protoLoader.loadSync(
protoPath, // Use full path
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [config.protoDir]
}
);
protos[service] = grpc.loadPackageDefinition(packageDefinition).api;
console.log(`Successfully loaded ${service} service`);
} catch (error) {
console.error(`Failed to load ${service}.proto:`, error);
throw error;
}
});
return protos;
};
// Create service clients
const createClients = (protos) => {
const clients = {};
const serverAddress = `${config.host}:${config.port}`;
const credentials = getCredentials();
// Create a client for each service
Object.keys(protos).forEach(service => {
const ServiceClass = protos[service][`${service.charAt(0).toUpperCase() + service.slice(1)}Service`];
if (ServiceClass) {
clients[service] = new ServiceClass(serverAddress, credentials);
}
});
return clients;
};
// Helper to handle authentication metadata
const getAuthMetadata = () => {
const metadata = new grpc.Metadata();
if (config.apiToken) {
metadata.add('authorization', `Bearer ${config.apiToken}`);
}
return metadata;
};
// Initialize all clients
let protos;
let clients;
try {
protos = loadProtoDescriptors();
console.log('Loaded proto descriptors:', Object.keys(protos));
clients = createClients(protos);
console.log('Initialized gRPC clients:', {
device: !!clients.device,
application: !!clients.application,
});
} catch (error) {
console.error('Failed to initialize ChirpStack gRPC clients:', error);
}
// Export functions for interacting with the ChirpStack API
module.exports = {
// Device operations
devices: {
list: (options = {}) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
// Create the proper request structure
const request = {
limit: options.limit || 100,
offset: options.offset || 0,
application_id: options.application_id,
search: options.search,
order_by: options.order_by,
order_by_desc: options.order_by_desc,
tags: options.tags,
device_profile_id: options.device_profile_id
};
// Add debug logging
console.log('Sending ListDevices request:', JSON.stringify(request, null, 2));
clients.device.List(request, getAuthMetadata(), (err, response) => {
if (err) {
console.error('ListDevices error:', err);
return reject(err);
}
console.log('ListDevices response:', JSON.stringify(response, null, 2));
resolve(response);
});
});
},
get: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.Get({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
create: (deviceData) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.Create(deviceData, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
update: (deviceData) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.Update(deviceData, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
delete: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.Delete({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
getActivation: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.GetActivation({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
Activation: (activationData) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.Activation({ dev_eui: activationData }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
deactivate: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.deactivate({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
getKeys: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.getKeys({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
createKeys: (keysData) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.createKeys({ dev_eui: keysData }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
updateKeys: (keysData) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.updateKeys({ dev_eui: keysData }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
deleteKeys: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.deleteKeys({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
getMetrics: ({ dev_eui, start, end, aggregation }) => {
return new Promise((resolve, reject) => {
dev_eui,
start,
end,
aggregation
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.getMetrics({ dev_eui, start, end, aggregation }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
getLinkMetrics: ({ dev_eui, start, end, aggregation }) => {
return new Promise((resolve, reject) => {
dev_eui,
start,
end,
aggregation
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.getLinkMetrics({ dev_eui, start, end, aggregation }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
getQueue: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.GetQueue({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
Enqueue: (queueItem) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.Enqueue({ queue_item: queueItem }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
flushQueue: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.FlushQueue({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
getNextFCntDown: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.getNextFCntDown({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
flushDevNonces: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('Device service not initialized'));
}
clients.device.flushDevNonces({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
// ... existing exports ...
getConnectionStatus: () => {
return {
connected: true, // Implement actual check
services: Object.keys(clients)
};
},
healthCheck: () => {
return new Promise((resolve) => {
if (!clients.device) {
return resolve(false);
}
// Simple request to check connectivity
clients.device.List({ limit: 1 }, getAuthMetadata(), (err) => {
resolve(!err);
});
});
},
},
// Add similar methods for other services (gateways, applications, etc.)
applications: {
// Application related methods
list: (request = {}) => {
return new Promise((resolve, reject) => {
if (!clients.application) {
return reject(new Error('Application service not initialized'));
}
clients.application.List(request, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
enqueueDownlink: {
// Enqueue a downlink message
Enqueue: (queueItem) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('DeviceQueue service not initialized'));
}
// Validate required fields
if (!queueItem.queue_item ||
!queueItem.queue_item.dev_eui ||
!queueItem.queue_item.f_port ||
!queueItem.queue_item.data) {
return reject(new Error('Missing required downlink fields'));
}
clients.device.Enqueue(queueItem, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
// List queued items for a device
list: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('DeviceQueue service not initialized'));
}
clients.device.List({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
},
// Flush queue for a device
flush: (devEui) => {
return new Promise((resolve, reject) => {
if (!clients.device) {
return reject(new Error('DeviceQueue service not initialized'));
}
clients.device.Flush({ dev_eui: devEui }, getAuthMetadata(), (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
}
},
// Add more application methods as needed
},
// Add more service objects as needed
};
devicecontroller.js
const chirpstack = require('../services/chirpstack');
// Get all devices with pagination
exports.getAllDevices = async (req, res) => {
try {
const applicationId = process.env.CHIRPSTACK_USER_APP_ID || '';
console.log("Using Application ID:", applicationId);
// Get devices from ChirpStack
const response = await chirpstack.devices.list({
limit: 100,
offset: 0,
application_id: applicationId
});
// Transform the data for your frontend
const devices = response.result.map(device => ({
devEui: device.dev_eui,
name: device.name,
description: device.description,
lastSeen: device.last_seen_at ?
new Date(device.last_seen_at.seconds * 1000).toISOString() : 'Never',
batteryLevel: device.device_status?.battery_level,
isActive: device.device_status?.external_power_source ||
(device.device_status?.battery_level > 0),
deviceProfile: device.device_profile_name,
signalStrength: device.device_status?.margin,
tags: device.tags
}));
res.json({
success: true,
count: response.total_count,
devices
});
} catch (error) {
console.error('Device controller error:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch devices',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
};
// Get detailed device information including queue items
exports.getDeviceDetails = async (req, res) => {
try {
const { devEui } = req.params;
if (!devEui || !/^[A-F0-9]{16}$/i.test(devEui)) {
return res.status(400).json({ error: 'Invalid DevEUI format' });
}
// Get device info
const device = await chirpstack.devices.get(devEui);
// Get activation info (if needed)
const activation = await chirpstack.devices.getActivation(devEui).catch(() => null);
res.json({
success: true,
device: {
...device,
activation
}
});
} catch (error) {
if (error.code === 5) { // NOT_FOUND
return res.status(404).json({ error: 'Device not found' });
}
res.status(500).json({
success: false,
error: 'Failed to fetch device details'
});
}
};
// Get device details with full history
exports.getDeviceById = async (req, res) => {
try {
const { devEui } = req.params;
if (!devEui) {
return res.status(400).json({ error: 'DevEUI is required' });
}
// Get device info
const device = await chirpstack.devices.get(devEui);
// Get activation info
const activation = await chirpstack.devices.getActivation(devEui);
// Get queue items
const queue = await chirpstack.deviceQueue.list({ dev_eui: devEui });
// Get metrics
const metrics = await chirpstack.devices.getMetrics({
dev_eui: devEui,
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Last 7 days
end: new Date(),
aggregation: 'DAY' // or 'DAY', 'MONTH'
});
res.json({
success: true,
result: {
...device,
activation,
queue: queue.result,
metrics
}
});
} catch (error) {
console.error(`Error fetching device ${req.params.devEui}:`, error);
if (error.code === 5) { // NOT_FOUND
return res.status(404).json({ error: 'Device not found' });
}
res.status(500).json({
success: false,
error: error.message || 'Failed to fetch device'
});
}
};
// Create a new device
exports.createDevice = async (req, res) => {
try {
const deviceData = req.body;
// Validate required fields
if (!deviceData.device || !deviceData.device.dev_eui || !deviceData.device.application_id) {
return res.status(400).json({ error: 'Missing required device information' });
}
const response = await chirpstack.devices.create(deviceData);
res.status(201).json(response);
} catch (error) {
console.error('Error creating device:', error);
res.status(500).json({ error: error.message || 'Failed to create device' });
}
};
// Update an existing device
exports.updateDevice = async (req, res) => {
try {
const { devEui } = req.params;
const deviceData = req.body;
// Ensure the device data contains the DevEUI from the URL
if (!deviceData.device) {
deviceData.device = {};
}
deviceData.device.dev_eui = devEui;
const response = await chirpstack.devices.update(deviceData);
res.json(response);
} catch (error) {
console.error(`Error updating device ${req.params.devEui}:`, error);
res.status(500).json({ error: error.message || 'Failed to update device' });
}
};
// Delete a device
exports.deleteDevice = async (req, res) => {
try {
const { devEui } = req.params;
if (!devEui) {
return res.status(400).json({ error: 'DevEUI is required' });
}
await chirpstack.devices.delete(devEui);
res.status(204).send(); // No content response for successful deletion
} catch (error) {
console.error(`Error deleting device ${req.params.devEui}:`, error);
res.status(500).json({ error: error.message || 'Failed to delete device' });
}
};
// Device metrics
exports.getMetrics = async (req, res) => {
try {
const { devEui } = req.params;
const { start, end, aggregation } = req.query;
const metrics = await chirpstack.devices.getMetrics({
dev_eui: devEui,
start: new Date(start),
end: new Date(end),
aggregation: aggregation || 'HOUR'
});
res.json({
success: true,
metrics
});
} catch (error) {
console.error(`Error getting metrics for device ${req.params.devEui}:`, error);
res.status(500).json({
success: false,
error: error.message || 'Failed to get device metrics'
});
}
};
what i get when I run the test
PS C:\intellisecIoT\nodejs\intelliseciot> node test-script.js
Successfully loaded user service
Loaded proto descriptors: [
'device',
'device_profile',
'gateway',
'application',
'tenant',
'user'
]
Initialized gRPC clients: { device: true, application: true }
Testing device listing...
Sending ListDevices request: {
"limit": 5,
"offset": 0,
"application_id": ""
}
ListDevices response: {
"result": [
{
"tags": {},
"dev_eui": "24e124445d354713",
"created_at": {
"seconds": "1743751598",
"nanos": 872155000
},
"updated_at": {
"seconds": "1744017014",
"nanos": 572388000
},
"last_seen_at": {
"seconds": "1747650146",
"nanos": 759629000
},
"name": "IO Controller Test Generator",
"description": "",
"device_profile_id": "49729896-cd65-471a-be26-52f0b98f43fc",
"device_profile_name": "UC300",
"device_status": {
"margin": 6,
"external_power_source": false,
"battery_level": -1
}
},
{
"tags": {},
"dev_eui": "24e124136d319204",
"created_at": {
"seconds": "1741768453",
"nanos": 971927000
},
"updated_at": {
"seconds": "1741769023",
"nanos": 221781000
},
"last_seen_at": {
"seconds": "1747650035",
"nanos": 315696000
},
"name": "Kitchen Freezer Sensor",
"description": "",
"device_profile_id": "bc08b8a3-ce21-48dc-b08d-9dfe5d7f20ae",
"device_profile_name": "EM300-MCS",
"device_status": {
"margin": 1,
"external_power_source": false,
"battery_level": 96.8499984741211
}
},
{
"tags": {},
"dev_eui": "24e124538c195163",
"created_at": {
"seconds": "1741768251",
"nanos": 648175000
},
"updated_at": {
"seconds": "1741768251",
"nanos": 648175000
},
"last_seen_at": {
"seconds": "1747649766",
"nanos": 368865000
},
"name": "Kitchen Movement Sensor",
"description": "",
"device_profile_id": "20e92c36-842a-4db2-93c5-9e622be64234",
"device_profile_name": "WS202",
"device_status": {
"margin": 6,
"external_power_source": false,
"battery_level": 48.810001373291016
}
},
{
"tags": {},
"dev_eui": "24e124716e210513",
"created_at": {
"seconds": "1741768395",
"nanos": 558757000
},
"updated_at": {
"seconds": "1741768395",
"nanos": 558757000
},
"last_seen_at": {
"seconds": "1747649993",
"nanos": 42397000
},
"name": "People Counter",
"description": "",
"device_profile_id": "4a16c151-ab3f-4ef1-9f9f-aef05302d69a",
"device_profile_name": "VS350",
"device_status": {
"margin": 7,
"external_power_source": true,
"battery_level": -1
}
},
{
"tags": {},
"dev_eui": "24e124535c400704",
"created_at": {
"seconds": "1741768580",
"nanos": 944276000
},
"updated_at": {
"seconds": "1741768580",
"nanos": 944276000
},
"last_seen_at": {
"seconds": "1747649810",
"nanos": 48745000
},
"name": "Test Panic Button",
"description": "",
"device_profile_id": "830b2ccc-c563-425c-881c-9b71815dae8e",
"device_profile_name": "WS101",
"device_status": {
"margin": 7,
"external_power_source": false,
"battery_level": 87.79000091552734
}
}
],
"total_count": 5
}
Devices: {
"result": [
{
"tags": {},
"dev_eui": "24e124445d354713",
"created_at": {
"seconds": "1743751598",
"nanos": 872155000
},
"updated_at": {
"seconds": "1744017014",
"nanos": 572388000
},
"last_seen_at": {
"seconds": "1747650146",
"nanos": 759629000
},
"name": "IO Controller Test Generator",
"description": "",
"device_profile_id": "49729896-cd65-471a-be26-52f0b98f43fc",
"device_profile_name": "UC300",
"device_status": {
"margin": 6,
"external_power_source": false,
"battery_level": -1
}
},
{
"tags": {},
"dev_eui": "24e124136d319204",
"created_at": {
"seconds": "1741768453",
"nanos": 971927000
},
"updated_at": {
"seconds": "1741769023",
"nanos": 221781000
},
"last_seen_at": {
"seconds": "1747650035",
"nanos": 315696000
},
"name": "Kitchen Freezer Sensor",
"description": "",
"device_profile_id": "bc08b8a3-ce21-48dc-b08d-9dfe5d7f20ae",
"device_profile_name": "EM300-MCS",
"device_status": {
"margin": 1,
"external_power_source": false,
"battery_level": 96.8499984741211
}
},
{
"tags": {},
"dev_eui": "24e124538c195163",
"created_at": {
"seconds": "1741768251",
"nanos": 648175000
},
"updated_at": {
"seconds": "1741768251",
"nanos": 648175000
},
"last_seen_at": {
"seconds": "1747649766",
"nanos": 368865000
},
"name": "Kitchen Movement Sensor",
"description": "",
"device_profile_id": "20e92c36-842a-4db2-93c5-9e622be64234",
"device_profile_name": "WS202",
"device_status": {
"margin": 6,
"external_power_source": false,
"battery_level": 48.810001373291016
}
},
{
"tags": {},
"dev_eui": "24e124716e210513",
"created_at": {
"seconds": "1741768395",
"nanos": 558757000
},
"updated_at": {
"seconds": "1741768395",
"nanos": 558757000
},
"last_seen_at": {
"seconds": "1747649993",
"nanos": 42397000
},
"name": "People Counter",
"description": "",
"device_profile_id": "4a16c151-ab3f-4ef1-9f9f-aef05302d69a",
"device_profile_name": "VS350",
"device_status": {
"margin": 7,
"external_power_source": true,
"battery_level": -1
}
},
{
"tags": {},
"dev_eui": "24e124535c400704",
"created_at": {
"seconds": "1741768580",
"nanos": 944276000
},
"updated_at": {
"seconds": "1741768580",
"nanos": 944276000
},
"last_seen_at": {
"seconds": "1747649810",
"nanos": 48745000
},
"name": "Test Panic Button",
"description": "",
"device_profile_id": "830b2ccc-c563-425c-881c-9b71815dae8e",
"device_profile_name": "WS101",
"device_status": {
"margin": 7,
"external_power_source": false,
"battery_level": 87.79000091552734
}
}
],
"total_count": 5
}
Found 5 devices
Testing metrics for device: IO Controller Test Generator (24e124445d354713)
Metrics for IO Controller Test Generator:
State Metrics:
gpio_out_2: off ()
gpio_out_1: off ()
Time Series Metrics:
Testing metrics for device: Kitchen Freezer Sensor (24e124136d319204)
Metrics for Kitchen Freezer Sensor:
State Metrics:
magnet_status: closed ()
Time Series Metrics:
battery:
battery: No data points available
humidity:
humidity: No data points available
temperature:
temperature: No data points available
Testing metrics for device: Kitchen Movement Sensor (24e124538c195163)
Metrics for Kitchen Movement Sensor:
State Metrics:
pir: movement detected ()
daylight: light ()
Time Series Metrics:
battery:
battery: No data points available
Testing metrics for device: People Counter (24e124716e210513)
Metrics for People Counter:
State Metrics:
Time Series Metrics:
Testing metrics for device: Test Panic Button (24e124535c400704)
Metrics for Test Panic Button:
State Metrics:
Time Series Metrics:
now I am trying to figure out why some of the metrics are not showing values but at lease steps in the right way.
so with the chirpstack’s SQL I would need to setup a purge to keep data for a year anything older than that purge it? will figure that out once i got the data coming through the right way first.