mqtt-devices-parser 1.0.19 → 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,9 @@
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
+
3
7
  ## 1.0.19
4
8
  Removes adding project on launching process
5
9
  models/models:
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,
@@ -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
 
@@ -181,8 +188,11 @@ function mqtt_connect(){
181
188
  });
182
189
 
183
190
  $.mqtt_client.on("message", (topic, payload, packet) => {
184
- // payload is Buffer
185
- $.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
+ }
186
196
  });
187
197
 
188
198
  $.mqtt_client.on("reconnect",()=>{
@@ -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.19",
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
+ });