Industrial Data Collection with AWS IoT Core: From ZMA Devices to Cloud

Amazeng Technical Team
12 min read
AWS IoT CoreMQTTZMAGDTModbus GatewayCloud IoTIndustrial IoT

Introduction

Moving industrial data to the cloud is the cornerstone of Industry 4.0 and predictive maintenance processes. However, data collected from sensors in the field typically communicate using traditional protocols like Modbus RTU or Modbus TCP. An IoT Gateway is required to transfer this data to cloud platforms like AWS IoT Core.

In this article, we examine in detail how data collected via Modbus from our ZMA Data Acquisition and GDT Digital Transmitter devices is transferred to AWS IoT Core through an IoT Gateway.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    Factory / Field                          │
│                                                             │
│  ┌─────────┐    Modbus RTU/TCP    ┌──────────────┐        │
│  │ ZMA-4   │◄────────────────────►│              │        │
│  │ Device  │                      │  IoT Gateway │        │
│  └─────────┘                      │  (Linux Box) │        │
│                                   │              │        │
│  ┌─────────┐    Modbus RTU/TCP    │  - Modbus    │        │
│  │ GDT     │◄────────────────────►│    Client    │        │
│  │ Trans.  │                      │  - MQTT      │        │
│  └─────────┘                      │    Client    │        │
│                                   │  - TLS/SSL   │        │
│                                   └───────┬──────┘        │
└───────────────────────────────────────────┼───────────────┘
                                            │
                                            │ MQTT over TLS
                                            │ Port 8883
                                            ▼
                                   ┌────────────────┐
                                   │   AWS Cloud    │
                                   │                │
                                   │  ┌──────────┐  │
                                   │  │ IoT Core │  │
                                   │  └────┬─────┘  │
                                   │       │        │
                                   │  ┌────▼─────┐  │
                                   │  │Timestream│  │
                                   │  └──────────┘  │
                                   └────────────────┘

Important Note: IoT Gateway Requirement

ZMA and GDT devices communicate via Modbus RTU (RS485) and Modbus TCP (Ethernet) protocols. AWS IoT Core uses the MQTT protocol. An IoT Gateway device is needed to perform protocol conversion between these two worlds.

Gateway Options

  1. Raspberry Pi / Jetson Nano: Linux-based, custom script with Python/Node.js
  2. Industrial IoT Gateways: Ready solutions like Moxa, Advantech, Siemens
  3. Our HMI Panel: Can function as both HMI and gateway on Embedded Linux
  4. PLC + OPC UA: Cloud connection via PLC's OPC UA server

In this article, we'll examine a custom gateway solution with Raspberry Pi 4 + Python, but the same logic applies to all platforms.

AWS IoT Core Setup

1. Create Thing (Device)

AWS Console → IoT Core → Manage → Things → Create

{
  "thingName": "amazeng-factory-gateway-01",
  "thingTypeName": "ModbusGateway",
  "attributes": {
    "location": "Istanbul-Factory-A",
    "firmware": "1.0.0"
  }
}

2. Create and Download Certificate

Each IoT device connects to AWS with X.509 certificate:

# Create certificate with AWS CLI
aws iot create-keys-and-certificate \
  --set-as-active \
  --certificate-pem-outfile gateway-cert.pem \
  --public-key-outfile gateway-public.key \
  --private-key-outfile gateway-private.key

Important: Store gateway-private.key file securely, cannot be downloaded again!

IoT Gateway Software (Python)

Gateway Application

gateway.py

#!/usr/bin/env python3

import json
import time
import struct
from pymodbus.client.sync import ModbusTcpClient
from awscrt import io, mqtt
from awsiot import mqtt_connection_builder

class ModbusToMQTTGateway:
    def __init__(self, config_file):
        with open(config_file, 'r') as f:
            self.config = json.load(f)

        self.modbus_clients = {}
        self.mqtt_connection = None

        # Create Modbus clients
        for device in self.config['modbus_devices']:
            if device['type'] == 'tcp':
                client = ModbusTcpClient(device['host'], port=device['port'])

            client.connect()
            self.modbus_clients[device['name']] = {
                'client': client,
                'config': device
            }

        # AWS IoT MQTT connection
        self._setup_mqtt()

    def _setup_mqtt(self):
        """Setup AWS IoT Core MQTT connection"""
        aws_config = self.config['aws_iot']
        cert_path = aws_config['cert_path']

        # Secure connection with TLS
        self.mqtt_connection = mqtt_connection_builder.mtls_from_path(
            endpoint=aws_config['endpoint'],
            port=aws_config['port'],
            cert_filepath=f"{cert_path}gateway-cert.pem",
            pri_key_filepath=f"{cert_path}gateway-private.key",
            ca_filepath=f"{cert_path}AmazonRootCA1.pem",
            client_id=aws_config['thing_name'],
            clean_session=False,
            keep_alive_secs=30
        )

        # Connect
        connect_future = self.mqtt_connection.connect()
        connect_future.result()
        print(f"✓ Connected to AWS IoT Core: {aws_config['endpoint']}")

    def read_modbus_device(self, device_name):
        """Read all registers from a Modbus device"""
        device = self.modbus_clients[device_name]
        client = device['client']
        config = device['config']

        data = {
            'device_name': device_name,
            'timestamp': int(time.time() * 1000),
            'values': {}
        }

        for register in config['registers']:
            try:
                # Read holding register
                result = client.read_holding_registers(
                    address=register['address'],
                    count=register['count'],
                    unit=config['unit_id']
                )

                if not result.isError():
                    # Float conversion (IEEE 754)
                    if register['type'] == 'float' and register['count'] == 2:
                        raw = (result.registers[0] << 16) | result.registers[1]
                        value = struct.unpack('f', struct.pack('I', raw))[0]

                    data['values'][register['name']] = round(value, 3)

            except Exception as e:
                print(f"✗ Error: {device_name} - {e}")
                data['values'][register['name']] = None

        return data

    def publish_to_aws(self, device_name, data):
        """Publish data to AWS IoT Core"""
        topic = f"{self.config['aws_iot']['topic_prefix']}/{device_name}/data"
        payload = json.dumps(data)

        self.mqtt_connection.publish(
            topic=topic,
            payload=payload,
            qos=mqtt.QoS.AT_LEAST_ONCE
        )

        print(f"→ Published to {topic}: {payload}")

    def run(self):
        """Main loop: Read from Modbus, send to MQTT"""
        interval = self.config['polling_interval']

        print(f"Gateway started. Polling interval: {interval}s")

        try:
            while True:
                for device_name in self.modbus_clients.keys():
                    # Read from Modbus
                    data = self.read_modbus_device(device_name)

                    # Send to AWS
                    self.publish_to_aws(device_name, data)

                time.sleep(interval)

        except KeyboardInterrupt:
            print("\nStopping gateway...")
            self.shutdown()

if __name__ == "__main__":
    gateway = ModbusToMQTTGateway('/etc/iot-gateway/config.json')
    gateway.run()

Real-Time Alarm Scenario

Lambda Function for Threshold Control

alarm_handler.py

import json
import boto3

sns = boto3.client('sns')

def lambda_handler(event, context):
    # Data from IoT Core
    device = event['device_name']
    weight = event['values'].get('net_weight', 0)

    # Threshold check
    if weight > 5000:  # Over 5 tons
        message = f"ALARM: {device} - Weight limit exceeded: {weight} kg"

        sns.publish(
            TopicArn='arn:aws:sns:eu-west-1:123456789:factory-alarms',
            Subject='Tank Capacity Alarm',
            Message=message
        )

        return {'statusCode': 200, 'body': 'Alarm sent'}

    return {'statusCode': 200, 'body': 'Normal'}

Cost Analysis

AWS IoT Core Pricing (eu-west-1)

ComponentPriceMonthly Cost*
Connectivity$0.08 / million connection-minutes$3.45 (2 devices, 24/7)
Messaging$1.00 / million messages$2.59 (1s polling, 2 devices)
Rule Engine$0.15 / million rules$0.38
Timestream Write$0.50 / million writes$1.30
Timestream Storage$0.03 / GB-hour$21.60 (30GB)
TOTAL~$29/month

*2 devices, 1 second polling, 30 days retention

Conclusion

Our ZMA and GDT products can easily connect to AWS IoT Core via IoT Gateway thanks to Modbus RTU/TCP support. This enables:

✅ Real-time data monitoring (Grafana/QuickSight)
✅ Long-term data storage (Timestream)
✅ Predictive maintenance (AI/ML models)
✅ Remote alarms and notifications

For more information about our Cloud & IoT Data Collection Solutions, contact us.