iotlabkit 1.0.1__tar.gz → 1.0.2__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.
- {iotlabkit-1.0.1/src/iotlabkit.egg-info → iotlabkit-1.0.2}/PKG-INFO +1 -1
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/pyproject.toml +1 -1
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/__init__.py +1 -1
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/registry.py +3 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_05.py +73 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_06.py +74 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_10.py +80 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2/src/iotlabkit.egg-info}/PKG-INFO +1 -1
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/LICENSE +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/README.md +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/setup.cfg +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/cli.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/coap_util.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/data/beijing_air/beijing_sample.csv +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/data/india_air/city_day_sample.csv +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/data/occupancy/datatraining.txt +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/doctor.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/generator.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_07.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_08.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_09.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_11.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_12.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_13.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_14.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_15.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_16.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit/templates/program_17.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit.egg-info/SOURCES.txt +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit.egg-info/dependency_links.txt +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit.egg-info/entry_points.txt +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit.egg-info/requires.txt +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/src/iotlabkit.egg-info/top_level.txt +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/tests/test_api_parsing.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/tests/test_cli.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/tests/test_coap_util.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/tests/test_doctor.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/tests/test_generator.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/tests/test_ml_pipeline.py +0 -0
- {iotlabkit-1.0.1 → iotlabkit-1.0.2}/tests/test_registry.py +0 -0
|
@@ -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.
|
|
7
|
+
__version__ = "1.0.2"
|
|
8
8
|
__author__ = "IoT Lab Contributors"
|
|
9
9
|
__license__ = "MIT"
|
|
10
10
|
|
|
@@ -81,6 +81,7 @@ PROGRAMS: Dict[int, ProgramInfo] = {
|
|
|
81
81
|
"dashboard.py",
|
|
82
82
|
"supabase_schema.sql",
|
|
83
83
|
"firebase_setup.md",
|
|
84
|
+
"init_db.py",
|
|
84
85
|
],
|
|
85
86
|
prerequisites=[
|
|
86
87
|
"Python 3.9+",
|
|
@@ -130,6 +131,7 @@ PROGRAMS: Dict[int, ProgramInfo] = {
|
|
|
130
131
|
"publisher.py",
|
|
131
132
|
"dashboard.py",
|
|
132
133
|
"supabase_schema.sql",
|
|
134
|
+
"init_db.py",
|
|
133
135
|
],
|
|
134
136
|
prerequisites=[
|
|
135
137
|
"Python 3.9+",
|
|
@@ -323,6 +325,7 @@ PROGRAMS: Dict[int, ProgramInfo] = {
|
|
|
323
325
|
"dashboard.py",
|
|
324
326
|
"supabase_schema.sql",
|
|
325
327
|
"coap_core.py",
|
|
328
|
+
"init_db.py",
|
|
326
329
|
],
|
|
327
330
|
prerequisites=[
|
|
328
331
|
"Python 3.9+",
|
|
@@ -11,6 +11,7 @@ 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"
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
files[".env.example"] = (
|
|
@@ -19,6 +20,7 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
19
20
|
"CLOUD_PROVIDER=supabase\n"
|
|
20
21
|
"SUPABASE_URL=https://your-project.supabase.co\n"
|
|
21
22
|
"SUPABASE_KEY=your-supabase-key\n"
|
|
23
|
+
"SUPABASE_DB_PASSWORD=your-database-password\n"
|
|
22
24
|
"FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
|
|
23
25
|
)
|
|
24
26
|
|
|
@@ -116,10 +118,81 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
116
118
|
" return jsonify(list(reversed(data[-50:]))), 200\n"
|
|
117
119
|
" return jsonify([]), 200\n\n"
|
|
118
120
|
"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"
|
|
119
126
|
" print(f'Starting REST API server on http://{HOST}:{PORT}')\n"
|
|
120
127
|
" app.run(host=HOST, port=PORT, debug=False)\n"
|
|
121
128
|
)
|
|
122
129
|
|
|
130
|
+
files["init_db.py"] = (
|
|
131
|
+
"import os\n"
|
|
132
|
+
"import re\n"
|
|
133
|
+
"import requests\n"
|
|
134
|
+
"from dotenv import load_dotenv\n\n"
|
|
135
|
+
"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"
|
|
167
|
+
" 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"
|
|
171
|
+
" 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
|
+
" 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"
|
|
192
|
+
"if __name__ == '__main__':\n"
|
|
193
|
+
" init_table()\n"
|
|
194
|
+
)
|
|
195
|
+
|
|
123
196
|
# client_sender.py
|
|
124
197
|
files["client_sender.py"] = (
|
|
125
198
|
"import argparse\n"
|
|
@@ -11,6 +11,7 @@ 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"
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
files[".env.example"] = (
|
|
@@ -20,6 +21,7 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
20
21
|
"CLOUD_PROVIDER=supabase\n"
|
|
21
22
|
"SUPABASE_URL=https://your-project.supabase.co\n"
|
|
22
23
|
"SUPABASE_KEY=your-supabase-key\n"
|
|
24
|
+
"SUPABASE_DB_PASSWORD=your-database-password\n"
|
|
23
25
|
"FIREBASE_DATABASE_URL=https://your-project.firebaseio.com\n"
|
|
24
26
|
)
|
|
25
27
|
|
|
@@ -113,6 +115,11 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
113
115
|
" except Exception as e:\n"
|
|
114
116
|
" print('Message parse error:', e)\n\n"
|
|
115
117
|
"def main():\n"
|
|
118
|
+
" try:\n"
|
|
119
|
+
" import init_db\n"
|
|
120
|
+
" init_db.init_table()\n"
|
|
121
|
+
" except Exception:\n"
|
|
122
|
+
" pass\n"
|
|
116
123
|
" client = mqtt.Client()\n"
|
|
117
124
|
" client.on_connect = on_connect\n"
|
|
118
125
|
" client.on_message = on_message\n"
|
|
@@ -123,6 +130,73 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
123
130
|
" main()\n"
|
|
124
131
|
)
|
|
125
132
|
|
|
133
|
+
files["init_db.py"] = (
|
|
134
|
+
"import os\n"
|
|
135
|
+
"import re\n"
|
|
136
|
+
"import requests\n"
|
|
137
|
+
"from dotenv import load_dotenv\n\n"
|
|
138
|
+
"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"
|
|
171
|
+
" 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"
|
|
175
|
+
" 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
|
+
" 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"
|
|
196
|
+
"if __name__ == '__main__':\n"
|
|
197
|
+
" init_table()\n"
|
|
198
|
+
)
|
|
199
|
+
|
|
126
200
|
# publisher.py
|
|
127
201
|
files["publisher.py"] = (
|
|
128
202
|
"import argparse\n"
|
|
@@ -11,6 +11,7 @@ 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"
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
files[".env.example"] = (
|
|
@@ -21,6 +22,7 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
21
22
|
"CLOUD_PROVIDER=supabase\n"
|
|
22
23
|
"SUPABASE_URL=https://your-project-ref.supabase.co\n"
|
|
23
24
|
"SUPABASE_KEY=your-supabase-anon-key\n"
|
|
25
|
+
"SUPABASE_DB_PASSWORD=your-database-password\n"
|
|
24
26
|
"FIREBASE_DATABASE_URL=https://your-firebase-project.firebaseio.com\n"
|
|
25
27
|
)
|
|
26
28
|
|
|
@@ -172,6 +174,11 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
172
174
|
" with open(LOCAL_BACKUP, 'w', encoding='utf-8') as f:\n"
|
|
173
175
|
" json.dump(records, f, indent=2)\n\n"
|
|
174
176
|
"def main():\n"
|
|
177
|
+
" try:\n"
|
|
178
|
+
" import init_db\n"
|
|
179
|
+
" init_db.init_table()\n"
|
|
180
|
+
" except Exception:\n"
|
|
181
|
+
" pass\n"
|
|
175
182
|
" parser = argparse.ArgumentParser()\n"
|
|
176
183
|
" parser.add_argument('--host', default=COAP_HOST)\n"
|
|
177
184
|
" parser.add_argument('--port', type=int, default=COAP_PORT)\n"
|
|
@@ -206,6 +213,79 @@ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
|
|
|
206
213
|
" main()\n"
|
|
207
214
|
)
|
|
208
215
|
|
|
216
|
+
files["init_db.py"] = (
|
|
217
|
+
"import os\n"
|
|
218
|
+
"import re\n"
|
|
219
|
+
"import requests\n"
|
|
220
|
+
"from dotenv import load_dotenv\n\n"
|
|
221
|
+
"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"
|
|
260
|
+
" 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"
|
|
264
|
+
" 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
|
+
" 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"
|
|
285
|
+
"if __name__ == '__main__':\n"
|
|
286
|
+
" init_table()\n"
|
|
287
|
+
)
|
|
288
|
+
|
|
209
289
|
files["dashboard.py"] = (
|
|
210
290
|
"import os\n"
|
|
211
291
|
"import json\n"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|