iotlabkit 1.0.0__py3-none-any.whl
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.
- iotlabkit/__init__.py +30 -0
- iotlabkit/cli.py +239 -0
- iotlabkit/coap_util.py +322 -0
- iotlabkit/data/beijing_air/beijing_sample.csv +801 -0
- iotlabkit/data/india_air/city_day_sample.csv +901 -0
- iotlabkit/data/occupancy/datatraining.txt +801 -0
- iotlabkit/doctor.py +160 -0
- iotlabkit/generator.py +110 -0
- iotlabkit/registry.py +705 -0
- iotlabkit/templates/program_05.py +436 -0
- iotlabkit/templates/program_06.py +404 -0
- iotlabkit/templates/program_07.py +249 -0
- iotlabkit/templates/program_08.py +278 -0
- iotlabkit/templates/program_09.py +329 -0
- iotlabkit/templates/program_10.py +338 -0
- iotlabkit/templates/program_11.py +347 -0
- iotlabkit/templates/program_12.py +317 -0
- iotlabkit/templates/program_13.py +272 -0
- iotlabkit/templates/program_14.py +287 -0
- iotlabkit/templates/program_15.py +411 -0
- iotlabkit/templates/program_16.py +382 -0
- iotlabkit/templates/program_17.py +422 -0
- iotlabkit-1.0.0.dist-info/LICENSE +21 -0
- iotlabkit-1.0.0.dist-info/METADATA +550 -0
- iotlabkit-1.0.0.dist-info/RECORD +28 -0
- iotlabkit-1.0.0.dist-info/WHEEL +5 -0
- iotlabkit-1.0.0.dist-info/entry_points.txt +2 -0
- iotlabkit-1.0.0.dist-info/top_level.txt +1 -0
iotlabkit/doctor.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""System diagnostic tool for verifying Python environment, dependencies, and configuration.
|
|
2
|
+
|
|
3
|
+
Ensures beginner students can pinpoint missing packages, tools, or configuration errors.
|
|
4
|
+
Follows strict security practices: NEVER prints sensitive credential values.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Dict, List, Tuple
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def check_python_version() -> Tuple[bool, str]:
|
|
15
|
+
"""Verifies that the Python version is at least 3.9."""
|
|
16
|
+
major, minor, micro = sys.version_info[:3]
|
|
17
|
+
version_str = f"{major}.{minor}.{micro}"
|
|
18
|
+
if sys.version_info >= (3, 9):
|
|
19
|
+
return True, f"Python {version_str} ({sys.executable})"
|
|
20
|
+
return (
|
|
21
|
+
False,
|
|
22
|
+
f"Python {version_str} is below required minimum 3.9. Please upgrade to Python 3.10+.",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def check_tool(binary_name: str) -> Tuple[bool, str]:
|
|
27
|
+
"""Checks whether a system binary/executable is in PATH."""
|
|
28
|
+
path = shutil.which(binary_name)
|
|
29
|
+
if path:
|
|
30
|
+
return True, f"Found at {path}"
|
|
31
|
+
return False, "Not found in PATH (optional)"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_python_package(package_name: str, import_name: str = None) -> Tuple[bool, str]:
|
|
35
|
+
"""Checks whether a Python package is importable and retrieves its version."""
|
|
36
|
+
import_target = import_name or package_name
|
|
37
|
+
try:
|
|
38
|
+
mod = __import__(import_target)
|
|
39
|
+
version = getattr(mod, "__version__", "installed")
|
|
40
|
+
return True, f"v{version}"
|
|
41
|
+
except ImportError:
|
|
42
|
+
return False, "Not installed"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def check_env_var_safe(var_name: str) -> Tuple[bool, str]:
|
|
46
|
+
"""Safely checks if an environment variable is set without leaking secrets."""
|
|
47
|
+
val = os.environ.get(var_name)
|
|
48
|
+
if not val:
|
|
49
|
+
return False, "[Not Set]"
|
|
50
|
+
masked = f"[Configured: {len(val)} chars, ends with '...{val[-3:]}']" if len(val) >= 4 else "[Configured]"
|
|
51
|
+
return True, masked
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_doctor() -> int:
|
|
55
|
+
"""Executes the full diagnostic suite and prints beginner-friendly reports."""
|
|
56
|
+
print("=" * 70)
|
|
57
|
+
print(" iotlabkit Doctor - Environment Diagnostics")
|
|
58
|
+
print("=" * 70)
|
|
59
|
+
|
|
60
|
+
all_passed = True
|
|
61
|
+
|
|
62
|
+
# 1. Python Environment
|
|
63
|
+
print("\n[1] Python Runtime:")
|
|
64
|
+
py_ok, py_msg = check_python_version()
|
|
65
|
+
status_icon = "✓" if py_ok else "✗"
|
|
66
|
+
print(f" {status_icon} Python Version : {py_msg}")
|
|
67
|
+
print(f" Platform : {platform.system()} {platform.release()} ({platform.machine()})")
|
|
68
|
+
if not py_ok:
|
|
69
|
+
all_passed = False
|
|
70
|
+
|
|
71
|
+
# 2. Command Line Tools
|
|
72
|
+
print("\n[2] System Binaries:")
|
|
73
|
+
tools = [
|
|
74
|
+
("git", "Version control & repository cloning"),
|
|
75
|
+
("curl", "HTTP request testing tool"),
|
|
76
|
+
("mosquitto", "Eclipse Mosquitto local MQTT broker (Prog 9)"),
|
|
77
|
+
("mosquitto_sub", "Mosquitto CLI subscriber tool"),
|
|
78
|
+
]
|
|
79
|
+
for binary, desc in tools:
|
|
80
|
+
found, msg = check_tool(binary)
|
|
81
|
+
icon = "✓" if found else "!"
|
|
82
|
+
print(f" {icon} {binary:<16}: {msg} - {desc}")
|
|
83
|
+
|
|
84
|
+
# 3. Layer Dependencies
|
|
85
|
+
print("\n[3] Python Packages by Layer:")
|
|
86
|
+
|
|
87
|
+
package_groups = {
|
|
88
|
+
"Core & Utilities": [
|
|
89
|
+
("requests", "requests"),
|
|
90
|
+
("python-dotenv", "dotenv"),
|
|
91
|
+
("flask", "flask"),
|
|
92
|
+
],
|
|
93
|
+
"IoT & Protocols": [
|
|
94
|
+
("paho-mqtt", "paho.mqtt"),
|
|
95
|
+
],
|
|
96
|
+
"Data Analytics": [
|
|
97
|
+
("pandas", "pandas"),
|
|
98
|
+
("matplotlib", "matplotlib"),
|
|
99
|
+
],
|
|
100
|
+
"Machine Learning": [
|
|
101
|
+
("scikit-learn", "sklearn"),
|
|
102
|
+
("joblib", "joblib"),
|
|
103
|
+
],
|
|
104
|
+
"Dashboards": [
|
|
105
|
+
("streamlit", "streamlit"),
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
missing_packages = []
|
|
110
|
+
|
|
111
|
+
for group_name, pkgs in package_groups.items():
|
|
112
|
+
print(f" -- {group_name} --")
|
|
113
|
+
for pkg_name, imp_name in pkgs:
|
|
114
|
+
installed, ver_info = check_python_package(pkg_name, imp_name)
|
|
115
|
+
icon = "✓" if installed else "✗"
|
|
116
|
+
print(f" {icon} {pkg_name:<16}: {ver_info}")
|
|
117
|
+
if not installed:
|
|
118
|
+
missing_packages.append(pkg_name)
|
|
119
|
+
|
|
120
|
+
# 4. Safe Cloud Configuration Check
|
|
121
|
+
print("\n[4] Cloud Credentials & Config (Safe Check - No Secrets Revealed):")
|
|
122
|
+
env_vars = [
|
|
123
|
+
("SUPABASE_URL", "Supabase project URL (Prog 5, 6, 10)"),
|
|
124
|
+
("SUPABASE_KEY", "Supabase anon/public API key (Prog 5, 6, 10)"),
|
|
125
|
+
("FIREBASE_DATABASE_URL", "Firebase Realtime DB URL (Prog 5, 6, 10)"),
|
|
126
|
+
("THINGSBOARD_ACCESS_TOKEN", "ThingsBoard Device Token (Prog 7)"),
|
|
127
|
+
("THINGSBOARD_BROKER", "ThingsBoard Broker host (Prog 7)"),
|
|
128
|
+
("MQTT_BROKER_HOST", "Custom MQTT broker host (Prog 6, 9)"),
|
|
129
|
+
]
|
|
130
|
+
for var_name, desc in env_vars:
|
|
131
|
+
is_set, safe_info = check_env_var_safe(var_name)
|
|
132
|
+
icon = "✓" if is_set else "-"
|
|
133
|
+
print(f" {icon} {var_name:<26}: {safe_info} ({desc})")
|
|
134
|
+
|
|
135
|
+
# 5. Diagnostic Summary
|
|
136
|
+
print("\n" + "=" * 70)
|
|
137
|
+
print(" Summary & Recommendations")
|
|
138
|
+
print("=" * 70)
|
|
139
|
+
if missing_packages:
|
|
140
|
+
print(f"\n! Note: {len(missing_packages)} optional packages are currently uninstalled.")
|
|
141
|
+
print(" To install packages for specific practicals, you can run:")
|
|
142
|
+
print(" pip install iotlabkit[analytics] # For Data Analytics practicals (11-14)")
|
|
143
|
+
print(" pip install iotlabkit[ml] # For Machine Learning practicals (15-17)")
|
|
144
|
+
print(" pip install iotlabkit[iot] # For Communication practicals (5-7, 9-10)")
|
|
145
|
+
print(" pip install iotlabkit[dashboard] # For Streamlit dashboards")
|
|
146
|
+
print(" pip install iotlabkit[all] # To install all dependencies at once")
|
|
147
|
+
else:
|
|
148
|
+
print("\n✓ All Python packages for all 13 practicals are installed and ready!")
|
|
149
|
+
|
|
150
|
+
print(
|
|
151
|
+
"\nReminder: Programs 8 (CoAP) runs with zero external packages (pure standard library).\n"
|
|
152
|
+
"Cloud credentials only need to be configured in generated `.env` files for Programs 5, 6, 7, and 10."
|
|
153
|
+
)
|
|
154
|
+
print("=" * 70)
|
|
155
|
+
|
|
156
|
+
return 0 if py_ok else 1
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
sys.exit(run_doctor())
|
iotlabkit/generator.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Project generator for IoT Laboratory practicals.
|
|
2
|
+
|
|
3
|
+
Generates self-contained, runnable practical directories with requirements.txt,
|
|
4
|
+
README.md, .env.example, source files, and dataset loaders.
|
|
5
|
+
Supports two execution modes:
|
|
6
|
+
- 'learning': Rich comments, step-by-step guides, architecture diagrams, viva Q&A.
|
|
7
|
+
- 'exam': Clean, concise, minimal comments, essential commands, viva Q&A.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
from iotlabkit.registry import get_program, ProgramInfo
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def prompt_overwrite_confirmation(directory: str) -> bool:
|
|
17
|
+
"""Prompts the user before overwriting existing files in a directory."""
|
|
18
|
+
if not os.path.exists(directory):
|
|
19
|
+
return True
|
|
20
|
+
existing_files = os.listdir(directory)
|
|
21
|
+
if not existing_files:
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
print(f"\n⚠️ Warning: Target directory '{directory}' already exists and contains {len(existing_files)} file(s):")
|
|
25
|
+
for f in existing_files[:5]:
|
|
26
|
+
print(f" - {f}")
|
|
27
|
+
if len(existing_files) > 5:
|
|
28
|
+
print(f" ... and {len(existing_files) - 5} more.")
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
reply = input("\nDo you want to overwrite existing files? [y/N]: ").strip().lower()
|
|
32
|
+
return reply in ("y", "yes")
|
|
33
|
+
except (EOFError, KeyboardInterrupt):
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def generate_program(
|
|
38
|
+
program_id: int,
|
|
39
|
+
output_dir: Optional[str] = None,
|
|
40
|
+
mode: str = "learning",
|
|
41
|
+
force: bool = False,
|
|
42
|
+
interactive: bool = True,
|
|
43
|
+
) -> str:
|
|
44
|
+
"""Generates a complete project directory for the requested practical.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
program_id: Practical program number (5-17).
|
|
48
|
+
output_dir: Target directory path (defaults to 'labXX').
|
|
49
|
+
mode: Presentation mode ('learning' or 'exam').
|
|
50
|
+
force: If True, overwrites existing directory without prompt.
|
|
51
|
+
interactive: If True, prompts user before overwriting.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
The absolute path to the generated project directory.
|
|
55
|
+
"""
|
|
56
|
+
prog_info: ProgramInfo = get_program(program_id)
|
|
57
|
+
|
|
58
|
+
valid_modes = ("learning", "exam")
|
|
59
|
+
clean_mode = mode.lower().strip()
|
|
60
|
+
if clean_mode not in valid_modes:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Invalid mode '{mode}'. Supported modes are: {', '.join(valid_modes)}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
target_dir = output_dir or f"lab{prog_info.code}"
|
|
66
|
+
target_abs = os.path.abspath(target_dir)
|
|
67
|
+
|
|
68
|
+
# Check for existing files
|
|
69
|
+
if not force and interactive:
|
|
70
|
+
if not prompt_overwrite_confirmation(target_dir):
|
|
71
|
+
print(f"Generation cancelled. Directory '{target_dir}' was preserved.")
|
|
72
|
+
return target_abs
|
|
73
|
+
|
|
74
|
+
os.makedirs(target_abs, exist_ok=True)
|
|
75
|
+
|
|
76
|
+
# Retrieve generator function from program template module
|
|
77
|
+
generator_fn = prog_info.get_generator()
|
|
78
|
+
files_to_write: Dict[str, str] = generator_fn(target_abs, clean_mode)
|
|
79
|
+
|
|
80
|
+
print(f"\n🚀 Generating Program {prog_info.id}: {prog_info.title}")
|
|
81
|
+
print(f" Mode: {clean_mode.upper()}")
|
|
82
|
+
print(f" Target Directory: {target_abs}\n")
|
|
83
|
+
|
|
84
|
+
for rel_path, content in files_to_write.items():
|
|
85
|
+
full_path = os.path.join(target_abs, rel_path)
|
|
86
|
+
parent_dir = os.path.dirname(full_path)
|
|
87
|
+
if parent_dir:
|
|
88
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
with open(full_path, "w", encoding="utf-8") as f:
|
|
91
|
+
f.write(content)
|
|
92
|
+
print(f" ✓ Created: {rel_path}")
|
|
93
|
+
|
|
94
|
+
print("\n" + "=" * 70)
|
|
95
|
+
print(f"🎉 Project successfully generated in '{target_dir}'!")
|
|
96
|
+
print("=" * 70)
|
|
97
|
+
print("\nNext steps to run this practical:")
|
|
98
|
+
print(f" 1. Navigate to the project directory:")
|
|
99
|
+
print(f" cd {target_dir}")
|
|
100
|
+
print(f" 2. Install dependencies (recommended in a virtual environment):")
|
|
101
|
+
print(f" pip install -r requirements.txt")
|
|
102
|
+
if prog_info.requires_credentials:
|
|
103
|
+
print(f" 3. Configure environment variables:")
|
|
104
|
+
print(f" cp .env.example .env")
|
|
105
|
+
print(f" (Open .env in any editor and enter your credentials)")
|
|
106
|
+
print(f" 4. Follow instructions in '{target_dir}/README.md':")
|
|
107
|
+
print(f" {prog_info.setup_steps[0] if prog_info.setup_steps else 'python main.py'}")
|
|
108
|
+
print("=" * 70 + "\n")
|
|
109
|
+
|
|
110
|
+
return target_abs
|