122 lines
4.0 KiB
Python
122 lines
4.0 KiB
Python
"""
|
|
IoT Producer — Flask app that generates fake IoT sensor events
|
|
and sends them to Kafka (TLS + SASL).
|
|
|
|
Env vars (set by Terraform):
|
|
KAFKA_BROKERS — bootstrap server (host:port)
|
|
KAFKA_TOPIC — topic name
|
|
KAFKA_USERNAME — SASL username
|
|
KAFKA_PASSWORD — SASL password
|
|
PRODUCE_INTERVAL — seconds between events (default 3)
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import random
|
|
import ssl
|
|
import time
|
|
import threading
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from flask import Flask, jsonify
|
|
from kafka import KafkaProducer
|
|
|
|
app = Flask(__name__)
|
|
|
|
# --- Config from env ---
|
|
KAFKA_BROKERS = os.environ["KAFKA_BROKERS"]
|
|
KAFKA_TOPIC = os.environ["KAFKA_TOPIC"]
|
|
KAFKA_USERNAME = os.environ["KAFKA_USERNAME"]
|
|
KAFKA_PASSWORD = os.environ["KAFKA_PASSWORD"]
|
|
PRODUCE_INTERVAL = float(os.environ.get("PRODUCE_INTERVAL", 3))
|
|
|
|
# --- IoT simulation config ---
|
|
SENSORS = [
|
|
{"id": "temp_living", "type": "temperature", "location": "living_room", "unit": "°C", "min": 18.0, "max": 28.0},
|
|
{"id": "temp_bedroom", "type": "temperature", "location": "bedroom", "unit": "°C", "min": 16.0, "max": 26.0},
|
|
{"id": "temp_office", "type": "temperature", "location": "office", "unit": "°C", "min": 19.0, "max": 27.0},
|
|
{"id": "humidity_living","type": "humidity", "location": "living_room", "unit": "%", "min": 30.0, "max": 70.0},
|
|
{"id": "humidity_bed", "type": "humidity", "location": "bedroom", "unit": "%", "min": 35.0, "max": 65.0},
|
|
{"id": "power_kitchen", "type": "power_meter", "location": "kitchen", "unit": "kW", "min": 0.1, "max": 3.5},
|
|
{"id": "power_living", "type": "power_meter", "location": "living_room", "unit": "kW", "min": 0.2, "max": 2.0},
|
|
{"id": "power_office", "type": "power_meter", "location": "office", "unit": "kW", "min": 0.3, "max": 4.0},
|
|
]
|
|
|
|
producer = None
|
|
events_sent = 0
|
|
errors_count = 0
|
|
|
|
|
|
def get_producer():
|
|
global producer
|
|
if producer is None:
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
producer = KafkaProducer(
|
|
bootstrap_servers=KAFKA_BROKERS,
|
|
security_protocol="SASL_SSL",
|
|
sasl_mechanism="PLAIN",
|
|
sasl_plain_username=KAFKA_USERNAME,
|
|
sasl_plain_password=KAFKA_PASSWORD,
|
|
ssl_context=ctx,
|
|
value_serializer=lambda v: json.dumps(v).encode("utf-8"),
|
|
key_serializer=lambda k: k.encode("utf-8") if k else None,
|
|
)
|
|
return producer
|
|
|
|
|
|
def generate_event():
|
|
sensor = random.choice(SENSORS)
|
|
value = round(random.uniform(sensor["min"], sensor["max"]), 2)
|
|
return {
|
|
"event_id": str(uuid.uuid4()),
|
|
"sensor_id": sensor["id"],
|
|
"device_type": sensor["type"],
|
|
"location": sensor["location"],
|
|
"value": value,
|
|
"unit": sensor["unit"],
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
|
|
def produce_loop():
|
|
global events_sent, errors_count
|
|
while True:
|
|
try:
|
|
event = generate_event()
|
|
p = get_producer()
|
|
future = p.send(KAFKA_TOPIC, key=event["sensor_id"], value=event)
|
|
future.get(timeout=5)
|
|
events_sent += 1
|
|
print(f"[PRODUCER] Sent: {event['sensor_id']} = {event['value']}{event['unit']} ({event['location']})")
|
|
except Exception as e:
|
|
errors_count += 1
|
|
print(f"[PRODUCER] Error: {e}")
|
|
time.sleep(PRODUCE_INTERVAL)
|
|
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return jsonify({
|
|
"service": "iot-producer",
|
|
"status": "running",
|
|
"kafka_topic": KAFKA_TOPIC,
|
|
"events_sent": events_sent,
|
|
"errors": errors_count,
|
|
"interval": PRODUCE_INTERVAL,
|
|
"sensors": len(SENSORS),
|
|
})
|
|
|
|
|
|
@app.route("/health")
|
|
def health():
|
|
return jsonify({"status": "ok", "events_sent": events_sent})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
threading.Thread(target=produce_loop, daemon=True).start()
|
|
port = int(os.environ.get("PORT", 5000))
|
|
app.run(host="0.0.0.0", port=port)
|