mqtt-devices-parser 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Changelog.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.20
4
+ Adds Kafka consumer support with optional MQTT message parsing (#5)
5
+ supports tables for mqtt templates
6
+
7
+ ## 1.0.19
8
+ Removes adding project on launching process
9
+ models/models:
10
+ - seedCommonLwM2MObjects: avoids error on description property not defined on object
11
+ - changes lwm2m seed functions name
12
+
3
13
  ## 1.0.18
4
14
  Fix and improve Sensors, checks uid length
5
15
  Adds seedCommonLwM2MObjects and seedCommonLwM2MResources functions
package/Readme.md CHANGED
@@ -4,6 +4,7 @@
4
4
  ## NPM Module
5
5
  - MQTT parser
6
6
  - Parses topics coming from devices and updates data on dB
7
+ - **NEW**: Kafka consumer support for multi-process message parsing
7
8
 
8
9
  ## Deploy
9
10
  ```
@@ -25,6 +26,7 @@
25
26
 
26
27
  - mysql8.0
27
28
  - MQTT
29
+ - **NEW**: Apache Kafka (optional)
28
30
 
29
31
  ## Front end
30
32
  - Node service running the following [project](https://github.com/zimbora/mgmt-iot-web)
@@ -37,3 +39,81 @@ In your program set the same struct and pass it as argument on the module init c
37
39
  An mqtt account will be created using the following settings
38
40
  mqtt.user - mqtt user login
39
41
  mqtt.pwd - mqtt pwd login
42
+
43
+ ### Kafka Consumer Configuration (NEW)
44
+
45
+ The module now supports consuming messages from Kafka topics as an alternative or complement to MQTT. This enables multi-process message parsing using Kafka's shared subscription mechanism.
46
+
47
+ #### Environment Variables
48
+
49
+ **Kafka Settings:**
50
+ - `KAFKA_ENABLED` - Set to `'true'` to enable Kafka consumer (default: `false`)
51
+ - `KAFKA_BROKERS` - Comma-separated list of Kafka brokers (default: `'localhost:9092'`)
52
+ - `KAFKA_GROUP_ID` - Consumer group ID for shared subscriptions (default: `'mqtt-devices-parser-group'`)
53
+ - `KAFKA_CLIENT_ID` - Kafka client identifier (default: `'mqtt-devices-parser'`)
54
+
55
+ **Authentication (SASL):**
56
+ - `KAFKA_SASL_MECHANISM` - SASL mechanism: `'plain'`, `'scram-sha-256'`, `'scram-sha-512'` (default: `'plain'`)
57
+ - `KAFKA_SASL_USERNAME` - SASL username (default: empty)
58
+ - `KAFKA_SASL_PASSWORD` - SASL password (default: empty)
59
+
60
+ **Security:**
61
+ - `KAFKA_SSL` - Set to `'true'` to enable SSL (default: `false`)
62
+
63
+ **Connection Settings:**
64
+ - `KAFKA_CONNECTION_TIMEOUT` - Connection timeout in ms (default: `3000`)
65
+ - `KAFKA_REQUEST_TIMEOUT` - Request timeout in ms (default: `30000`)
66
+
67
+ **MQTT Parsing Control:**
68
+ - `MQTT_PARSE_MESSAGES` - Set to `'false'` to disable direct MQTT message parsing (default: `true`)
69
+
70
+ #### Usage Examples
71
+
72
+ **Example 1: Enable Kafka with basic authentication**
73
+ ```bash
74
+ export KAFKA_ENABLED=true
75
+ export KAFKA_BROKERS=kafka1:9092,kafka2:9092
76
+ export KAFKA_SASL_USERNAME=myuser
77
+ export KAFKA_SASL_PASSWORD=mypassword
78
+ export KAFKA_SASL_MECHANISM=scram-sha-256
79
+ ```
80
+
81
+ **Example 2: Use Kafka only (disable MQTT parsing)**
82
+ ```bash
83
+ export KAFKA_ENABLED=true
84
+ export MQTT_PARSE_MESSAGES=false
85
+ export KAFKA_BROKERS=kafka-cluster:9092
86
+ ```
87
+
88
+ **Example 3: Multi-process deployment**
89
+ ```bash
90
+ # Start multiple instances with the same group ID for load balancing
91
+ export KAFKA_ENABLED=true
92
+ export KAFKA_GROUP_ID=my-parser-group
93
+ export KAFKA_BROKERS=kafka1:9092,kafka2:9092
94
+ # Each process will consume different partitions automatically
95
+ ```
96
+
97
+ #### Kafka Topic Structure
98
+
99
+ Kafka topics should correspond to project names configured in your `projects` array. Messages are expected to have:
100
+
101
+ - **Topic**: The project name (e.g., `demo`, `freeRTOS2`)
102
+ - **Key**: Device path part (e.g., `uid:123/status`)
103
+ - **Value**: Message payload (same format as MQTT payloads)
104
+
105
+ The consumer will automatically format messages as `{project}/{key}` to match the expected MQTT topic structure.
106
+
107
+ #### Shared Subscriptions
108
+
109
+ When multiple instances use the same `KAFKA_GROUP_ID`, Kafka automatically distributes messages across consumers, enabling:
110
+
111
+ - **Load balancing**: Messages are distributed across multiple parser instances
112
+ - **High availability**: If one instance fails, others continue processing
113
+ - **Scalability**: Add more instances to handle increased load
114
+
115
+ #### Message Flow
116
+
117
+ 1. **MQTT Mode** (traditional): MQTT messages → `$.device.parseMessage`
118
+ 2. **Kafka Mode**: Kafka messages → Topic formatting → `$.device.parseMessage`
119
+ 3. **Hybrid Mode**: Both MQTT and Kafka messages → `$.device.parseMessage`
package/config/index.js CHANGED
@@ -13,7 +13,22 @@ module.exports = {
13
13
  user:process.env.MQTT_USER || 'admin',
14
14
  pwd:process.env.MQTT_PWD || 'admin',
15
15
  client:process.env.MQTT_CLIENT || 'mqtt-devices-parser',
16
- logs_path: process.env.MQTT_LOGS || 'uServices'
16
+ logs_path: process.env.MQTT_LOGS || 'uServices',
17
+ parseMessages: process.env.MQTT_PARSE_MESSAGES !== 'false' // Default to true for backward compatibility
18
+ },
19
+ kafka: {
20
+ enabled: process.env.KAFKA_ENABLED === 'true' ? true : true,
21
+ brokers: (process.env.KAFKA_BROKERS || 'localhost:9092').split(','),
22
+ groupId: process.env.KAFKA_GROUP_ID || 'mqtt-devices-parser-group',
23
+ clientId: process.env.KAFKA_CLIENT_ID || 'mqtt-devices-parser',
24
+ sasl: {
25
+ mechanism: process.env.KAFKA_SASL_MECHANISM || 'plain', // plain, scram-sha-256, scram-sha-512
26
+ username: process.env.KAFKA_SASL_USERNAME || '',
27
+ password: process.env.KAFKA_SASL_PASSWORD || ''
28
+ },
29
+ ssl: process.env.KAFKA_SSL === 'true' ? true : false,
30
+ connectionTimeout: parseInt(process.env.KAFKA_CONNECTION_TIMEOUT) || 3000,
31
+ requestTimeout: parseInt(process.env.KAFKA_REQUEST_TIMEOUT) || 30000
17
32
  },
18
33
  mysqldb: {
19
34
  conn_limit: process.env.DB_CONN_LIMIT || 15,
@@ -27,7 +42,7 @@ module.exports = {
27
42
  projects: {
28
43
  demo : process.env.demo || false,
29
44
  },
30
- demo : {
45
+ demo : { // not need anymore. Can be created on dashboard
31
46
  uidPrefix: 'uid:',
32
47
  uidLength: 16 // max len for uid
33
48
  }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Example showing how to use mqtt-devices-parser with Kafka consumer support
3
+ *
4
+ * This example demonstrates:
5
+ * 1. Traditional MQTT-only mode
6
+ * 2. Kafka-only mode
7
+ * 3. Hybrid mode (both MQTT and Kafka)
8
+ */
9
+
10
+ const parser = require('../index.js');
11
+ const fs = require('fs');
12
+
13
+ global.BASE_DIR = process.cwd();
14
+
15
+ // Example configuration with Kafka support
16
+ const configWithKafka = {
17
+ version: "1.0.0",
18
+ dev: true,
19
+ web: {
20
+ protocol: "http://",
21
+ domain: 'localhost',
22
+ port: 8002,
23
+ },
24
+ mqtt: {
25
+ protocol: 'MQTT',
26
+ host: 'localhost',
27
+ port: '1883',
28
+ user: 'admin',
29
+ pwd: 'admin',
30
+ client: 'mqtt-devices-parser-example',
31
+ logs_path: 'uServices',
32
+ parseMessages: true // Set to false to disable MQTT message parsing
33
+ },
34
+ kafka: {
35
+ enabled: true, // Enable Kafka consumer
36
+ brokers: ['localhost:9092'], // Your Kafka brokers
37
+ groupId: 'mqtt-devices-parser-group',
38
+ clientId: 'mqtt-devices-parser-client',
39
+ sasl: {
40
+ mechanism: 'plain', // or 'scram-sha-256', 'scram-sha-512'
41
+ username: '', // Your Kafka username
42
+ password: '' // Your Kafka password
43
+ },
44
+ ssl: false, // Set to true for SSL/TLS
45
+ connectionTimeout: 3000,
46
+ requestTimeout: 30000
47
+ },
48
+ mysqldb: {
49
+ conn_limit: 15,
50
+ host: 'localhost',
51
+ port: 3306,
52
+ user: 'user',
53
+ pwd: 'user_pwd',
54
+ name: 'mqtt-aedes',
55
+ },
56
+ sync_main_tables: true,
57
+ projects: {
58
+ demo: true,
59
+ // Add your project names here
60
+ // These will become Kafka topic names when Kafka is enabled
61
+ },
62
+ demo: {
63
+ uidPrefix: 'uid:',
64
+ uidLength: 16
65
+ }
66
+ };
67
+
68
+ // Example: MQTT-only configuration (traditional mode)
69
+ const configMQTTOnly = {
70
+ ...configWithKafka,
71
+ kafka: {
72
+ ...configWithKafka.kafka,
73
+ enabled: false // Disable Kafka
74
+ },
75
+ mqtt: {
76
+ ...configWithKafka.mqtt,
77
+ parseMessages: true // Keep MQTT parsing enabled
78
+ }
79
+ };
80
+
81
+ // Example: Kafka-only configuration
82
+ const configKafkaOnly = {
83
+ ...configWithKafka,
84
+ kafka: {
85
+ ...configWithKafka.kafka,
86
+ enabled: true // Enable Kafka
87
+ },
88
+ mqtt: {
89
+ ...configWithKafka.mqtt,
90
+ parseMessages: false // Disable MQTT parsing
91
+ }
92
+ };
93
+
94
+ // Load projects from directories
95
+ var projectsPath = '../projects/';
96
+ var projects = [];
97
+
98
+ try {
99
+ fs.readdirSync(projectsPath)
100
+ .filter((file) => {
101
+ return (file.indexOf('.') == -1);
102
+ })
103
+ .forEach((project) => {
104
+ if (configWithKafka.projects[project]) {
105
+ projects.push(project);
106
+ }
107
+ });
108
+ } catch (error) {
109
+ console.log('Projects directory not found, using default projects');
110
+ projects = ['demo'];
111
+ }
112
+
113
+ async function runExample() {
114
+ try {
115
+ console.log('=== MQTT Devices Parser with Kafka Support Example ===\n');
116
+
117
+ // Choose which configuration to use
118
+ const selectedConfig = configWithKafka; // Change this to test different modes
119
+
120
+ console.log('Configuration mode:');
121
+ console.log('- MQTT parsing enabled:', selectedConfig.mqtt.parseMessages);
122
+ console.log('- Kafka consumer enabled:', selectedConfig.kafka.enabled);
123
+ console.log('- Projects:', projects.join(', '));
124
+ console.log();
125
+
126
+ if (selectedConfig.kafka.enabled) {
127
+ console.log('Kafka configuration:');
128
+ console.log('- Brokers:', selectedConfig.kafka.brokers.join(', '));
129
+ console.log('- Group ID:', selectedConfig.kafka.groupId);
130
+ console.log('- SASL enabled:', !!(selectedConfig.kafka.sasl.username && selectedConfig.kafka.sasl.password));
131
+ console.log();
132
+ }
133
+
134
+ // Initialize the parser
135
+ console.log('Initializing parser...');
136
+ await parser.init(selectedConfig, projects);
137
+
138
+ console.log('Parser initialized successfully!');
139
+ console.log();
140
+
141
+ if (selectedConfig.kafka.enabled) {
142
+ console.log('Kafka consumer is running and listening for messages on topics:', projects.join(', '));
143
+ console.log('Send messages to these Kafka topics with format:');
144
+ console.log('- Topic: <project_name> (e.g., "demo")');
145
+ console.log('- Key: <device_path> (e.g., "uid:123/status")');
146
+ console.log('- Value: <message_payload> (e.g., "online")');
147
+ console.log();
148
+ }
149
+
150
+ if (selectedConfig.mqtt.parseMessages) {
151
+ console.log('MQTT client is connected and parsing messages');
152
+ console.log('Subscribed to MQTT topics:', projects.map(p => p + '/#').join(', '));
153
+ console.log();
154
+ }
155
+
156
+ console.log('Press Ctrl+C to stop...');
157
+
158
+ // Keep the process running
159
+ await new Promise(resolve => {
160
+ process.on('SIGINT', () => {
161
+ console.log('\nShutting down gracefully...');
162
+ resolve();
163
+ });
164
+ });
165
+
166
+ } catch (error) {
167
+ console.error('Error running example:', error);
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ // Handle unhandled rejections
173
+ process.on('unhandledRejection', (reason, promise) => {
174
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
175
+ if (configWithKafka.dev) {
176
+ process.exit(1);
177
+ }
178
+ });
179
+
180
+ // Run the example
181
+ runExample();
package/index.js CHANGED
@@ -7,6 +7,7 @@ $.md5 = require('md5');
7
7
  $.config = require('./config');
8
8
  $.models = require('./models/models.js');
9
9
  $.device = require('./src/device/device.js');
10
+ $.kafka_consumer = require('./src/kafka/consumer.js');
10
11
  $.db = require('./src/db/db');
11
12
  $.db_data = require('./src/db/data');
12
13
  $.db_project = require('./src/db/project');
@@ -40,6 +41,12 @@ var self = module.exports = {
40
41
  //auth.init();
41
42
  await $.device.init($.config,projects);
42
43
 
44
+ // Initialize Kafka consumer if enabled
45
+ if ($.config.kafka.enabled) {
46
+ await $.kafka_consumer.init($.config, projects);
47
+ await $.kafka_consumer.start();
48
+ }
49
+
43
50
  console.log("mqtt connecting..")
44
51
  mqtt_connect();
45
52
 
@@ -107,6 +114,7 @@ var self = module.exports = {
107
114
  // adds client with credentials admin@admin
108
115
  await $.models.insertClient("admin","admin",user_id); // dashboard login
109
116
 
117
+ /* Add projects using dashboard !!
110
118
  for(name of projects){
111
119
  project = $.config[name];
112
120
  if (project) {
@@ -115,6 +123,7 @@ var self = module.exports = {
115
123
  await $.models.insertProject(projectData);
116
124
  }
117
125
  }
126
+ */
118
127
  }
119
128
 
120
129
  // project tables
@@ -179,8 +188,11 @@ function mqtt_connect(){
179
188
  });
180
189
 
181
190
  $.mqtt_client.on("message", (topic, payload, packet) => {
182
- // payload is Buffer
183
- $.device.parseMessage($.mqtt_client,topic.toString(),payload.toString(),packet.retain);
191
+ // Only parse MQTT messages if enabled in configuration
192
+ if ($.config.mqtt.parseMessages) {
193
+ // payload is Buffer
194
+ $.device.parseMessage($.mqtt_client,topic.toString(),payload.toString(),packet.retain);
195
+ }
184
196
  });
185
197
 
186
198
  $.mqtt_client.on("reconnect",()=>{
package/models/models.js CHANGED
@@ -253,7 +253,7 @@ var self = module.exports = {
253
253
  });
254
254
  },
255
255
 
256
- seedCommonLwM2MObjects: async(objects) => {
256
+ seedLwM2MObjects: async(objects) => {
257
257
  try {
258
258
  const results = await Promise.all(
259
259
  objects.map(async (object) => {
@@ -261,7 +261,7 @@ var self = module.exports = {
261
261
  {
262
262
  objectId: object.objectId,
263
263
  name: object.name,
264
- description: object.description,
264
+ description: object?.description,
265
265
  },
266
266
  {
267
267
  where: { objectId: object.objectId }, // Ensure the record exists based on objectId
@@ -317,7 +317,7 @@ var self = module.exports = {
317
317
  * @param {import('sequelize').Sequelize} sequelize - An initialized Sequelize instance.
318
318
  * @returns {Promise<Array<{ objectId:number, resourceId:number, created:boolean, updated:boolean }>>}
319
319
  */
320
- seedCommonLwM2MResources: async (resources) => {
320
+ seedLwM2MResources: async (resources) => {
321
321
  if (!sequelize || !sequelize.models || !sequelize.models['lwm2mResources']) {
322
322
  throw new Error("sequelize instance with model 'lwm2mResources' is required");
323
323
  }
@@ -0,0 +1,41 @@
1
+
2
+ module.exports = (sequelize,DataTypes)=>{
3
+ return sequelize.define("mqtt", {
4
+ device_id: {
5
+ type: DataTypes.INTEGER,
6
+ allowNull: true
7
+ },
8
+ topic: {
9
+ type: DataTypes.STRING,
10
+ allowNull: false
11
+ },
12
+ description: {
13
+ type: DataTypes.JSON,
14
+ allowNull: false
15
+ },
16
+ defaultData: {
17
+ type: DataTypes.JSON,
18
+ allowNull: true
19
+ },
20
+ remoteData: {
21
+ type: DataTypes.JSON,
22
+ allowNull: true
23
+ },
24
+ localData: {
25
+ type: DataTypes.JSON,
26
+ allowNull: true
27
+ },
28
+ readInterval: {
29
+ type: DataTypes.INTEGER,
30
+ allowNull: true
31
+ },
32
+ template_id: {
33
+ type: DataTypes.INTEGER,
34
+ allowNull: true,
35
+ }
36
+ },
37
+ {
38
+ tableName: 'mqtt',
39
+ freezeTableName: true
40
+ })
41
+ }
@@ -0,0 +1,29 @@
1
+
2
+ module.exports = (sequelize,DataTypes)=>{
3
+ return sequelize.define("mqttTemplate", {
4
+ topic: {
5
+ type: DataTypes.STRING,
6
+ allowNull: false
7
+ },
8
+ description: {
9
+ type: DataTypes.JSON,
10
+ allowNull: false
11
+ },
12
+ defaultData: {
13
+ type: DataTypes.JSON,
14
+ allowNull: true
15
+ },
16
+ readInterval: {
17
+ type: DataTypes.INTEGER,
18
+ allowNull: true
19
+ },
20
+ template_id: {
21
+ type: DataTypes.INTEGER,
22
+ allowNull: false
23
+ },
24
+ },
25
+ {
26
+ tableName: 'mqttTemplate',
27
+ freezeTableName: true
28
+ })
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mqtt-devices-parser",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,6 +11,7 @@
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
13
  "async": "^3.2.4",
14
+ "kafkajs": "^2.2.4",
14
15
  "lodash": "^4.17.21",
15
16
  "md5": "^2.3.0",
16
17
  "moment": "^2.29.4",
@@ -35,6 +35,10 @@ var self = module.exports = {
35
35
 
36
36
  parseMessage : async (client,topic,payload,retain)=>{
37
37
 
38
+ try{
39
+ payload = JSON.parse(payload)
40
+ }catch(err){};
41
+
38
42
  let topic_bck = topic;
39
43
 
40
44
  //let index = topic.indexOf(MACRO_UID_PREFIX);
@@ -0,0 +1,176 @@
1
+ const { Kafka, logLevel } = require('kafkajs');
2
+
3
+ var kafka = null;
4
+ var consumer = null;
5
+ var running = false;
6
+
7
+ var self = module.exports = {
8
+
9
+ init: async (config, projects) => {
10
+
11
+ return new Promise(async (resolve, reject) => {
12
+ try {
13
+ if (!config.kafka.enabled) {
14
+ console.log("Kafka consumer is disabled");
15
+ return resolve();
16
+ }
17
+
18
+ console.log("Initializing Kafka consumer...");
19
+
20
+ // Build Kafka configuration
21
+ const kafkaConfig = {
22
+ clientId: config.kafka.clientId,
23
+ brokers: config.kafka.brokers,
24
+ connectionTimeout: config.kafka.connectionTimeout,
25
+ requestTimeout: config.kafka.requestTimeout,
26
+ logLevel: logLevel.INFO
27
+ };
28
+
29
+ // Add SASL authentication if credentials provided
30
+ if (config.kafka.sasl.username && config.kafka.sasl.password) {
31
+ kafkaConfig.sasl = {
32
+ mechanism: config.kafka.sasl.mechanism,
33
+ username: config.kafka.sasl.username,
34
+ password: config.kafka.sasl.password
35
+ };
36
+ }
37
+
38
+ // Add SSL configuration if enabled
39
+ if (config.kafka.ssl) {
40
+ kafkaConfig.ssl = true;
41
+ }
42
+
43
+ kafka = new Kafka(kafkaConfig);
44
+
45
+ // Create consumer with shared subscription support
46
+ consumer = kafka.consumer({
47
+ groupId: config.kafka.groupId,
48
+ sessionTimeout: 30000,
49
+ rebalanceTimeout: 60000,
50
+ heartbeatInterval: 3000,
51
+ maxBytesPerPartition: 1048576,
52
+ minBytes: 1,
53
+ maxBytes: 10485760,
54
+ maxWaitTimeInMs: 5000,
55
+ retry: {
56
+ initialRetryTime: 100,
57
+ retries: 8
58
+ }
59
+ });
60
+
61
+ // Subscribe to project topics
62
+ const topics = projects.map(project => project.toString());
63
+
64
+ if (topics.length === 0) {
65
+ console.log("No Kafka topics to subscribe to");
66
+ return resolve();
67
+ }
68
+
69
+ await consumer.subscribe({
70
+ topics: topics,
71
+ fromBeginning: false
72
+ });
73
+
74
+ console.log(`Kafka consumer subscribed to topics: ${topics.join(', ')}`);
75
+
76
+ return resolve();
77
+ } catch (error) {
78
+ console.error("Failed to initialize Kafka consumer:", error);
79
+ return reject(error);
80
+ }
81
+ });
82
+ },
83
+
84
+ start: async () => {
85
+ if (!consumer || running) {
86
+ return;
87
+ }
88
+
89
+ try {
90
+ console.log("Starting Kafka consumer...");
91
+ running = true;
92
+
93
+ await consumer.run({
94
+ eachMessage: async ({ topic, partition, message }) => {
95
+ try {
96
+ let payload = message.value ? message.value.toString() : '';
97
+ const kafkaTopic = topic.toString();
98
+
99
+ // Format message to look like MQTT format for compatibility
100
+ // Kafka topic becomes the project name, message key becomes the device path
101
+ const messageKey = message.key ? message.key.toString() : '';
102
+
103
+ let formattedTopic;
104
+ if (messageKey) {
105
+ // If message has a key, use it as the device path: project/device_path
106
+ formattedTopic = `${kafkaTopic}/${messageKey}`;
107
+ } else {
108
+ // If no key, try to extract from headers or use a default format
109
+ const headers = message.headers || {};
110
+ const devicePath = headers.device_path ? headers.device_path.toString() : 'unknown';
111
+ formattedTopic = `${kafkaTopic}/${devicePath}`;
112
+ }
113
+
114
+ try{
115
+ payload = JSON.parse(payload);
116
+ }catch(error){}
117
+
118
+ // Call the device parser with formatted topic
119
+ await $.device.parseMessage(payload.client.id, formattedTopic, payload.mqtt.payload, payload.mqtt.retain);
120
+
121
+ } catch (error) {
122
+ console.error('[Kafka] Error processing message:', {
123
+ topic: topic.toString(),
124
+ partition,
125
+ offset: message.offset ? message.offset.toString() : 'unknown',
126
+ error: error.message
127
+ });
128
+ }
129
+ },
130
+ });
131
+
132
+ console.log("Kafka consumer started successfully");
133
+ } catch (error) {
134
+ console.error("Failed to start Kafka consumer:", error);
135
+ running = false;
136
+ throw error;
137
+ }
138
+ },
139
+
140
+ stop: async () => {
141
+ if (!consumer || !running) {
142
+ return;
143
+ }
144
+
145
+ try {
146
+ console.log("Stopping Kafka consumer...");
147
+ running = false;
148
+ await consumer.stop();
149
+ await consumer.disconnect();
150
+ console.log("Kafka consumer stopped successfully");
151
+ } catch (error) {
152
+ console.error("Error stopping Kafka consumer:", error);
153
+ }
154
+ },
155
+
156
+ isRunning: () => {
157
+ return running;
158
+ }
159
+ };
160
+
161
+ // Graceful shutdown handling
162
+ process.on('SIGINT', async () => {
163
+ if (running) {
164
+ console.log('Received SIGINT, gracefully shutting down Kafka consumer...');
165
+ await self.stop();
166
+ }
167
+ process.exit(0);
168
+ });
169
+
170
+ process.on('SIGTERM', async () => {
171
+ if (running) {
172
+ console.log('Received SIGTERM, gracefully shutting down Kafka consumer...');
173
+ await self.stop();
174
+ }
175
+ process.exit(0);
176
+ });
@@ -0,0 +1,118 @@
1
+ const fs = require('fs');
2
+
3
+ $ = {};
4
+ var path = "/../..";
5
+ $.config = require(__dirname + path + '/config');
6
+
7
+ // Mock device module to prevent actual parsing
8
+ $.device = {
9
+ parseMessage: async (client, topic, payload, retain) => {
10
+ console.log('Mock parseMessage called:', { topic, payload, retain });
11
+ return Promise.resolve();
12
+ }
13
+ };
14
+
15
+ async function testConfigurationValues() {
16
+ console.log('\n=== Testing Configuration Values ===');
17
+
18
+ // Test default values
19
+ console.log('Default MQTT parseMessages:', $.config.mqtt.parseMessages);
20
+ console.log('Default Kafka enabled:', $.config.kafka.enabled);
21
+ console.log('Default Kafka brokers:', $.config.kafka.brokers);
22
+ console.log('Default Kafka groupId:', $.config.kafka.groupId);
23
+ console.log('Default Kafka SASL mechanism:', $.config.kafka.sasl.mechanism);
24
+
25
+ // Test with environment variables
26
+ process.env.MQTT_PARSE_MESSAGES = 'false';
27
+ process.env.KAFKA_ENABLED = 'true';
28
+ process.env.KAFKA_BROKERS = 'broker1:9092,broker2:9092';
29
+ process.env.KAFKA_GROUP_ID = 'test-group';
30
+ process.env.KAFKA_SASL_MECHANISM = 'scram-sha-256';
31
+ process.env.KAFKA_SASL_USERNAME = 'testuser';
32
+ process.env.KAFKA_SASL_PASSWORD = 'testpass';
33
+
34
+ // Reload config to pick up env variables
35
+ delete require.cache[require.resolve(__dirname + path + '/config')];
36
+ const newConfig = require(__dirname + path + '/config');
37
+
38
+ console.log('\nWith environment variables:');
39
+ console.log('MQTT parseMessages:', newConfig.mqtt.parseMessages);
40
+ console.log('Kafka enabled:', newConfig.kafka.enabled);
41
+ console.log('Kafka brokers:', newConfig.kafka.brokers);
42
+ console.log('Kafka groupId:', newConfig.kafka.groupId);
43
+ console.log('Kafka SASL mechanism:', newConfig.kafka.sasl.mechanism);
44
+ console.log('Kafka SASL username:', newConfig.kafka.sasl.username);
45
+ console.log('Kafka SASL password:', newConfig.kafka.sasl.password ? '[SET]' : '[NOT SET]');
46
+
47
+ console.log('✓ Configuration values test passed!');
48
+ }
49
+
50
+ async function testKafkaConsumerModule() {
51
+ console.log('\n=== Testing Kafka Consumer Module Structure ===');
52
+
53
+ try {
54
+ const kafkaConsumer = require(__dirname + path + '/src/kafka/consumer.js');
55
+
56
+ // Test module exports
57
+ console.log('Module has init method:', typeof kafkaConsumer.init === 'function');
58
+ console.log('Module has start method:', typeof kafkaConsumer.start === 'function');
59
+ console.log('Module has stop method:', typeof kafkaConsumer.stop === 'function');
60
+ console.log('Module has isRunning method:', typeof kafkaConsumer.isRunning === 'function');
61
+
62
+ // Test initial state
63
+ console.log('Initial running state:', kafkaConsumer.isRunning());
64
+
65
+ // Test with disabled Kafka
66
+ const configDisabled = { kafka: { enabled: false } };
67
+ await kafkaConsumer.init(configDisabled, ['test']);
68
+ console.log('Init with disabled config succeeded');
69
+
70
+ console.log('✓ Kafka consumer module structure test passed!');
71
+ } catch (error) {
72
+ console.error('✗ Kafka consumer module test failed:', error.message);
73
+ }
74
+ }
75
+
76
+ async function testMQTTMessageParsing() {
77
+ console.log('\n=== Testing MQTT Message Parsing Logic ===');
78
+
79
+ // Test the logic that would be used in index.js
80
+ const testConfigs = [
81
+ { mqtt: { parseMessages: true } },
82
+ { mqtt: { parseMessages: false } }
83
+ ];
84
+
85
+ for (const config of testConfigs) {
86
+ console.log(`\nTesting with parseMessages: ${config.mqtt.parseMessages}`);
87
+
88
+ // Simulate the MQTT message handler logic
89
+ const mockTopic = "test/uid:123/status";
90
+ const mockPayload = "online";
91
+ const mockPacket = { retain: false };
92
+
93
+ if (config.mqtt.parseMessages) {
94
+ console.log('Would call device.parseMessage');
95
+ await $.device.parseMessage(null, mockTopic, mockPayload, mockPacket.retain);
96
+ } else {
97
+ console.log('Would skip device.parseMessage');
98
+ }
99
+ }
100
+
101
+ console.log('✓ MQTT message parsing logic test passed!');
102
+ }
103
+
104
+ async function runTests() {
105
+ console.log('Starting Kafka Integration Tests...');
106
+
107
+ await testConfigurationValues();
108
+ await testKafkaConsumerModule();
109
+ await testMQTTMessageParsing();
110
+
111
+ console.log('\n=== All Tests Completed Successfully ===');
112
+ process.exit(0);
113
+ }
114
+
115
+ runTests().catch(error => {
116
+ console.error('Test suite failed:', error);
117
+ process.exit(1);
118
+ });
@@ -0,0 +1,72 @@
1
+ const fs = require('fs');
2
+
3
+ $ = {};
4
+ var path = "/../..";
5
+ $.config = require(__dirname + path + '/config');
6
+ $.kafka_consumer = require(__dirname + path + '/src/kafka/consumer.js');
7
+ $.device = {
8
+ parseMessage: async (client, topic, payload, retain) => {
9
+ console.log('Mock parseMessage called:', { topic, payload, retain });
10
+ }
11
+ };
12
+
13
+ async function testKafkaConsumerInit() {
14
+ console.log('\n=== Testing Kafka Consumer Initialization ===');
15
+
16
+ try {
17
+ // Test with Kafka disabled
18
+ console.log('1. Testing with Kafka disabled...');
19
+ const configDisabled = { ...$.config, kafka: { ...$.config.kafka, enabled: false } };
20
+ await $.kafka_consumer.init(configDisabled, ['test-project']);
21
+ console.log('✓ Kafka consumer init with disabled config passed');
22
+
23
+ // Test with Kafka enabled but no authentication
24
+ console.log('2. Testing with Kafka enabled (no auth)...');
25
+ const configEnabled = {
26
+ ...$.config,
27
+ kafka: {
28
+ ...$.config.kafka,
29
+ enabled: true,
30
+ sasl: { username: '', password: '' }
31
+ }
32
+ };
33
+ await $.kafka_consumer.init(configEnabled, ['test-project']);
34
+ console.log('✓ Kafka consumer init with enabled config passed');
35
+
36
+ // Test isRunning method
37
+ console.log('3. Testing isRunning method...');
38
+ console.log('Is running:', $.kafka_consumer.isRunning());
39
+ console.log('✓ isRunning method works');
40
+
41
+ console.log('\n✓ All Kafka consumer tests passed!');
42
+ } catch (error) {
43
+ console.error('✗ Kafka consumer test failed:', error.message);
44
+ }
45
+ }
46
+
47
+ async function testConfiguration() {
48
+ console.log('\n=== Testing Configuration ===');
49
+
50
+ console.log('MQTT parseMessages:', $.config.mqtt.parseMessages);
51
+ console.log('Kafka enabled:', $.config.kafka.enabled);
52
+ console.log('Kafka brokers:', $.config.kafka.brokers);
53
+ console.log('Kafka groupId:', $.config.kafka.groupId);
54
+ console.log('Kafka SASL mechanism:', $.config.kafka.sasl.mechanism);
55
+
56
+ console.log('✓ Configuration test passed!');
57
+ }
58
+
59
+ async function runTests() {
60
+ console.log('Starting Kafka Consumer Tests...');
61
+
62
+ await testConfiguration();
63
+ await testKafkaConsumerInit();
64
+
65
+ console.log('\n=== All Tests Completed ===');
66
+ process.exit(0);
67
+ }
68
+
69
+ runTests().catch(error => {
70
+ console.error('Test suite failed:', error);
71
+ process.exit(1);
72
+ });