iotlabkit 1.0.0__tar.gz → 1.0.1__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.
Files changed (52) hide show
  1. {iotlabkit-1.0.0/src/iotlabkit.egg-info → iotlabkit-1.0.1}/PKG-INFO +1 -1
  2. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/pyproject.toml +1 -1
  3. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/__init__.py +1 -1
  4. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/cli.py +12 -12
  5. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/doctor.py +6 -6
  6. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/generator.py +4 -4
  7. iotlabkit-1.0.1/src/iotlabkit/templates/program_05.py +209 -0
  8. iotlabkit-1.0.1/src/iotlabkit/templates/program_06.py +203 -0
  9. iotlabkit-1.0.1/src/iotlabkit/templates/program_07.py +117 -0
  10. iotlabkit-1.0.1/src/iotlabkit/templates/program_08.py +129 -0
  11. iotlabkit-1.0.1/src/iotlabkit/templates/program_09.py +161 -0
  12. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/templates/program_10.py +45 -90
  13. iotlabkit-1.0.1/src/iotlabkit/templates/program_11.py +188 -0
  14. iotlabkit-1.0.1/src/iotlabkit/templates/program_12.py +164 -0
  15. iotlabkit-1.0.1/src/iotlabkit/templates/program_13.py +145 -0
  16. iotlabkit-1.0.1/src/iotlabkit/templates/program_14.py +141 -0
  17. iotlabkit-1.0.1/src/iotlabkit/templates/program_15.py +192 -0
  18. iotlabkit-1.0.1/src/iotlabkit/templates/program_16.py +193 -0
  19. iotlabkit-1.0.1/src/iotlabkit/templates/program_17.py +203 -0
  20. {iotlabkit-1.0.0 → iotlabkit-1.0.1/src/iotlabkit.egg-info}/PKG-INFO +1 -1
  21. iotlabkit-1.0.0/src/iotlabkit/templates/program_05.py +0 -436
  22. iotlabkit-1.0.0/src/iotlabkit/templates/program_06.py +0 -404
  23. iotlabkit-1.0.0/src/iotlabkit/templates/program_07.py +0 -249
  24. iotlabkit-1.0.0/src/iotlabkit/templates/program_08.py +0 -278
  25. iotlabkit-1.0.0/src/iotlabkit/templates/program_09.py +0 -329
  26. iotlabkit-1.0.0/src/iotlabkit/templates/program_11.py +0 -347
  27. iotlabkit-1.0.0/src/iotlabkit/templates/program_12.py +0 -317
  28. iotlabkit-1.0.0/src/iotlabkit/templates/program_13.py +0 -272
  29. iotlabkit-1.0.0/src/iotlabkit/templates/program_14.py +0 -287
  30. iotlabkit-1.0.0/src/iotlabkit/templates/program_15.py +0 -411
  31. iotlabkit-1.0.0/src/iotlabkit/templates/program_16.py +0 -382
  32. iotlabkit-1.0.0/src/iotlabkit/templates/program_17.py +0 -422
  33. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/LICENSE +0 -0
  34. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/README.md +0 -0
  35. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/setup.cfg +0 -0
  36. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/coap_util.py +0 -0
  37. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/data/beijing_air/beijing_sample.csv +0 -0
  38. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/data/india_air/city_day_sample.csv +0 -0
  39. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/data/occupancy/datatraining.txt +0 -0
  40. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit/registry.py +0 -0
  41. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit.egg-info/SOURCES.txt +0 -0
  42. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit.egg-info/dependency_links.txt +0 -0
  43. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit.egg-info/entry_points.txt +0 -0
  44. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit.egg-info/requires.txt +0 -0
  45. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/src/iotlabkit.egg-info/top_level.txt +0 -0
  46. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/tests/test_api_parsing.py +0 -0
  47. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/tests/test_cli.py +0 -0
  48. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/tests/test_coap_util.py +0 -0
  49. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/tests/test_doctor.py +0 -0
  50. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/tests/test_generator.py +0 -0
  51. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/tests/test_ml_pipeline.py +0 -0
  52. {iotlabkit-1.0.0 → iotlabkit-1.0.1}/tests/test_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iotlabkit
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: A Beginner-Friendly IoT Lab Code Generator for MSc/MCA Practicals (Programs 5-17)
5
5
  Author: IoT Lab Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iotlabkit"
7
- version = "1.0.0"
7
+ version = "1.0.1"
8
8
  description = "A Beginner-Friendly IoT Lab Code Generator for MSc/MCA Practicals (Programs 5-17)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,7 +4,7 @@ Designed for MSc/MCA Semester 3 IoT Laboratory practicals (Programs 5-17).
4
4
  Generates clean, working, self-contained project templates in Learning and Exam modes.
5
5
  """
6
6
 
7
- __version__ = "1.0.0"
7
+ __version__ = "1.0.1"
8
8
  __author__ = "IoT Lab Contributors"
9
9
  __license__ = "MIT"
10
10
 
@@ -70,14 +70,14 @@ def cmd_info(args: argparse.Namespace) -> int:
70
70
  program_id = args.program_id
71
71
 
72
72
  if program_id in EXCLUDED_PROGRAM_IDS:
73
- print(f"\n❌ Error: Program {program_id} is outside the supported syllabus scope.")
73
+ print(f"\nError: Program {program_id} is outside the supported syllabus scope.")
74
74
  print(" Only Programs 5 through 17 are included in iotlabkit.\n")
75
75
  return 1
76
76
 
77
77
  try:
78
78
  p = get_program(program_id)
79
79
  except ValueError as err:
80
- print(f"\n❌ Error: {err}\n")
80
+ print(f"\nError: {err}\n")
81
81
  return 1
82
82
 
83
83
  print("\n" + "=" * 75)
@@ -89,11 +89,11 @@ def cmd_info(args: argparse.Namespace) -> int:
89
89
  print(f"Data Source / API: {p.source_info}")
90
90
 
91
91
  print("\nOperational Flags:")
92
- print(f" • Internet Required : {'Yes' if p.requires_internet else 'No'}")
93
- print(f" • Cloud Credentials : {'Yes' if p.requires_credentials else 'No'}")
94
- print(f" • External Broker : {'Yes' if p.requires_broker else 'No'}")
95
- print(f" • Local Dataset : {'Yes' if p.requires_dataset else 'No'}")
96
- print(f" • Local Server : {'Yes' if p.requires_server else 'No'}")
92
+ print(f" - Internet Required : {'Yes' if p.requires_internet else 'No'}")
93
+ print(f" - Cloud Credentials : {'Yes' if p.requires_credentials else 'No'}")
94
+ print(f" - External Broker : {'Yes' if p.requires_broker else 'No'}")
95
+ print(f" - Local Dataset : {'Yes' if p.requires_dataset else 'No'}")
96
+ print(f" - Local Server : {'Yes' if p.requires_server else 'No'}")
97
97
 
98
98
  print("\nRequired Python Dependencies:")
99
99
  if p.dependencies:
@@ -111,7 +111,7 @@ def cmd_info(args: argparse.Namespace) -> int:
111
111
 
112
112
  print("\nGenerated Files:")
113
113
  for gf in p.generated_files:
114
- print(f" • {gf}")
114
+ print(f" - {gf}")
115
115
 
116
116
  if p.viva_preview:
117
117
  print("\nSample Viva Questions & Answers:")
@@ -121,8 +121,8 @@ def cmd_info(args: argparse.Namespace) -> int:
121
121
 
122
122
  print("=" * 75)
123
123
  print(f"To generate this practical:")
124
- print(f" iotlabkit create {p.id} --mode learning # Detailed guide & comments")
125
- print(f" iotlabkit create {p.id} --mode exam # Concise code for lab examination")
124
+ print(f" iotlabkit create {p.id} --mode learning (detailed guide)")
125
+ print(f" iotlabkit create {p.id} --mode exam (concise exam code)")
126
126
  print("=" * 75 + "\n")
127
127
  return 0
128
128
 
@@ -132,7 +132,7 @@ def cmd_create(args: argparse.Namespace) -> int:
132
132
  program_id = args.program_id
133
133
 
134
134
  if program_id in EXCLUDED_PROGRAM_IDS:
135
- print(f"\n❌ Error: Program {program_id} is outside the supported syllabus scope.")
135
+ print(f"\nError: Program {program_id} is outside the supported syllabus scope.")
136
136
  print(" Only Programs 5 through 17 are included in iotlabkit.\n")
137
137
  return 1
138
138
 
@@ -146,7 +146,7 @@ def cmd_create(args: argparse.Namespace) -> int:
146
146
  )
147
147
  return 0
148
148
  except Exception as err:
149
- print(f"\n❌ Error generating practical {program_id}: {err}\n")
149
+ print(f"\nError generating practical {program_id}: {err}\n")
150
150
  return 1
151
151
 
152
152
 
@@ -62,7 +62,7 @@ def run_doctor() -> int:
62
62
  # 1. Python Environment
63
63
  print("\n[1] Python Runtime:")
64
64
  py_ok, py_msg = check_python_version()
65
- status_icon = "✓" if py_ok else "✗"
65
+ status_icon = "[OK]" if py_ok else "[X]"
66
66
  print(f" {status_icon} Python Version : {py_msg}")
67
67
  print(f" Platform : {platform.system()} {platform.release()} ({platform.machine()})")
68
68
  if not py_ok:
@@ -78,7 +78,7 @@ def run_doctor() -> int:
78
78
  ]
79
79
  for binary, desc in tools:
80
80
  found, msg = check_tool(binary)
81
- icon = "✓" if found else "!"
81
+ icon = "[OK]" if found else "[ ]"
82
82
  print(f" {icon} {binary:<16}: {msg} - {desc}")
83
83
 
84
84
  # 3. Layer Dependencies
@@ -112,7 +112,7 @@ def run_doctor() -> int:
112
112
  print(f" -- {group_name} --")
113
113
  for pkg_name, imp_name in pkgs:
114
114
  installed, ver_info = check_python_package(pkg_name, imp_name)
115
- icon = "✓" if installed else "✗"
115
+ icon = "[OK]" if installed else "[ ]"
116
116
  print(f" {icon} {pkg_name:<16}: {ver_info}")
117
117
  if not installed:
118
118
  missing_packages.append(pkg_name)
@@ -129,7 +129,7 @@ def run_doctor() -> int:
129
129
  ]
130
130
  for var_name, desc in env_vars:
131
131
  is_set, safe_info = check_env_var_safe(var_name)
132
- icon = "✓" if is_set else "-"
132
+ icon = "[OK]" if is_set else "[-]"
133
133
  print(f" {icon} {var_name:<26}: {safe_info} ({desc})")
134
134
 
135
135
  # 5. Diagnostic Summary
@@ -137,7 +137,7 @@ def run_doctor() -> int:
137
137
  print(" Summary & Recommendations")
138
138
  print("=" * 70)
139
139
  if missing_packages:
140
- print(f"\n! Note: {len(missing_packages)} optional packages are currently uninstalled.")
140
+ print(f"\nNote: {len(missing_packages)} optional packages are currently uninstalled.")
141
141
  print(" To install packages for specific practicals, you can run:")
142
142
  print(" pip install iotlabkit[analytics] # For Data Analytics practicals (11-14)")
143
143
  print(" pip install iotlabkit[ml] # For Machine Learning practicals (15-17)")
@@ -145,7 +145,7 @@ def run_doctor() -> int:
145
145
  print(" pip install iotlabkit[dashboard] # For Streamlit dashboards")
146
146
  print(" pip install iotlabkit[all] # To install all dependencies at once")
147
147
  else:
148
- print("\n✓ All Python packages for all 13 practicals are installed and ready!")
148
+ print("\nAll Python packages for all 13 practicals are installed and ready!")
149
149
 
150
150
  print(
151
151
  "\nReminder: Programs 8 (CoAP) runs with zero external packages (pure standard library).\n"
@@ -21,7 +21,7 @@ def prompt_overwrite_confirmation(directory: str) -> bool:
21
21
  if not existing_files:
22
22
  return True
23
23
 
24
- print(f"\n⚠️ Warning: Target directory '{directory}' already exists and contains {len(existing_files)} file(s):")
24
+ print(f"\nWarning: Target directory '{directory}' already exists and contains {len(existing_files)} file(s):")
25
25
  for f in existing_files[:5]:
26
26
  print(f" - {f}")
27
27
  if len(existing_files) > 5:
@@ -77,7 +77,7 @@ def generate_program(
77
77
  generator_fn = prog_info.get_generator()
78
78
  files_to_write: Dict[str, str] = generator_fn(target_abs, clean_mode)
79
79
 
80
- print(f"\n🚀 Generating Program {prog_info.id}: {prog_info.title}")
80
+ print(f"\nGenerating Program {prog_info.id}: {prog_info.title}")
81
81
  print(f" Mode: {clean_mode.upper()}")
82
82
  print(f" Target Directory: {target_abs}\n")
83
83
 
@@ -89,10 +89,10 @@ def generate_program(
89
89
 
90
90
  with open(full_path, "w", encoding="utf-8") as f:
91
91
  f.write(content)
92
- print(f" ✓ Created: {rel_path}")
92
+ print(f" Created: {rel_path}")
93
93
 
94
94
  print("\n" + "=" * 70)
95
- print(f"🎉 Project successfully generated in '{target_dir}'!")
95
+ print(f"Project generated in '{target_dir}'.")
96
96
  print("=" * 70)
97
97
  print("\nNext steps to run this practical:")
98
98
  print(f" 1. Navigate to the project directory:")
@@ -0,0 +1,209 @@
1
+ """Template generator for Program 5: HTTP REST API + Cloud Database + Dashboard."""
2
+
3
+ from typing import Dict
4
+
5
+
6
+ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
7
+ files = {}
8
+
9
+ files["requirements.txt"] = (
10
+ "requests>=2.28.0\n"
11
+ "python-dotenv>=1.0.0\n"
12
+ "flask>=2.2.0\n"
13
+ "streamlit>=1.25.0\n"
14
+ )
15
+
16
+ files[".env.example"] = (
17
+ "PORT=5000\n"
18
+ "HOST=127.0.0.1\n"
19
+ "CLOUD_PROVIDER=supabase\n"
20
+ "SUPABASE_URL=https://your-project.supabase.co\n"
21
+ "SUPABASE_KEY=your-supabase-key\n"
22
+ "FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
23
+ )
24
+
25
+ files[".gitignore"] = (
26
+ ".env\n"
27
+ "__pycache__/\n"
28
+ "*.py[cod]\n"
29
+ "data/local_telemetry.json\n"
30
+ )
31
+
32
+ files["supabase_schema.sql"] = (
33
+ "CREATE TABLE IF NOT EXISTS sensor_telemetry (\n"
34
+ " id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n"
35
+ " created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,\n"
36
+ " sensor_id TEXT NOT NULL DEFAULT 'DHT22_01',\n"
37
+ " temperature DOUBLE PRECISION NOT NULL,\n"
38
+ " humidity DOUBLE PRECISION NOT NULL\n"
39
+ ");\n"
40
+ )
41
+
42
+ files["firebase_setup.md"] = (
43
+ "# Firebase Setup Guide\n\n"
44
+ "1. Open https://console.firebase.google.com/ and create a project.\n"
45
+ "2. Go to Build -> Realtime Database -> Create Database.\n"
46
+ "3. Select test mode rules.\n"
47
+ "4. Copy database URL to .env under FIREBASE_DATABASE_URL.\n"
48
+ "5. Set CLOUD_PROVIDER=firebase in .env.\n"
49
+ )
50
+
51
+ # server.py
52
+ files["server.py"] = (
53
+ "import json\n"
54
+ "import os\n"
55
+ "from datetime import datetime, timezone\n"
56
+ "from flask import Flask, request, jsonify\n"
57
+ "import requests\n"
58
+ "from dotenv import load_dotenv\n\n"
59
+ "load_dotenv()\n\n"
60
+ "app = Flask(__name__)\n\n"
61
+ "HOST = os.getenv('HOST', '127.0.0.1')\n"
62
+ "PORT = int(os.getenv('PORT', 5000))\n"
63
+ "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'supabase').lower().strip()\n"
64
+ "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
65
+ "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
66
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
67
+ "LOCAL_BACKUP = os.path.join(os.path.dirname(__file__), 'data', 'local_telemetry.json')\n\n"
68
+ "def save_to_supabase(record):\n"
69
+ " if not SUPABASE_URL or not SUPABASE_KEY or 'your-project' in SUPABASE_URL:\n"
70
+ " return False\n"
71
+ " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
72
+ " resp = requests.post(f'{SUPABASE_URL}/rest/v1/sensor_telemetry', json=record, headers=headers, timeout=5)\n"
73
+ " return resp.status_code in (200, 201, 204)\n\n"
74
+ "def save_to_firebase(record):\n"
75
+ " if not FIREBASE_URL or 'your-firebase' in FIREBASE_URL:\n"
76
+ " return False\n"
77
+ " resp = requests.post(f'{FIREBASE_URL}/sensor_telemetry.json', json=record, timeout=5)\n"
78
+ " return resp.status_code == 200\n\n"
79
+ "def save_local(record):\n"
80
+ " os.makedirs(os.path.dirname(LOCAL_BACKUP), exist_ok=True)\n"
81
+ " data = []\n"
82
+ " if os.path.exists(LOCAL_BACKUP):\n"
83
+ " try:\n"
84
+ " with open(LOCAL_BACKUP, 'r', encoding='utf-8') as f:\n"
85
+ " data = json.load(f)\n"
86
+ " except Exception:\n"
87
+ " data = []\n"
88
+ " data.append(record)\n"
89
+ " with open(LOCAL_BACKUP, 'w', encoding='utf-8') as f:\n"
90
+ " json.dump(data, f, indent=2)\n\n"
91
+ "@app.route('/api/telemetry', methods=['POST'])\n"
92
+ "def receive_telemetry():\n"
93
+ " payload = request.get_json(silent=True) or {}\n"
94
+ " temp = float(payload.get('temperature', 25.0))\n"
95
+ " hum = float(payload.get('humidity', 60.0))\n"
96
+ " record = {\n"
97
+ " 'sensor_id': str(payload.get('sensor_id', 'DHT22_01')),\n"
98
+ " 'temperature': temp,\n"
99
+ " 'humidity': hum,\n"
100
+ " 'created_at': datetime.now(timezone.utc).isoformat()\n"
101
+ " }\n"
102
+ " saved_cloud = False\n"
103
+ " if CLOUD_PROVIDER == 'supabase':\n"
104
+ " saved_cloud = save_to_supabase(record)\n"
105
+ " elif CLOUD_PROVIDER == 'firebase':\n"
106
+ " saved_cloud = save_to_firebase(record)\n"
107
+ " save_local(record)\n"
108
+ " status = 'Cloud Stored' if saved_cloud else 'Saved Locally'\n"
109
+ " print(f\"[{datetime.now().strftime('%H:%M:%S')}] Telemetry: Temp={temp}C, Humidity={hum}% -> {status}\")\n"
110
+ " return jsonify({'message': 'Accepted', 'cloud_synced': saved_cloud, 'record': record}), 201\n\n"
111
+ "@app.route('/api/telemetry', methods=['GET'])\n"
112
+ "def get_telemetry():\n"
113
+ " if os.path.exists(LOCAL_BACKUP):\n"
114
+ " with open(LOCAL_BACKUP, 'r', encoding='utf-8') as f:\n"
115
+ " data = json.load(f)\n"
116
+ " return jsonify(list(reversed(data[-50:]))), 200\n"
117
+ " return jsonify([]), 200\n\n"
118
+ "if __name__ == '__main__':\n"
119
+ " print(f'Starting REST API server on http://{HOST}:{PORT}')\n"
120
+ " app.run(host=HOST, port=PORT, debug=False)\n"
121
+ )
122
+
123
+ # client_sender.py
124
+ files["client_sender.py"] = (
125
+ "import argparse\n"
126
+ "import time\n"
127
+ "import requests\n\n"
128
+ "DEFAULT_URL = 'http://127.0.0.1:5000/api/telemetry'\n"
129
+ "READINGS = [\n"
130
+ " (24.5, 62.0), (24.8, 61.5), (25.1, 60.8), (25.4, 59.9), (25.7, 58.5)\n"
131
+ "]\n\n"
132
+ "def send_reading(url, temp, hum):\n"
133
+ " payload = {'sensor_id': 'DHT22_01', 'temperature': temp, 'humidity': hum}\n"
134
+ " try:\n"
135
+ " r = requests.post(url, json=payload, timeout=3)\n"
136
+ " print(f\"Sent: Temp={temp}C, Hum={hum}% | Response: {r.status_code}\")\n"
137
+ " except Exception as e:\n"
138
+ " print('Send error:', e)\n\n"
139
+ "def main():\n"
140
+ " parser = argparse.ArgumentParser()\n"
141
+ " parser.add_argument('--url', default=DEFAULT_URL)\n"
142
+ " parser.add_argument('--temp', type=float)\n"
143
+ " parser.add_argument('--humidity', type=float)\n"
144
+ " args = parser.parse_args()\n\n"
145
+ " if args.temp is not None and args.humidity is not None:\n"
146
+ " send_reading(args.url, args.temp, args.humidity)\n"
147
+ " return\n\n"
148
+ " print('Streaming sample sensor readings...')\n"
149
+ " for t, h in READINGS:\n"
150
+ " send_reading(args.url, t, h)\n"
151
+ " time.sleep(1.5)\n\n"
152
+ "if __name__ == '__main__':\n"
153
+ " main()\n"
154
+ )
155
+
156
+ # dashboard.py
157
+ files["dashboard.py"] = (
158
+ "import os\n"
159
+ "import json\n"
160
+ "import requests\n"
161
+ "import streamlit as st\n\n"
162
+ "st.title('Sensor Telemetry Dashboard')\n\n"
163
+ "REST_API_URL = os.getenv('REST_API_URL', 'http://127.0.0.1:5000/api/telemetry')\n"
164
+ "LOCAL_FILE = os.path.join(os.path.dirname(__file__), 'data', 'local_telemetry.json')\n\n"
165
+ "def fetch_data():\n"
166
+ " try:\n"
167
+ " resp = requests.get(REST_API_URL, timeout=3)\n"
168
+ " if resp.status_code == 200:\n"
169
+ " return resp.json()\n"
170
+ " except Exception:\n"
171
+ " pass\n"
172
+ " if os.path.exists(LOCAL_FILE):\n"
173
+ " try:\n"
174
+ " with open(LOCAL_FILE, 'r', encoding='utf-8') as f:\n"
175
+ " return list(reversed(json.load(f)))\n"
176
+ " except Exception:\n"
177
+ " pass\n"
178
+ " return []\n\n"
179
+ "records = fetch_data()\n"
180
+ "if not records:\n"
181
+ " st.write('No telemetry records found. Start python server.py and send data.')\n"
182
+ "else:\n"
183
+ " latest = records[0]\n"
184
+ " st.write(f\"Latest Reading: Temperature = {latest.get('temperature', 0)} C | Humidity = {latest.get('humidity', 0)} %\")\n"
185
+ " temps = [r.get('temperature', 0) for r in reversed(records[-30:])]\n"
186
+ " hums = [r.get('humidity', 0) for r in reversed(records[-30:])]\n"
187
+ " st.line_chart({'Temperature': temps, 'Humidity': hums})\n"
188
+ " st.write('Recent Records:')\n"
189
+ " st.dataframe(records[:20])\n"
190
+ )
191
+
192
+ # README.md
193
+ files["README.md"] = (
194
+ "# Program 5 — HTTP REST API + Cloud DB + Dashboard\n\n"
195
+ "## Run\n"
196
+ "```bash\n"
197
+ "# Terminal 1: Start REST Server\n"
198
+ "python server.py\n\n"
199
+ "# Terminal 2: Send Sensor Data\n"
200
+ "python client_sender.py\n\n"
201
+ "# Terminal 3: View Dashboard\n"
202
+ "streamlit run dashboard.py\n"
203
+ "```\n\n"
204
+ "## Viva Q&A\n"
205
+ "- Q: What is REST?\n"
206
+ " A: Representational State Transfer, a stateless HTTP-based architecture using methods like GET and POST.\n"
207
+ )
208
+
209
+ return files
@@ -0,0 +1,203 @@
1
+ """Template generator for Program 6: MQTT + Cloud Database + Dashboard."""
2
+
3
+ from typing import Dict
4
+
5
+
6
+ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
7
+ files = {}
8
+
9
+ files["requirements.txt"] = (
10
+ "paho-mqtt>=1.6.1\n"
11
+ "requests>=2.28.0\n"
12
+ "python-dotenv>=1.0.0\n"
13
+ "streamlit>=1.25.0\n"
14
+ )
15
+
16
+ files[".env.example"] = (
17
+ "MQTT_BROKER=broker.hivemq.com\n"
18
+ "MQTT_PORT=1883\n"
19
+ "MQTT_TOPIC=iot/lab/telemetry\n"
20
+ "CLOUD_PROVIDER=supabase\n"
21
+ "SUPABASE_URL=https://your-project.supabase.co\n"
22
+ "SUPABASE_KEY=your-supabase-key\n"
23
+ "FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
24
+ )
25
+
26
+ files[".gitignore"] = (
27
+ ".env\n"
28
+ "__pycache__/\n"
29
+ "*.py[cod]\n"
30
+ "data/local_mqtt_telemetry.json\n"
31
+ )
32
+
33
+ files["supabase_schema.sql"] = (
34
+ "CREATE TABLE IF NOT EXISTS mqtt_telemetry (\n"
35
+ " id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n"
36
+ " created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,\n"
37
+ " topic TEXT NOT NULL,\n"
38
+ " sensor_id TEXT NOT NULL DEFAULT 'DHT22_01',\n"
39
+ " temperature DOUBLE PRECISION NOT NULL,\n"
40
+ " humidity DOUBLE PRECISION NOT NULL\n"
41
+ ");\n\n"
42
+ "ALTER TABLE mqtt_telemetry ENABLE ROW LEVEL SECURITY;\n"
43
+ "CREATE POLICY \"Allow anon read\" ON mqtt_telemetry FOR SELECT USING (true);\n"
44
+ "CREATE POLICY \"Allow anon insert\" ON mqtt_telemetry FOR INSERT WITH CHECK (true);\n"
45
+ )
46
+
47
+ files["subscriber.py"] = (
48
+ "import json\n"
49
+ "import os\n"
50
+ "from datetime import datetime, timezone\n"
51
+ "import requests\n"
52
+ "from dotenv import load_dotenv\n"
53
+ "import paho.mqtt.client as mqtt\n\n"
54
+ "load_dotenv()\n\n"
55
+ "BROKER = os.getenv('MQTT_BROKER', 'broker.hivemq.com')\n"
56
+ "PORT = int(os.getenv('MQTT_PORT', 1883))\n"
57
+ "TOPIC = os.getenv('MQTT_TOPIC', 'iot/lab/telemetry')\n"
58
+ "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'supabase').lower().strip()\n"
59
+ "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
60
+ "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
61
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
62
+ "LOCAL_BACKUP = os.path.join(os.path.dirname(__file__), 'data', 'local_mqtt_telemetry.json')\n\n"
63
+ "def save_to_supabase(record):\n"
64
+ " if not SUPABASE_URL or not SUPABASE_KEY or 'your-project' in SUPABASE_URL:\n"
65
+ " return False\n"
66
+ " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
67
+ " resp = requests.post(f'{SUPABASE_URL}/rest/v1/mqtt_telemetry', json=record, headers=headers, timeout=5)\n"
68
+ " return resp.status_code in (200, 201, 204)\n\n"
69
+ "def save_to_firebase(record):\n"
70
+ " if not FIREBASE_URL or 'your-firebase' in FIREBASE_URL:\n"
71
+ " return False\n"
72
+ " resp = requests.post(f'{FIREBASE_URL}/mqtt_telemetry.json', json=record, timeout=5)\n"
73
+ " return resp.status_code == 200\n\n"
74
+ "def save_local(record):\n"
75
+ " os.makedirs(os.path.dirname(LOCAL_BACKUP), exist_ok=True)\n"
76
+ " data = []\n"
77
+ " if os.path.exists(LOCAL_BACKUP):\n"
78
+ " try:\n"
79
+ " with open(LOCAL_BACKUP, 'r', encoding='utf-8') as f:\n"
80
+ " data = json.load(f)\n"
81
+ " except Exception:\n"
82
+ " data = []\n"
83
+ " data.append(record)\n"
84
+ " with open(LOCAL_BACKUP, 'w', encoding='utf-8') as f:\n"
85
+ " json.dump(data, f, indent=2)\n\n"
86
+ "def on_connect(client, userdata, flags, rc):\n"
87
+ " if rc == 0:\n"
88
+ " print(f'Connected to MQTT broker {BROKER}:{PORT}')\n"
89
+ " client.subscribe(TOPIC)\n"
90
+ " print(f'Subscribed to topic: {TOPIC}')\n"
91
+ " else:\n"
92
+ " print('Connection failed with code', rc)\n\n"
93
+ "def on_message(client, userdata, msg):\n"
94
+ " try:\n"
95
+ " payload = json.loads(msg.payload.decode('utf-8'))\n"
96
+ " temp = float(payload.get('temperature', 0))\n"
97
+ " hum = float(payload.get('humidity', 0))\n"
98
+ " record = {\n"
99
+ " 'topic': msg.topic,\n"
100
+ " 'sensor_id': str(payload.get('sensor_id', 'DHT22_01')),\n"
101
+ " 'temperature': temp,\n"
102
+ " 'humidity': hum,\n"
103
+ " 'created_at': datetime.now(timezone.utc).isoformat()\n"
104
+ " }\n"
105
+ " saved = False\n"
106
+ " if CLOUD_PROVIDER == 'supabase':\n"
107
+ " saved = save_to_supabase(record)\n"
108
+ " elif CLOUD_PROVIDER == 'firebase':\n"
109
+ " saved = save_to_firebase(record)\n"
110
+ " save_local(record)\n"
111
+ " status = 'Cloud Stored' if saved else 'Saved Locally'\n"
112
+ " print(f\"[{datetime.now().strftime('%H:%M:%S')}] Received on {msg.topic}: Temp={temp}C, Hum={hum}% -> {status}\")\n"
113
+ " except Exception as e:\n"
114
+ " print('Message parse error:', e)\n\n"
115
+ "def main():\n"
116
+ " client = mqtt.Client()\n"
117
+ " client.on_connect = on_connect\n"
118
+ " client.on_message = on_message\n"
119
+ " print(f'Connecting to {BROKER}:{PORT}...')\n"
120
+ " client.connect(BROKER, PORT, keepalive=60)\n"
121
+ " client.loop_forever()\n\n"
122
+ "if __name__ == '__main__':\n"
123
+ " main()\n"
124
+ )
125
+
126
+ # publisher.py
127
+ files["publisher.py"] = (
128
+ "import argparse\n"
129
+ "import json\n"
130
+ "import os\n"
131
+ "import time\n"
132
+ "from dotenv import load_dotenv\n"
133
+ "import paho.mqtt.client as mqtt\n\n"
134
+ "load_dotenv()\n\n"
135
+ "BROKER = os.getenv('MQTT_BROKER', 'broker.hivemq.com')\n"
136
+ "PORT = int(os.getenv('MQTT_PORT', 1883))\n"
137
+ "TOPIC = os.getenv('MQTT_TOPIC', 'iot/lab/telemetry')\n"
138
+ "READINGS = [(26.0, 70.0), (26.3, 69.2), (26.7, 68.5), (27.1, 67.8), (27.4, 66.9)]\n\n"
139
+ "def main():\n"
140
+ " parser = argparse.ArgumentParser()\n"
141
+ " parser.add_argument('--temp', type=float)\n"
142
+ " parser.add_argument('--humidity', type=float)\n"
143
+ " args = parser.parse_args()\n\n"
144
+ " client = mqtt.Client()\n"
145
+ " client.connect(BROKER, PORT, 60)\n\n"
146
+ " if args.temp is not None and args.humidity is not None:\n"
147
+ " payload = json.dumps({'sensor_id': 'DHT22_01', 'temperature': args.temp, 'humidity': args.humidity})\n"
148
+ " client.publish(TOPIC, payload)\n"
149
+ " print(f'Published: Temp={args.temp}C, Hum={args.humidity}% to {TOPIC}')\n"
150
+ " return\n\n"
151
+ " print('Publishing sequence of sensor readings...')\n"
152
+ " for t, h in READINGS:\n"
153
+ " payload = json.dumps({'sensor_id': 'DHT22_01', 'temperature': t, 'humidity': h})\n"
154
+ " client.publish(TOPIC, payload)\n"
155
+ " print(f'Published: Temp={t}C, Hum={h}%')\n"
156
+ " time.sleep(1.5)\n"
157
+ " client.disconnect()\n\n"
158
+ "if __name__ == '__main__':\n"
159
+ " main()\n"
160
+ )
161
+
162
+ # dashboard.py
163
+ files["dashboard.py"] = (
164
+ "import os\n"
165
+ "import json\n"
166
+ "import streamlit as st\n\n"
167
+ "st.title('MQTT Telemetry Dashboard')\n\n"
168
+ "LOCAL_FILE = os.path.join(os.path.dirname(__file__), 'data', 'local_mqtt_telemetry.json')\n"
169
+ "if os.path.exists(LOCAL_FILE):\n"
170
+ " with open(LOCAL_FILE, 'r', encoding='utf-8') as f:\n"
171
+ " records = list(reversed(json.load(f)))\n"
172
+ " if records:\n"
173
+ " latest = records[0]\n"
174
+ " st.write(f\"Latest Reading: Temp = {latest.get('temperature')} C | Humidity = {latest.get('humidity')} %\")\n"
175
+ " temps = [r.get('temperature', 0) for r in reversed(records[-30:])]\n"
176
+ " hums = [r.get('humidity', 0) for r in reversed(records[-30:])]\n"
177
+ " st.line_chart({'Temperature': temps, 'Humidity': hums})\n"
178
+ " st.write('Stored Telemetry Records:')\n"
179
+ " st.dataframe(records[:25])\n"
180
+ " else:\n"
181
+ " st.write('No records found in local database.')\n"
182
+ "else:\n"
183
+ " st.write('No MQTT telemetry received yet. Run python subscriber.py and python publisher.py.')\n"
184
+ )
185
+
186
+ # README.md
187
+ files["README.md"] = (
188
+ "# Program 6 — MQTT + Cloud DB + Dashboard\n\n"
189
+ "## Run\n"
190
+ "```bash\n"
191
+ "# Terminal 1: Start Subscriber\n"
192
+ "python subscriber.py\n\n"
193
+ "# Terminal 2: Publish Data\n"
194
+ "python publisher.py\n\n"
195
+ "# Terminal 3: View Dashboard\n"
196
+ "streamlit run dashboard.py\n"
197
+ "```\n\n"
198
+ "## Viva Q&A\n"
199
+ "- Q: What is MQTT?\n"
200
+ " A: Message Queuing Telemetry Transport, a lightweight publish-subscribe messaging protocol over TCP.\n"
201
+ )
202
+
203
+ return files