smartfleet-simulator 0.1.0__tar.gz

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.
@@ -0,0 +1,4 @@
1
+ MIT License
2
+ Copyright (c) 2025 Satvik T M
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software to deal in the Software without restriction.
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: smartfleet-simulator
3
+ Version: 0.1.0
4
+ Summary: Reusable library for simulating vehicle fleet telemetry
5
+ Author: Satvik T M
6
+ Author-email: satvikw007@gmail.com
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: license-file
19
+ Dynamic: requires-python
20
+ Dynamic: summary
21
+
22
+ # fleet-simulator-satvik25159712
23
+
24
+ Reusable Python library for simulating delivery vehicle fleet telemetry.
25
+ Generates GPS, speed, fuel and engine temperature readings and posts
26
+ them to AWS API Gateway.
27
+
28
+ ## Install
29
+ pip install fleet-simulator-satvik25159712
30
+
31
+ ## Quick start
32
+ from fleet_simulator_pkg import FleetSimulator
33
+ sim = FleetSimulator()
34
+ sim.run(dry_run=True, max_ticks=5)
@@ -0,0 +1,13 @@
1
+ # fleet-simulator-satvik25159712
2
+
3
+ Reusable Python library for simulating delivery vehicle fleet telemetry.
4
+ Generates GPS, speed, fuel and engine temperature readings and posts
5
+ them to AWS API Gateway.
6
+
7
+ ## Install
8
+ pip install fleet-simulator-satvik25159712
9
+
10
+ ## Quick start
11
+ from fleet_simulator_pkg import FleetSimulator
12
+ sim = FleetSimulator()
13
+ sim.run(dry_run=True, max_ticks=5)
@@ -0,0 +1,6 @@
1
+ from .vehicle import Vehicle
2
+ from .simulator import FleetSimulator
3
+ from .telemetry import TelemetryReading
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["Vehicle", "FleetSimulator", "TelemetryReading"]
@@ -0,0 +1,113 @@
1
+ """
2
+ simulator.py
3
+ Manages a fleet of Vehicle objects and drives the telemetry loop.
4
+ Sends TelemetryReading objects to AWS API Gateway via HTTP POST.
5
+ """
6
+ import json
7
+ import time
8
+ import urllib.request
9
+ import urllib.error
10
+ from .vehicle import Vehicle
11
+ from .telemetry import TelemetryReading
12
+
13
+ DEFAULT_FLEET = [
14
+ {"vehicle_id": "VAN-001", "driver_name": "Raghu Bandi", "route_id": "ROUTE-D1"},
15
+ {"vehicle_id": "VAN-002", "driver_name": "Manju Kolar", "route_id": "ROUTE-D2"},
16
+ {"vehicle_id": "VAN-003", "driver_name": "Shubham Dube", "route_id": "ROUTE-D3"},
17
+ {"vehicle_id": "VAN-004", "driver_name": "Pranith Raj", "route_id": "ROUTE-D4"},
18
+ {"vehicle_id": "VAN-005", "driver_name": "Nikhil Bandi", "route_id": "ROUTE-D5"},
19
+ ]
20
+
21
+
22
+ class FleetSimulator:
23
+ """
24
+ Manages a fleet of vehicles and drives a continuous telemetry loop.
25
+ """
26
+
27
+ def __init__(self, fleet_config=None, api_url=None, api_key=None):
28
+ """
29
+ Initialise with a fleet and optional API target.
30
+
31
+ Args:
32
+ fleet_config (list): List of dicts with vehicle_id,
33
+ driver_name, route_id. Uses DEFAULT_FLEET if None.
34
+ api_url (str): API Gateway endpoint URL.
35
+ api_key (str): Optional x-api-key header value.
36
+ """
37
+ config = fleet_config if fleet_config is not None else DEFAULT_FLEET
38
+ self.vehicles = [
39
+ Vehicle(v["vehicle_id"], v["driver_name"], v["route_id"])
40
+ for v in config
41
+ ]
42
+ self.api_url = api_url
43
+ self.api_key = api_key
44
+
45
+ def generate_readings(self):
46
+ """Advance all vehicles one tick. Returns list of TelemetryReadings."""
47
+ readings = []
48
+ for vehicle in self.vehicles:
49
+ vehicle.update_state()
50
+ readings.append(TelemetryReading.from_vehicle(vehicle))
51
+ return readings
52
+
53
+ def send_reading(self, reading):
54
+ """
55
+ POST a single TelemetryReading to API Gateway.
56
+ Returns True on HTTP 200/201/202, False on failure.
57
+ """
58
+ if not self.api_url:
59
+ return False
60
+ payload = json.dumps(reading.to_dict()).encode("utf-8")
61
+ headers = {"Content-Type": "application/json"}
62
+ if self.api_key:
63
+ headers["x-api-key"] = self.api_key
64
+ req = urllib.request.Request(
65
+ self.api_url, data=payload, headers=headers, method="POST"
66
+ )
67
+ try:
68
+ with urllib.request.urlopen(req, timeout=10) as resp:
69
+ return resp.status in (200, 201, 202)
70
+ except urllib.error.URLError as e:
71
+ print(f" [ERROR] {reading.vehicle_id}: {e}")
72
+ return False
73
+
74
+ def run(self, interval_seconds=5.0, max_ticks=None,
75
+ dry_run=False, on_reading=None):
76
+ """
77
+ Start the simulation loop.
78
+
79
+ Args:
80
+ interval_seconds (float): Pause between ticks.
81
+ max_ticks (int): Stop after this many ticks. None = run forever.
82
+ dry_run (bool): Print to console instead of sending to AWS.
83
+ on_reading (callable): Optional callback per reading.
84
+ """
85
+ tick = 0
86
+ print(f"[FleetSimulator] Starting — {len(self.vehicles)} vehicles, "
87
+ f"interval={interval_seconds}s, dry_run={dry_run}")
88
+ try:
89
+ while True:
90
+ tick += 1
91
+ readings = self.generate_readings()
92
+ for reading in readings:
93
+ if dry_run or not self.api_url:
94
+ print(f" [tick {tick:04d}] {reading.vehicle_id} | "
95
+ f"speed={reading.speed_kmh:.1f}km/h | "
96
+ f"fuel={reading.fuel_percent:.1f}% | "
97
+ f"temp={reading.engine_temp_c:.1f}C")
98
+ else:
99
+ ok = self.send_reading(reading)
100
+ print(f" [tick {tick:04d}] {reading.vehicle_id} → "
101
+ f"{'OK' if ok else 'FAIL'}")
102
+ if on_reading:
103
+ on_reading(reading)
104
+ if max_ticks and tick >= max_ticks:
105
+ print(f"[FleetSimulator] Done — {max_ticks} ticks.")
106
+ break
107
+ time.sleep(interval_seconds)
108
+ except KeyboardInterrupt:
109
+ print("\n[FleetSimulator] Stopped.")
110
+
111
+ def get_fleet_summary(self):
112
+ """Return current state of all vehicles as a list of dicts."""
113
+ return [v.to_dict() for v in self.vehicles]
@@ -0,0 +1,53 @@
1
+ """
2
+ telemetry.py
3
+ A timestamped snapshot of one vehicle at one point in time.
4
+ This is the payload sent to AWS API Gateway.
5
+ """
6
+ import datetime
7
+
8
+
9
+ class TelemetryReading:
10
+ """
11
+ Immutable snapshot of a vehicle's state at a single moment.
12
+ """
13
+
14
+ def __init__(self, vehicle_id, driver_name, route_id,
15
+ lat, lon, speed_kmh, fuel_percent, engine_temp_c):
16
+ self.vehicle_id = vehicle_id
17
+ self.driver_name = driver_name
18
+ self.route_id = route_id
19
+ self.timestamp = datetime.datetime.now(
20
+ datetime.timezone.utc).isoformat()
21
+ self.lat = lat
22
+ self.lon = lon
23
+ self.speed_kmh = speed_kmh
24
+ self.fuel_percent = fuel_percent
25
+ self.engine_temp_c = engine_temp_c
26
+
27
+ @classmethod
28
+ def from_vehicle(cls, vehicle):
29
+ """Create a reading directly from a Vehicle instance."""
30
+ return cls(
31
+ vehicle_id = vehicle.vehicle_id,
32
+ driver_name = vehicle.driver_name,
33
+ route_id = vehicle.route_id,
34
+ lat = vehicle.lat,
35
+ lon = vehicle.lon,
36
+ speed_kmh = vehicle.speed_kmh,
37
+ fuel_percent = vehicle.fuel_percent,
38
+ engine_temp_c = vehicle.engine_temp_c,
39
+ )
40
+
41
+ def to_dict(self):
42
+ """Serialise to a flat dict for JSON encoding."""
43
+ return {
44
+ "vehicle_id": self.vehicle_id,
45
+ "driver_name": self.driver_name,
46
+ "route_id": self.route_id,
47
+ "timestamp": self.timestamp,
48
+ "lat": self.lat,
49
+ "lon": self.lon,
50
+ "speed_kmh": self.speed_kmh,
51
+ "fuel_percent": self.fuel_percent,
52
+ "engine_temp_c": self.engine_temp_c,
53
+ }
@@ -0,0 +1,68 @@
1
+ """
2
+ vehicle.py
3
+ This file represents a single delivery vehicle with persistent state.
4
+ Each vehicle maintains its own position, speed, fuel and engine
5
+ temperature across telemetry ticks.
6
+ """
7
+ import random
8
+
9
+ DEFAULT_LAT = 53.3498
10
+ DEFAULT_LON = -6.2603
11
+
12
+
13
+ class Vehicle:
14
+ """
15
+ A single fleet vehicle with state that updates each tick.
16
+ """
17
+
18
+ def __init__(self, vehicle_id, driver_name, route_id,
19
+ start_lat=DEFAULT_LAT, start_lon=DEFAULT_LON):
20
+ self.vehicle_id = vehicle_id
21
+ self.driver_name = driver_name
22
+ self.route_id = route_id
23
+ self.lat = start_lat
24
+ self.lon = start_lon
25
+ self.speed_kmh = 0.0
26
+ self.fuel_percent = random.uniform(60.0, 100.0)
27
+ self.engine_temp_c = random.uniform(75.0, 95.0)
28
+ # Small random fault probability so some vehicles generate alerts
29
+ self._fault_probability = random.uniform(0.0, 0.15)
30
+
31
+ def update_state(self):
32
+ """
33
+ Advance vehicle state by one telemetry tick.
34
+ Simulates realistic GPS drift, speed changes,
35
+ fuel drain and occasional engine temperature spikes.
36
+ """
37
+ # Simulates a GPS movement
38
+ self.lat += random.uniform(-0.005, 0.005)
39
+ self.lon += random.uniform(-0.005, 0.005)
40
+
41
+ # speed transitions
42
+ speed_delta = random.uniform(-15, 15)
43
+ self.speed_kmh = max(0.0, min(110.0, self.speed_kmh + speed_delta))
44
+
45
+ # Fuel drains proportionally to speed
46
+ fuel_drain = (self.speed_kmh / 100.0) * random.uniform(0.05, 0.2)
47
+ self.fuel_percent = max(5.0, self.fuel_percent - fuel_drain)
48
+
49
+ # Engine temp — occasionally made to spike on faulty vehicles
50
+ if random.random() < self._fault_probability:
51
+ self.engine_temp_c = random.uniform(105.0, 120.0)
52
+ else:
53
+ temp_delta = random.uniform(-2, 2)
54
+ self.engine_temp_c = max(75.0, min(95.0,
55
+ self.engine_temp_c + temp_delta))
56
+
57
+ def to_dict(self):
58
+ """Serialise current state to a dict for JSON encoding."""
59
+ return {
60
+ "vehicle_id": self.vehicle_id,
61
+ "driver_name": self.driver_name,
62
+ "route_id": self.route_id,
63
+ "lat": round(self.lat, 6),
64
+ "lon": round(self.lon, 6),
65
+ "speed_kmh": round(self.speed_kmh, 2),
66
+ "fuel_percent": round(self.fuel_percent, 2),
67
+ "engine_temp_c": round(self.engine_temp_c, 2),
68
+ }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,22 @@
1
+ import setuptools
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setuptools.setup(
7
+ name="smartfleet-simulator",
8
+ version="0.1.0",
9
+ author="Satvik T M",
10
+ author_email="satvikw007@gmail.com",
11
+ description="Reusable library for simulating vehicle fleet telemetry",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ packages=setuptools.find_packages(),
15
+ install_requires=[],
16
+ classifiers=[
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ],
21
+ python_requires=">=3.8",
22
+ )
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: smartfleet-simulator
3
+ Version: 0.1.0
4
+ Summary: Reusable library for simulating vehicle fleet telemetry
5
+ Author: Satvik T M
6
+ Author-email: satvikw007@gmail.com
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: license-file
19
+ Dynamic: requires-python
20
+ Dynamic: summary
21
+
22
+ # fleet-simulator-satvik25159712
23
+
24
+ Reusable Python library for simulating delivery vehicle fleet telemetry.
25
+ Generates GPS, speed, fuel and engine temperature readings and posts
26
+ them to AWS API Gateway.
27
+
28
+ ## Install
29
+ pip install fleet-simulator-satvik25159712
30
+
31
+ ## Quick start
32
+ from fleet_simulator_pkg import FleetSimulator
33
+ sim = FleetSimulator()
34
+ sim.run(dry_run=True, max_ticks=5)
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ fleet_simulator_pkg/__init__.py
5
+ fleet_simulator_pkg/simulator.py
6
+ fleet_simulator_pkg/telemetry.py
7
+ fleet_simulator_pkg/vehicle.py
8
+ smartfleet_simulator.egg-info/PKG-INFO
9
+ smartfleet_simulator.egg-info/SOURCES.txt
10
+ smartfleet_simulator.egg-info/dependency_links.txt
11
+ smartfleet_simulator.egg-info/top_level.txt
12
+ tests/test_simulator.py
@@ -0,0 +1 @@
1
+ fleet_simulator_pkg
@@ -0,0 +1,85 @@
1
+ """
2
+ test_simulator.py
3
+ Unit tests for fleet_simulator_pkg.
4
+ Run with: python3 -m pytest tests/ -v
5
+ """
6
+ from fleet_simulator_pkg.vehicle import Vehicle
7
+ from fleet_simulator_pkg.telemetry import TelemetryReading
8
+ from fleet_simulator_pkg.simulator import FleetSimulator
9
+
10
+
11
+ class TestVehicle:
12
+
13
+ def test_initialises_with_correct_id(self):
14
+ v = Vehicle("VAN-001", "Raghu Bandi", "ROUTE-D1")
15
+ assert v.vehicle_id == "VAN-001"
16
+
17
+ def test_fuel_starts_within_range(self):
18
+ v = Vehicle("VAN-001", "Raghu Bandi", "ROUTE-D1")
19
+ assert 60.0 <= v.fuel_percent <= 100.0
20
+
21
+ def test_fuel_drains_after_updates(self):
22
+ v = Vehicle("VAN-001", "Raghu Bandi", "ROUTE-D1")
23
+ initial = v.fuel_percent
24
+ for _ in range(20):
25
+ v.update_state()
26
+ assert v.fuel_percent <= initial
27
+
28
+ def test_speed_stays_within_bounds(self):
29
+ v = Vehicle("VAN-001", "Raghu Bandi", "ROUTE-D1")
30
+ for _ in range(50):
31
+ v.update_state()
32
+ assert 0.0 <= v.speed_kmh <= 110.0
33
+
34
+ def test_to_dict_has_all_keys(self):
35
+ v = Vehicle("VAN-001", "Raghu Bandi", "ROUTE-D1")
36
+ keys = set(v.to_dict().keys())
37
+ assert keys == {"vehicle_id", "driver_name", "route_id",
38
+ "lat", "lon", "speed_kmh",
39
+ "fuel_percent", "engine_temp_c"}
40
+
41
+
42
+ class TestTelemetryReading:
43
+
44
+ def test_from_vehicle_captures_id(self):
45
+ v = Vehicle("VAN-002", "Manju Kolar", "ROUTE-D2")
46
+ r = TelemetryReading.from_vehicle(v)
47
+ assert r.vehicle_id == "VAN-002"
48
+
49
+ def test_timestamp_is_utc_iso(self):
50
+ v = Vehicle("VAN-002", "Manju Kolar", "ROUTE-D2")
51
+ r = TelemetryReading.from_vehicle(v)
52
+ assert "T" in r.timestamp
53
+ assert "+" in r.timestamp or "Z" in r.timestamp
54
+
55
+ def test_to_dict_includes_timestamp(self):
56
+ v = Vehicle("VAN-003", "Shubham Dube", "ROUTE-D3")
57
+ d = TelemetryReading.from_vehicle(v).to_dict()
58
+ assert "timestamp" in d
59
+
60
+
61
+ class TestFleetSimulator:
62
+
63
+ def test_default_fleet_has_five_vehicles(self):
64
+ sim = FleetSimulator()
65
+ assert len(sim.vehicles) == 5
66
+
67
+ def test_custom_fleet_config(self):
68
+ sim = FleetSimulator(fleet_config=[
69
+ {"vehicle_id": "T-01", "driver_name": "Arun", "route_id": "R1"},
70
+ ])
71
+ assert len(sim.vehicles) == 1
72
+ assert sim.vehicles[0].vehicle_id == "T-01"
73
+
74
+ def test_generate_readings_count(self):
75
+ sim = FleetSimulator()
76
+ assert len(sim.generate_readings()) == 5
77
+
78
+ def test_callback_fires_for_each_reading(self):
79
+ sim = FleetSimulator()
80
+ collected = []
81
+ sim.run(interval_seconds=0, max_ticks=2,
82
+ dry_run=True, on_reading=lambda r: collected.append(r))
83
+ assert len(collected) == 10
84
+ # 5 vehicles x 2 ticks
85
+