-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathiot_sim_main.py
More file actions
197 lines (167 loc) · 8.14 KB
/
iot_sim_main.py
File metadata and controls
197 lines (167 loc) · 8.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import argparse
import time
import json # For parsing device_profiles if given as JSON string
import uuid
from iot_simulator.generators import generate_sensor_data, generate_timestamp, DEVICE_STATES
from iot_simulator.publishers import format_payload, print_to_console, send_http_post
def parse_device_profiles(profiles_str_list):
"""
Parses device profile strings.
Each string can be "device_id:sensor1,sensor2" or just "sensor1,sensor2" (auto-gen ID).
Or, it can be a JSON string representing a list of more complex profiles.
"""
profiles = []
if not profiles_str_list:
return profiles
# Check if the input is a single string that might be JSON
if len(profiles_str_list) == 1 and (profiles_str_list[0].startswith('[') or profiles_str_list[0].startswith('{')):
try:
parsed_json = json.loads(profiles_str_list[0])
if isinstance(parsed_json, list):
# Expecting list of {"id": "dev1", "sensors": ["temp", "hum"]}
for p_item in parsed_json:
if isinstance(p_item, dict) and "id" in p_item and "sensors" in p_item:
profiles.append({"id": p_item["id"], "sensors": p_item["sensors"]})
else:
print(f"Warning: Invalid JSON profile item format: {p_item}. Skipping.")
return profiles
elif isinstance(parsed_json, dict): # Single profile as JSON object
if "id" in parsed_json and "sensors" in parsed_json:
profiles.append({"id": parsed_json["id"], "sensors": parsed_json["sensors"]})
return profiles
else:
print(f"Warning: Invalid JSON profile object format: {parsed_json}. Skipping.")
return [] # Or handle as error
except json.JSONDecodeError as e:
print(f"Warning: Could not parse profile string as JSON '{profiles_str_list[0]}': {e}. Proceeding with string parsing.")
# Fall through to string parsing if JSON attempt fails for a single ambiguous string
# String parsing for "device_id:sensor1,sensor2" or "sensor1,sensor2"
for profile_str in profiles_str_list:
parts = profile_str.split(':', 1)
device_id = ""
sensors_str = ""
if len(parts) == 2:
device_id = parts[0].strip()
sensors_str = parts[1].strip()
elif len(parts) == 1:
sensors_str = parts[0].strip()
# No device_id provided, will auto-generate
else:
print(f"Warning: Invalid profile string format '{profile_str}'. Skipping.")
continue
if not device_id:
device_id = f"sim-{uuid.uuid4().hex[:8]}"
print(f"Auto-generated device ID: {device_id} for sensors: {sensors_str}")
sensor_types = [s.strip() for s in sensors_str.split(',') if s.strip()]
if not sensor_types:
print(f"Warning: No sensor types specified for device ID '{device_id}' in profile '{profile_str}'. Skipping.")
continue
profiles.append({"id": device_id, "sensors": sensor_types})
return profiles
def main_loop(profiles, interval_seconds, num_messages, output_target, http_url=None):
"""
Main simulation loop.
"""
if not profiles:
print("No valid device profiles configured. Exiting.")
return
print(f"\nStarting IoT simulation...")
print(f"Device Profiles: {profiles}")
print(f"Interval: {interval_seconds}s")
print(f"Messages per run (per profile): {num_messages if num_messages > 0 else 'Infinite'}")
print(f"Output Target: {output_target}")
if output_target == 'http' and http_url:
print(f"HTTP Target URL: {http_url}")
# Reset global device states for a fresh run if needed, or manage them per profile run.
# For simplicity, DEVICE_STATES in generators.py is global.
# If we want truly independent runs for counters/GPS per main_loop call, clear it here.
# DEVICE_STATES.clear() # Uncomment if each run of main_loop should reset all device states
try:
msg_count = 0
while True:
if num_messages > 0 and msg_count >= num_messages:
print(f"\nReached target message count ({num_messages}). Simulation finished for this run.")
break
current_ts = generate_timestamp()
for profile in profiles:
device_id = profile["id"]
sensor_types = profile["sensors"]
# This list will hold data from multiple sensors for this device for this timestamp
all_sensor_readings_for_device = []
for sensor_type in sensor_types:
# generate_sensor_data from generators.py manages state per device_id
reading = generate_sensor_data(device_id, sensor_type)
if reading:
all_sensor_readings_for_device.append(reading)
if not all_sensor_readings_for_device:
print(f"Warning: No sensor data generated for device {device_id} at {current_ts}. Skipping.")
continue
# Format the payload with all readings for this device
payload = format_payload(device_id, current_ts, all_sensor_readings_for_device)
if output_target == 'console':
print_to_console(payload)
elif output_target == 'http':
if http_url:
send_http_post(http_url, payload)
else:
print("Error: HTTP output specified but no URL provided. Skipping send.")
msg_count += 1
if num_messages == 0 or msg_count < num_messages : # Only sleep if not the last message of a finite run
print(f"--- Sent message batch #{msg_count}. Waiting {interval_seconds}s... ---")
time.sleep(interval_seconds)
except KeyboardInterrupt:
print("\nSimulation stopped by user (Ctrl+C).")
except Exception as e:
print(f"\nAn unexpected error occurred during simulation: {e}")
finally:
print("IoT Simulation ended.")
def main():
parser = argparse.ArgumentParser(description="Generic IoT Data Simulator.")
parser.add_argument(
"-p", "--profiles",
nargs='+',
required=True,
help='Device profiles. Each profile as "device_id:sensor1,sensor2,..." or "sensor1,sensor2" (ID auto-generated). '
'Alternatively, a single argument which is a JSON string: \'[{"id":"dev1","sensors":["temp","hum"]}]\' '
'Supported sensors: temperature, humidity, gps, status, counter.'
)
parser.add_argument(
"-i", "--interval",
type=float,
default=5.0,
help="Interval in seconds between sending messages (default: 5.0s)."
)
parser.add_argument(
"-n", "--num_messages",
type=int,
default=10,
help="Number of messages to send per simulation run (0 for infinite) (default: 10)."
)
parser.add_argument(
"-o", "--output",
choices=['console', 'http'],
default='console',
help="Output target: 'console' or 'http' (default: console)."
)
parser.add_argument(
"--http_url",
help="Target URL for HTTP POST output (required if --output=http)."
)
args = parser.parse_args()
if args.output == 'http' and not args.http_url:
parser.error("--http_url is required when --output is 'http'.")
parsed_profiles = parse_device_profiles(args.profiles)
if not parsed_profiles:
print("Error: No valid device profiles could be parsed. Please check your --profiles argument.")
print("Examples: --profiles \"myDevice:temperature,humidity\" \"another:gps,counter\"")
print(" or: --profiles '[{\"id\":\"dev1\",\"sensors\":[\"temperature\",\"status\"]}, {\"id\":\"dev2\",\"sensors\":[\"gps\"]}]'")
return
main_loop(
profiles=parsed_profiles,
interval_seconds=args.interval,
num_messages=args.num_messages,
output_target=args.output,
http_url=args.http_url
)
if __name__ == "__main__":
main()