iotlabkit 1.0.2__tar.gz → 1.0.3__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 (40) hide show
  1. {iotlabkit-1.0.2/src/iotlabkit.egg-info → iotlabkit-1.0.3}/PKG-INFO +1 -1
  2. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/pyproject.toml +1 -1
  3. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/__init__.py +1 -1
  4. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_05.py +49 -81
  5. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_06.py +36 -79
  6. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_10.py +47 -86
  7. {iotlabkit-1.0.2 → iotlabkit-1.0.3/src/iotlabkit.egg-info}/PKG-INFO +1 -1
  8. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/LICENSE +0 -0
  9. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/README.md +0 -0
  10. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/setup.cfg +0 -0
  11. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/cli.py +0 -0
  12. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/coap_util.py +0 -0
  13. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/data/beijing_air/beijing_sample.csv +0 -0
  14. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/data/india_air/city_day_sample.csv +0 -0
  15. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/data/occupancy/datatraining.txt +0 -0
  16. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/doctor.py +0 -0
  17. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/generator.py +0 -0
  18. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/registry.py +0 -0
  19. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_07.py +0 -0
  20. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_08.py +0 -0
  21. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_09.py +0 -0
  22. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_11.py +0 -0
  23. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_12.py +0 -0
  24. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_13.py +0 -0
  25. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_14.py +0 -0
  26. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_15.py +0 -0
  27. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_16.py +0 -0
  28. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit/templates/program_17.py +0 -0
  29. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit.egg-info/SOURCES.txt +0 -0
  30. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit.egg-info/dependency_links.txt +0 -0
  31. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit.egg-info/entry_points.txt +0 -0
  32. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit.egg-info/requires.txt +0 -0
  33. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/src/iotlabkit.egg-info/top_level.txt +0 -0
  34. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/tests/test_api_parsing.py +0 -0
  35. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/tests/test_cli.py +0 -0
  36. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/tests/test_coap_util.py +0 -0
  37. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/tests/test_doctor.py +0 -0
  38. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/tests/test_generator.py +0 -0
  39. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/tests/test_ml_pipeline.py +0 -0
  40. {iotlabkit-1.0.2 → iotlabkit-1.0.3}/tests/test_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iotlabkit
3
- Version: 1.0.2
3
+ Version: 1.0.3
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.2"
7
+ version = "1.0.3"
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.2"
7
+ __version__ = "1.0.3"
8
8
  __author__ = "IoT Lab Contributors"
9
9
  __license__ = "MIT"
10
10
 
@@ -11,17 +11,15 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
11
11
  "python-dotenv>=1.0.0\n"
12
12
  "flask>=2.2.0\n"
13
13
  "streamlit>=1.25.0\n"
14
- "psycopg2-binary>=2.9.0\n"
15
14
  )
16
15
 
17
16
  files[".env.example"] = (
18
17
  "PORT=5000\n"
19
18
  "HOST=127.0.0.1\n"
20
- "CLOUD_PROVIDER=supabase\n"
19
+ "CLOUD_PROVIDER=firebase\n"
20
+ "FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
21
21
  "SUPABASE_URL=https://your-project.supabase.co\n"
22
22
  "SUPABASE_KEY=your-supabase-key\n"
23
- "SUPABASE_DB_PASSWORD=your-database-password\n"
24
- "FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
25
23
  )
26
24
 
27
25
  files[".gitignore"] = (
@@ -45,9 +43,10 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
45
43
  "# Firebase Setup Guide\n\n"
46
44
  "1. Open https://console.firebase.google.com/ and create a project.\n"
47
45
  "2. Go to Build -> Realtime Database -> Create Database.\n"
48
- "3. Select test mode rules.\n"
49
- "4. Copy database URL to .env under FIREBASE_DATABASE_URL.\n"
50
- "5. Set CLOUD_PROVIDER=firebase in .env.\n"
46
+ "3. Select 'Start in test mode' rules.\n"
47
+ "4. Copy the database URL (e.g. https://your-project-rtdb.firebaseio.com/).\n"
48
+ "5. Paste the URL into .env under FIREBASE_DATABASE_URL.\n"
49
+ "6. Tables/paths are created automatically on first insert (zero SQL needed).\n"
51
50
  )
52
51
 
53
52
  # server.py
@@ -62,22 +61,28 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
62
61
  "app = Flask(__name__)\n\n"
63
62
  "HOST = os.getenv('HOST', '127.0.0.1')\n"
64
63
  "PORT = int(os.getenv('PORT', 5000))\n"
65
- "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'supabase').lower().strip()\n"
64
+ "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'firebase').lower().strip()\n"
65
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
66
66
  "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
67
67
  "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
68
- "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
69
68
  "LOCAL_BACKUP = os.path.join(os.path.dirname(__file__), 'data', 'local_telemetry.json')\n\n"
69
+ "def save_to_firebase(record):\n"
70
+ " if not FIREBASE_URL or 'your-project' in FIREBASE_URL:\n"
71
+ " return False\n"
72
+ " try:\n"
73
+ " resp = requests.post(f'{FIREBASE_URL}/sensor_telemetry.json', json=record, timeout=5)\n"
74
+ " return resp.status_code == 200\n"
75
+ " except Exception:\n"
76
+ " return False\n\n"
70
77
  "def save_to_supabase(record):\n"
71
78
  " if not SUPABASE_URL or not SUPABASE_KEY or 'your-project' in SUPABASE_URL:\n"
72
79
  " return False\n"
73
- " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
74
- " resp = requests.post(f'{SUPABASE_URL}/rest/v1/sensor_telemetry', json=record, headers=headers, timeout=5)\n"
75
- " return resp.status_code in (200, 201, 204)\n\n"
76
- "def save_to_firebase(record):\n"
77
- " if not FIREBASE_URL or 'your-firebase' in FIREBASE_URL:\n"
78
- " return False\n"
79
- " resp = requests.post(f'{FIREBASE_URL}/sensor_telemetry.json', json=record, timeout=5)\n"
80
- " return resp.status_code == 200\n\n"
80
+ " try:\n"
81
+ " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
82
+ " resp = requests.post(f'{SUPABASE_URL}/rest/v1/sensor_telemetry', json=record, headers=headers, timeout=5)\n"
83
+ " return resp.status_code in (200, 201, 204)\n"
84
+ " except Exception:\n"
85
+ " return False\n\n"
81
86
  "def save_local(record):\n"
82
87
  " os.makedirs(os.path.dirname(LOCAL_BACKUP), exist_ok=True)\n"
83
88
  " data = []\n"
@@ -102,95 +107,58 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
102
107
  " 'created_at': datetime.now(timezone.utc).isoformat()\n"
103
108
  " }\n"
104
109
  " saved_cloud = False\n"
105
- " if CLOUD_PROVIDER == 'supabase':\n"
106
- " saved_cloud = save_to_supabase(record)\n"
107
- " elif CLOUD_PROVIDER == 'firebase':\n"
110
+ " if CLOUD_PROVIDER == 'firebase':\n"
108
111
  " saved_cloud = save_to_firebase(record)\n"
112
+ " elif CLOUD_PROVIDER == 'supabase':\n"
113
+ " saved_cloud = save_to_supabase(record)\n"
109
114
  " save_local(record)\n"
110
- " status = 'Cloud Stored' if saved_cloud else 'Saved Locally'\n"
115
+ " status = 'Stored in Cloud' if saved_cloud else 'Saved Locally'\n"
111
116
  " print(f\"[{datetime.now().strftime('%H:%M:%S')}] Telemetry: Temp={temp}C, Humidity={hum}% -> {status}\")\n"
112
117
  " return jsonify({'message': 'Accepted', 'cloud_synced': saved_cloud, 'record': record}), 201\n\n"
113
118
  "@app.route('/api/telemetry', methods=['GET'])\n"
114
119
  "def get_telemetry():\n"
120
+ " if CLOUD_PROVIDER == 'firebase' and FIREBASE_URL and 'your-project' not in FIREBASE_URL:\n"
121
+ " try:\n"
122
+ " resp = requests.get(f'{FIREBASE_URL}/sensor_telemetry.json', timeout=4)\n"
123
+ " if resp.status_code == 200 and resp.json():\n"
124
+ " data = resp.json()\n"
125
+ " if isinstance(data, dict):\n"
126
+ " return jsonify(list(reversed(list(data.values())))), 200\n"
127
+ " except Exception:\n"
128
+ " pass\n"
115
129
  " if os.path.exists(LOCAL_BACKUP):\n"
116
130
  " with open(LOCAL_BACKUP, 'r', encoding='utf-8') as f:\n"
117
131
  " data = json.load(f)\n"
118
132
  " return jsonify(list(reversed(data[-50:]))), 200\n"
119
133
  " return jsonify([]), 200\n\n"
120
134
  "if __name__ == '__main__':\n"
121
- " try:\n"
122
- " import init_db\n"
123
- " init_db.init_table()\n"
124
- " except Exception:\n"
125
- " pass\n"
126
135
  " print(f'Starting REST API server on http://{HOST}:{PORT}')\n"
127
136
  " app.run(host=HOST, port=PORT, debug=False)\n"
128
137
  )
129
138
 
130
139
  files["init_db.py"] = (
131
140
  "import os\n"
132
- "import re\n"
133
141
  "import requests\n"
134
142
  "from dotenv import load_dotenv\n\n"
135
143
  "load_dotenv()\n\n"
136
- "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
137
- "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
138
- "DATABASE_URL = os.getenv('DATABASE_URL', '')\n"
139
- "DB_PASSWORD = os.getenv('SUPABASE_DB_PASSWORD', '')\n"
140
- "ACCESS_TOKEN = os.getenv('SUPABASE_ACCESS_TOKEN', '')\n\n"
141
- "SQL_QUERY = '''\n"
142
- "CREATE TABLE IF NOT EXISTS sensor_telemetry (\n"
143
- " id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n"
144
- " created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,\n"
145
- " sensor_id TEXT NOT NULL DEFAULT 'DHT22_01',\n"
146
- " temperature DOUBLE PRECISION NOT NULL,\n"
147
- " humidity DOUBLE PRECISION NOT NULL\n"
148
- ");\n"
149
- "ALTER TABLE sensor_telemetry ENABLE ROW LEVEL SECURITY;\n"
150
- "CREATE POLICY \"Allow anon read\" ON sensor_telemetry FOR SELECT USING (true);\n"
151
- "CREATE POLICY \"Allow anon insert\" ON sensor_telemetry FOR INSERT WITH CHECK (true);\n"
152
- "'''\n\n"
153
- "def init_table():\n"
154
- " if not SUPABASE_URL or 'your-project' in SUPABASE_URL:\n"
155
- " return False\n"
156
- " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}'}\n"
157
- " try:\n"
158
- " r = requests.get(f'{SUPABASE_URL}/rest/v1/sensor_telemetry?limit=1', headers=headers, timeout=4)\n"
159
- " if r.status_code == 200:\n"
160
- " print('Supabase: Table sensor_telemetry is ready.')\n"
161
- " return True\n"
162
- " except Exception:\n"
163
- " pass\n"
164
- " m = re.search(r'https://([a-z0-9_-]+)\\.supabase\\.co', SUPABASE_URL)\n"
165
- " ref = m.group(1) if m else None\n"
166
- " if ACCESS_TOKEN and ref:\n"
144
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
145
+ "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n\n"
146
+ "def check():\n"
147
+ " print('Database Check:')\n"
148
+ " if FIREBASE_URL and 'your-project' not in FIREBASE_URL:\n"
167
149
  " try:\n"
168
- " resp = requests.post(f'https://api.supabase.com/v1/projects/{ref}/database/query', json={'query': SQL_QUERY}, headers={'Authorization': f'Bearer {ACCESS_TOKEN}', 'Content-Type': 'application/json'}, timeout=8)\n"
169
- " if resp.status_code in (200, 201):\n"
170
- " print('Supabase: Table sensor_telemetry created via API.')\n"
150
+ " r = requests.get(f'{FIREBASE_URL}/.json?shallow=true', timeout=5)\n"
151
+ " if r.status_code == 200:\n"
152
+ " print('Firebase: Connected. Tables are created automatically on first insert.')\n"
171
153
  " return True\n"
172
- " except Exception:\n"
173
- " pass\n"
174
- " conn_str = DATABASE_URL\n"
175
- " if not conn_str and DB_PASSWORD and ref:\n"
176
- " conn_str = f'postgresql://postgres:{DB_PASSWORD}@db.{ref}.supabase.co:5432/postgres'\n"
177
- " if conn_str:\n"
178
- " try:\n"
179
- " import psycopg2\n"
180
- " conn = psycopg2.connect(conn_str, connect_timeout=6)\n"
181
- " cur = conn.cursor()\n"
182
- " cur.execute(SQL_QUERY)\n"
183
- " conn.commit()\n"
184
- " cur.close()\n"
185
- " conn.close()\n"
186
- " print('Supabase: Table sensor_telemetry created via PostgreSQL.')\n"
187
- " return True\n"
188
154
  " except Exception as e:\n"
189
- " print(f'Supabase PostgreSQL note: {e}')\n"
190
- " print('Supabase: Table not found. To create via code, add SUPABASE_DB_PASSWORD in .env, or paste supabase_schema.sql in Supabase SQL editor.')\n"
191
- " return False\n\n"
155
+ " print('Firebase note:', e)\n"
156
+ " if SUPABASE_URL and 'your-project' not in SUPABASE_URL:\n"
157
+ " print('Supabase configured. Schema is in supabase_schema.sql.')\n"
158
+ " print('Local database: Ready (data/local_telemetry.json)')\n"
159
+ " return True\n\n"
192
160
  "if __name__ == '__main__':\n"
193
- " init_table()\n"
161
+ " check()\n"
194
162
  )
195
163
 
196
164
  # client_sender.py
@@ -11,18 +11,16 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
11
11
  "requests>=2.28.0\n"
12
12
  "python-dotenv>=1.0.0\n"
13
13
  "streamlit>=1.25.0\n"
14
- "psycopg2-binary>=2.9.0\n"
15
14
  )
16
15
 
17
16
  files[".env.example"] = (
18
17
  "MQTT_BROKER=broker.hivemq.com\n"
19
18
  "MQTT_PORT=1883\n"
20
19
  "MQTT_TOPIC=iot/lab/telemetry\n"
21
- "CLOUD_PROVIDER=supabase\n"
20
+ "CLOUD_PROVIDER=firebase\n"
21
+ "FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
22
22
  "SUPABASE_URL=https://your-project.supabase.co\n"
23
23
  "SUPABASE_KEY=your-supabase-key\n"
24
- "SUPABASE_DB_PASSWORD=your-database-password\n"
25
- "FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
26
24
  )
27
25
 
28
26
  files[".gitignore"] = (
@@ -57,22 +55,28 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
57
55
  "BROKER = os.getenv('MQTT_BROKER', 'broker.hivemq.com')\n"
58
56
  "PORT = int(os.getenv('MQTT_PORT', 1883))\n"
59
57
  "TOPIC = os.getenv('MQTT_TOPIC', 'iot/lab/telemetry')\n"
60
- "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'supabase').lower().strip()\n"
58
+ "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'firebase').lower().strip()\n"
59
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
61
60
  "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
62
61
  "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
63
- "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
64
62
  "LOCAL_BACKUP = os.path.join(os.path.dirname(__file__), 'data', 'local_mqtt_telemetry.json')\n\n"
65
- "def save_to_supabase(record):\n"
66
- " if not SUPABASE_URL or not SUPABASE_KEY or 'your-project' in SUPABASE_URL:\n"
67
- " return False\n"
68
- " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
69
- " resp = requests.post(f'{SUPABASE_URL}/rest/v1/mqtt_telemetry', json=record, headers=headers, timeout=5)\n"
70
- " return resp.status_code in (200, 201, 204)\n\n"
71
63
  "def save_to_firebase(record):\n"
72
64
  " if not FIREBASE_URL or 'your-firebase' in FIREBASE_URL:\n"
73
65
  " return False\n"
74
- " resp = requests.post(f'{FIREBASE_URL}/mqtt_telemetry.json', json=record, timeout=5)\n"
75
- " return resp.status_code == 200\n\n"
66
+ " try:\n"
67
+ " resp = requests.post(f'{FIREBASE_URL}/mqtt_telemetry.json', json=record, timeout=5)\n"
68
+ " return resp.status_code == 200\n"
69
+ " except Exception:\n"
70
+ " return False\n\n"
71
+ "def save_to_supabase(record):\n"
72
+ " if not SUPABASE_URL or not SUPABASE_KEY or 'your-project' in SUPABASE_URL:\n"
73
+ " return False\n"
74
+ " try:\n"
75
+ " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
76
+ " resp = requests.post(f'{SUPABASE_URL}/rest/v1/mqtt_telemetry', json=record, headers=headers, timeout=5)\n"
77
+ " return resp.status_code in (200, 201, 204)\n"
78
+ " except Exception:\n"
79
+ " return False\n\n"
76
80
  "def save_local(record):\n"
77
81
  " os.makedirs(os.path.dirname(LOCAL_BACKUP), exist_ok=True)\n"
78
82
  " data = []\n"
@@ -105,21 +109,16 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
105
109
  " 'created_at': datetime.now(timezone.utc).isoformat()\n"
106
110
  " }\n"
107
111
  " saved = False\n"
108
- " if CLOUD_PROVIDER == 'supabase':\n"
109
- " saved = save_to_supabase(record)\n"
110
- " elif CLOUD_PROVIDER == 'firebase':\n"
112
+ " if CLOUD_PROVIDER == 'firebase':\n"
111
113
  " saved = save_to_firebase(record)\n"
114
+ " elif CLOUD_PROVIDER == 'supabase':\n"
115
+ " saved = save_to_supabase(record)\n"
112
116
  " save_local(record)\n"
113
- " status = 'Cloud Stored' if saved else 'Saved Locally'\n"
117
+ " status = 'Stored in Cloud' if saved else 'Saved Locally'\n"
114
118
  " print(f\"[{datetime.now().strftime('%H:%M:%S')}] Received on {msg.topic}: Temp={temp}C, Hum={hum}% -> {status}\")\n"
115
119
  " except Exception as e:\n"
116
120
  " print('Message parse error:', e)\n\n"
117
121
  "def main():\n"
118
- " try:\n"
119
- " import init_db\n"
120
- " init_db.init_table()\n"
121
- " except Exception:\n"
122
- " pass\n"
123
122
  " client = mqtt.Client()\n"
124
123
  " client.on_connect = on_connect\n"
125
124
  " client.on_message = on_message\n"
@@ -132,69 +131,27 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
132
131
 
133
132
  files["init_db.py"] = (
134
133
  "import os\n"
135
- "import re\n"
136
134
  "import requests\n"
137
135
  "from dotenv import load_dotenv\n\n"
138
136
  "load_dotenv()\n\n"
139
- "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
140
- "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
141
- "DATABASE_URL = os.getenv('DATABASE_URL', '')\n"
142
- "DB_PASSWORD = os.getenv('SUPABASE_DB_PASSWORD', '')\n"
143
- "ACCESS_TOKEN = os.getenv('SUPABASE_ACCESS_TOKEN', '')\n\n"
144
- "SQL_QUERY = '''\n"
145
- "CREATE TABLE IF NOT EXISTS mqtt_telemetry (\n"
146
- " id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n"
147
- " created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,\n"
148
- " topic TEXT NOT NULL,\n"
149
- " sensor_id TEXT NOT NULL DEFAULT 'DHT22_01',\n"
150
- " temperature DOUBLE PRECISION NOT NULL,\n"
151
- " humidity DOUBLE PRECISION NOT NULL\n"
152
- ");\n"
153
- "ALTER TABLE mqtt_telemetry ENABLE ROW LEVEL SECURITY;\n"
154
- "CREATE POLICY \"Allow anon read\" ON mqtt_telemetry FOR SELECT USING (true);\n"
155
- "CREATE POLICY \"Allow anon insert\" ON mqtt_telemetry FOR INSERT WITH CHECK (true);\n"
156
- "'''\n\n"
157
- "def init_table():\n"
158
- " if not SUPABASE_URL or 'your-project' in SUPABASE_URL:\n"
159
- " return False\n"
160
- " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}'}\n"
161
- " try:\n"
162
- " r = requests.get(f'{SUPABASE_URL}/rest/v1/mqtt_telemetry?limit=1', headers=headers, timeout=4)\n"
163
- " if r.status_code == 200:\n"
164
- " print('Supabase: Table mqtt_telemetry is ready.')\n"
165
- " return True\n"
166
- " except Exception:\n"
167
- " pass\n"
168
- " m = re.search(r'https://([a-z0-9_-]+)\\.supabase\\.co', SUPABASE_URL)\n"
169
- " ref = m.group(1) if m else None\n"
170
- " if ACCESS_TOKEN and ref:\n"
137
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
138
+ "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n\n"
139
+ "def check():\n"
140
+ " print('Database Check:')\n"
141
+ " if FIREBASE_URL and 'your-project' not in FIREBASE_URL:\n"
171
142
  " try:\n"
172
- " resp = requests.post(f'https://api.supabase.com/v1/projects/{ref}/database/query', json={'query': SQL_QUERY}, headers={'Authorization': f'Bearer {ACCESS_TOKEN}', 'Content-Type': 'application/json'}, timeout=8)\n"
173
- " if resp.status_code in (200, 201):\n"
174
- " print('Supabase: Table mqtt_telemetry created via API.')\n"
143
+ " r = requests.get(f'{FIREBASE_URL}/.json?shallow=true', timeout=5)\n"
144
+ " if r.status_code == 200:\n"
145
+ " print('Firebase: Connected. Tables are created automatically on first insert.')\n"
175
146
  " return True\n"
176
- " except Exception:\n"
177
- " pass\n"
178
- " conn_str = DATABASE_URL\n"
179
- " if not conn_str and DB_PASSWORD and ref:\n"
180
- " conn_str = f'postgresql://postgres:{DB_PASSWORD}@db.{ref}.supabase.co:5432/postgres'\n"
181
- " if conn_str:\n"
182
- " try:\n"
183
- " import psycopg2\n"
184
- " conn = psycopg2.connect(conn_str, connect_timeout=6)\n"
185
- " cur = conn.cursor()\n"
186
- " cur.execute(SQL_QUERY)\n"
187
- " conn.commit()\n"
188
- " cur.close()\n"
189
- " conn.close()\n"
190
- " print('Supabase: Table mqtt_telemetry created via PostgreSQL.')\n"
191
- " return True\n"
192
147
  " except Exception as e:\n"
193
- " print(f'Supabase PostgreSQL note: {e}')\n"
194
- " print('Supabase: Table not found. To create via code, add SUPABASE_DB_PASSWORD in .env, or paste supabase_schema.sql in Supabase SQL editor.')\n"
195
- " return False\n\n"
148
+ " print('Firebase note:', e)\n"
149
+ " if SUPABASE_URL and 'your-project' not in SUPABASE_URL:\n"
150
+ " print('Supabase configured. Schema is in supabase_schema.sql.')\n"
151
+ " print('Local database: Ready (data/local_mqtt_telemetry.json)')\n"
152
+ " return True\n\n"
196
153
  "if __name__ == '__main__':\n"
197
- " init_table()\n"
154
+ " check()\n"
198
155
  )
199
156
 
200
157
  # publisher.py
@@ -11,7 +11,6 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
11
11
  "requests>=2.28.0\n"
12
12
  "python-dotenv>=1.0.0\n"
13
13
  "streamlit>=1.25.0\n"
14
- "psycopg2-binary>=2.9.0\n"
15
14
  )
16
15
 
17
16
  files[".env.example"] = (
@@ -19,11 +18,10 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
19
18
  "LONGITUDE=76.3419\n"
20
19
  "COAP_HOST=127.0.0.1\n"
21
20
  "COAP_PORT=56830\n"
22
- "CLOUD_PROVIDER=supabase\n"
21
+ "CLOUD_PROVIDER=firebase\n"
22
+ "FIREBASE_DATABASE_URL=https://your-firebase-project.firebaseio.com\n"
23
23
  "SUPABASE_URL=https://your-project-ref.supabase.co\n"
24
24
  "SUPABASE_KEY=your-supabase-anon-key\n"
25
- "SUPABASE_DB_PASSWORD=your-database-password\n"
26
- "FIREBASE_DATABASE_URL=https://your-firebase-project.firebaseio.com\n"
27
25
  )
28
26
 
29
27
  files[".gitignore"] = (
@@ -145,22 +143,28 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
145
143
  "load_dotenv()\n\n"
146
144
  "COAP_HOST = os.getenv('COAP_HOST', '127.0.0.1')\n"
147
145
  "COAP_PORT = int(os.getenv('COAP_PORT', 56830))\n"
148
- "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'supabase').lower().strip()\n"
146
+ "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'firebase').lower().strip()\n"
147
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
149
148
  "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
150
149
  "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
151
- "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
152
150
  "LOCAL_BACKUP = os.path.join(os.path.dirname(__file__), 'data', 'local_coap_env.json')\n\n"
153
- "def save_to_supabase(record):\n"
154
- " if not SUPABASE_URL or not SUPABASE_KEY or 'your-project' in SUPABASE_URL:\n"
155
- " return False\n"
156
- " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
157
- " resp = requests.post(f'{SUPABASE_URL}/rest/v1/coap_environmental_records', json=record, headers=headers, timeout=5)\n"
158
- " return resp.status_code in (200, 201, 204)\n\n"
159
151
  "def save_to_firebase(record):\n"
160
152
  " if not FIREBASE_URL or 'your-firebase' in FIREBASE_URL:\n"
161
153
  " return False\n"
162
- " resp = requests.post(f'{FIREBASE_URL}/coap_environmental_records.json', json=record, timeout=5)\n"
163
- " return resp.status_code == 200\n\n"
154
+ " try:\n"
155
+ " resp = requests.post(f'{FIREBASE_URL}/coap_environmental_records.json', json=record, timeout=5)\n"
156
+ " return resp.status_code == 200\n"
157
+ " except Exception:\n"
158
+ " return False\n\n"
159
+ "def save_to_supabase(record):\n"
160
+ " if not SUPABASE_URL or not SUPABASE_KEY or 'your-project' in SUPABASE_URL:\n"
161
+ " return False\n"
162
+ " try:\n"
163
+ " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}', 'Content-Type': 'application/json'}\n"
164
+ " resp = requests.post(f'{SUPABASE_URL}/rest/v1/coap_environmental_records', json=record, headers=headers, timeout=5)\n"
165
+ " return resp.status_code in (200, 201, 204)\n"
166
+ " except Exception:\n"
167
+ " return False\n\n"
164
168
  "def save_locally(record):\n"
165
169
  " os.makedirs(os.path.dirname(LOCAL_BACKUP), exist_ok=True)\n"
166
170
  " records = []\n"
@@ -174,11 +178,6 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
174
178
  " with open(LOCAL_BACKUP, 'w', encoding='utf-8') as f:\n"
175
179
  " json.dump(records, f, indent=2)\n\n"
176
180
  "def main():\n"
177
- " try:\n"
178
- " import init_db\n"
179
- " init_db.init_table()\n"
180
- " except Exception:\n"
181
- " pass\n"
182
181
  " parser = argparse.ArgumentParser()\n"
183
182
  " parser.add_argument('--host', default=COAP_HOST)\n"
184
183
  " parser.add_argument('--port', type=int, default=COAP_PORT)\n"
@@ -200,12 +199,12 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
200
199
  " print(f\" O3 : {data.get('ozone')} ug/m3\")\n"
201
200
  " print(f\" European AQI : {data.get('european_aqi')}\")\n\n"
202
201
  " cloud_ok = False\n"
203
- " if CLOUD_PROVIDER == 'supabase':\n"
204
- " cloud_ok = save_to_supabase(data)\n"
205
- " elif CLOUD_PROVIDER == 'firebase':\n"
202
+ " if CLOUD_PROVIDER == 'firebase':\n"
206
203
  " cloud_ok = save_to_firebase(data)\n"
204
+ " elif CLOUD_PROVIDER == 'supabase':\n"
205
+ " cloud_ok = save_to_supabase(data)\n"
207
206
  " save_locally(data)\n\n"
208
- " status_msg = 'Stored in cloud' if cloud_ok else 'Saved locally'\n"
207
+ " status_msg = 'Stored in Cloud' if cloud_ok else 'Saved locally'\n"
209
208
  " print(f\"Status: {status_msg}\")\n"
210
209
  " except Exception as err:\n"
211
210
  " print(f\"Request failed: {err}\")\n\n"
@@ -215,75 +214,27 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
215
214
 
216
215
  files["init_db.py"] = (
217
216
  "import os\n"
218
- "import re\n"
219
217
  "import requests\n"
220
218
  "from dotenv import load_dotenv\n\n"
221
219
  "load_dotenv()\n\n"
222
- "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
223
- "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
224
- "DATABASE_URL = os.getenv('DATABASE_URL', '')\n"
225
- "DB_PASSWORD = os.getenv('SUPABASE_DB_PASSWORD', '')\n"
226
- "ACCESS_TOKEN = os.getenv('SUPABASE_ACCESS_TOKEN', '')\n\n"
227
- "SQL_QUERY = '''\n"
228
- "CREATE TABLE IF NOT EXISTS coap_environmental_records (\n"
229
- " id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n"
230
- " created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,\n"
231
- " latitude DOUBLE PRECISION NOT NULL,\n"
232
- " longitude DOUBLE PRECISION NOT NULL,\n"
233
- " temperature DOUBLE PRECISION,\n"
234
- " humidity DOUBLE PRECISION,\n"
235
- " pm10 DOUBLE PRECISION,\n"
236
- " pm2_5 DOUBLE PRECISION,\n"
237
- " carbon_monoxide DOUBLE PRECISION,\n"
238
- " nitrogen_dioxide DOUBLE PRECISION,\n"
239
- " ozone DOUBLE PRECISION,\n"
240
- " european_aqi INTEGER\n"
241
- ");\n"
242
- "ALTER TABLE coap_environmental_records ENABLE ROW LEVEL SECURITY;\n"
243
- "CREATE POLICY \"Allow anon read\" ON coap_environmental_records FOR SELECT USING (true);\n"
244
- "CREATE POLICY \"Allow anon insert\" ON coap_environmental_records FOR INSERT WITH CHECK (true);\n"
245
- "'''\n\n"
246
- "def init_table():\n"
247
- " if not SUPABASE_URL or 'your-project' in SUPABASE_URL:\n"
248
- " return False\n"
249
- " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}'}\n"
250
- " try:\n"
251
- " r = requests.get(f'{SUPABASE_URL}/rest/v1/coap_environmental_records?limit=1', headers=headers, timeout=4)\n"
252
- " if r.status_code == 200:\n"
253
- " print('Supabase: Table coap_environmental_records is ready.')\n"
254
- " return True\n"
255
- " except Exception:\n"
256
- " pass\n"
257
- " m = re.search(r'https://([a-z0-9_-]+)\\.supabase\\.co', SUPABASE_URL)\n"
258
- " ref = m.group(1) if m else None\n"
259
- " if ACCESS_TOKEN and ref:\n"
220
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
221
+ "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n\n"
222
+ "def check():\n"
223
+ " print('Database Check:')\n"
224
+ " if FIREBASE_URL and 'your-firebase' not in FIREBASE_URL:\n"
260
225
  " try:\n"
261
- " resp = requests.post(f'https://api.supabase.com/v1/projects/{ref}/database/query', json={'query': SQL_QUERY}, headers={'Authorization': f'Bearer {ACCESS_TOKEN}', 'Content-Type': 'application/json'}, timeout=8)\n"
262
- " if resp.status_code in (200, 201):\n"
263
- " print('Supabase: Table coap_environmental_records created via API.')\n"
226
+ " r = requests.get(f'{FIREBASE_URL}/.json?shallow=true', timeout=5)\n"
227
+ " if r.status_code == 200:\n"
228
+ " print('Firebase: Connected. Tables are created automatically on first insert.')\n"
264
229
  " return True\n"
265
- " except Exception:\n"
266
- " pass\n"
267
- " conn_str = DATABASE_URL\n"
268
- " if not conn_str and DB_PASSWORD and ref:\n"
269
- " conn_str = f'postgresql://postgres:{DB_PASSWORD}@db.{ref}.supabase.co:5432/postgres'\n"
270
- " if conn_str:\n"
271
- " try:\n"
272
- " import psycopg2\n"
273
- " conn = psycopg2.connect(conn_str, connect_timeout=6)\n"
274
- " cur = conn.cursor()\n"
275
- " cur.execute(SQL_QUERY)\n"
276
- " conn.commit()\n"
277
- " cur.close()\n"
278
- " conn.close()\n"
279
- " print('Supabase: Table coap_environmental_records created via PostgreSQL.')\n"
280
- " return True\n"
281
230
  " except Exception as e:\n"
282
- " print(f'Supabase PostgreSQL note: {e}')\n"
283
- " print('Supabase: Table not found. To create via code, add SUPABASE_DB_PASSWORD in .env, or paste supabase_schema.sql in Supabase SQL editor.')\n"
284
- " return False\n\n"
231
+ " print('Firebase note:', e)\n"
232
+ " if SUPABASE_URL and 'your-project' not in SUPABASE_URL:\n"
233
+ " print('Supabase configured. Schema is in supabase_schema.sql.')\n"
234
+ " print('Local database: Ready (data/local_coap_env.json)')\n"
235
+ " return True\n\n"
285
236
  "if __name__ == '__main__':\n"
286
- " init_table()\n"
237
+ " check()\n"
287
238
  )
288
239
 
289
240
  files["dashboard.py"] = (
@@ -294,11 +245,21 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
294
245
  "from dotenv import load_dotenv\n\n"
295
246
  "load_dotenv()\n\n"
296
247
  "st.title('CoAP Environmental Monitoring')\n\n"
297
- "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'supabase').lower().strip()\n"
248
+ "CLOUD_PROVIDER = os.getenv('CLOUD_PROVIDER', 'firebase').lower().strip()\n"
249
+ "FIREBASE_URL = os.getenv('FIREBASE_DATABASE_URL', '').rstrip('/')\n"
298
250
  "SUPABASE_URL = os.getenv('SUPABASE_URL', '').rstrip('/')\n"
299
251
  "SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')\n"
300
252
  "LOCAL_BACKUP = os.path.join(os.path.dirname(__file__), 'data', 'local_coap_env.json')\n\n"
301
253
  "def fetch_records():\n"
254
+ " if CLOUD_PROVIDER == 'firebase' and FIREBASE_URL and 'your-firebase' not in FIREBASE_URL:\n"
255
+ " try:\n"
256
+ " resp = requests.get(f'{FIREBASE_URL}/coap_environmental_records.json', timeout=4)\n"
257
+ " if resp.status_code == 200 and resp.json():\n"
258
+ " data = resp.json()\n"
259
+ " if isinstance(data, dict):\n"
260
+ " return list(reversed(list(data.values())))\n"
261
+ " except Exception:\n"
262
+ " pass\n"
302
263
  " if CLOUD_PROVIDER == 'supabase' and SUPABASE_URL and 'your-project' not in SUPABASE_URL:\n"
303
264
  " try:\n"
304
265
  " headers = {'apikey': SUPABASE_KEY, 'Authorization': f'Bearer {SUPABASE_KEY}'}\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iotlabkit
3
- Version: 1.0.2
3
+ Version: 1.0.3
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
File without changes
File without changes
File without changes
File without changes