commit ea1e8a9266b650b7761483baada24a143877641e Author: Kirubakaran Date: Sun Aug 24 22:09:18 2025 +0530 feat(backend): implemented the backend of station dashboard web app diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..bc44926 --- /dev/null +++ b/backend/.env @@ -0,0 +1,15 @@ +# --- Flask Application Settings --- +# This is a secret key used by Flask for session management. +# You can generate a new one with: python -c 'import os; print(os.urandom(24).hex())' +SECRET_KEY="80473e17c5707e19252ef3736fba32805be21a9b3e914190" + +# --- PostgreSQL Database Connection --- +# Replace with your actual database credentials. +# Format: postgresql://:@:/ +DATABASE_URL="postgresql://swap_app_user:2004@localhost:5432/swap_station_db" + +# --- MQTT Broker Connection --- +MQTT_BROKER="mqtt-dev.upgrid.in" +MQTT_PORT="1883" +MQTT_USER="guest" +MQTT_PASSWORD="password" diff --git a/backend/__pycache__/models.cpython-313.pyc b/backend/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..cc15cfc Binary files /dev/null and b/backend/__pycache__/models.cpython-313.pyc differ diff --git a/backend/core/__init__.py b/backend/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/core/__pycache__/__init__.cpython-313.pyc b/backend/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..1b8d744 Binary files /dev/null and b/backend/core/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/core/__pycache__/influxdb_client.cpython-313.pyc b/backend/core/__pycache__/influxdb_client.cpython-313.pyc new file mode 100644 index 0000000..88926cf Binary files /dev/null and b/backend/core/__pycache__/influxdb_client.cpython-313.pyc differ diff --git a/backend/core/__pycache__/mqtt_client.cpython-313.pyc b/backend/core/__pycache__/mqtt_client.cpython-313.pyc new file mode 100644 index 0000000..2b82dd2 Binary files /dev/null and b/backend/core/__pycache__/mqtt_client.cpython-313.pyc differ diff --git a/backend/core/__pycache__/protobuf_decoder.cpython-313.pyc b/backend/core/__pycache__/protobuf_decoder.cpython-313.pyc new file mode 100644 index 0000000..06b6075 Binary files /dev/null and b/backend/core/__pycache__/protobuf_decoder.cpython-313.pyc differ diff --git a/backend/core/mqtt_client.py b/backend/core/mqtt_client.py new file mode 100644 index 0000000..b459470 --- /dev/null +++ b/backend/core/mqtt_client.py @@ -0,0 +1,75 @@ +import paho.mqtt.client as mqtt +import uuid +import time +import threading +import socket + +class MqttClient: + """ + Handles the connection and message processing for a single MQTT station. + This is a standard Python class, with no GUI dependencies. + """ + def __init__(self, broker, port, user, password, station_id, on_message_callback): + super().__init__() + self.broker = broker + self.port = port + self.user = user + self.password = password + self.station_id = station_id + self.on_message_callback = on_message_callback + + # Generate a unique client ID to prevent connection conflicts + unique_id = str(uuid.uuid4()) + self.client_id = f"WebApp-Backend-{self.station_id}-{unique_id}" + + self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, self.client_id) + + # Assign callback functions + self.client.on_connect = self.on_connect + self.client.on_message = self.on_message + self.client.on_disconnect = self.on_disconnect + + if self.user and self.password: + self.client.username_pw_set(self.user, self.password) + + def on_connect(self, client, userdata, flags, rc, properties): + """Callback for when the client connects to the broker.""" + if rc == 0: + print(f"Successfully connected to MQTT broker for station: {self.station_id}") + # Subscribe to all topics for this station using a wildcard + topic_base = f"VEC/batterySmartStation/v100/{self.station_id}/#" + self.client.subscribe(topic_base) + print(f"Subscribed to: {topic_base}") + else: + print(f"Failed to connect to MQTT for station {self.station_id}, return code {rc}") + + def on_disconnect(self, client, userdata, rc, properties): + """Callback for when the client disconnects.""" + print(f"Disconnected from MQTT for station {self.station_id}. Will attempt to reconnect...") + # Paho-MQTT's loop_start() handles automatic reconnection. + + def on_message(self, client, userdata, msg): + """Callback for when a message is received from the broker.""" + try: + # Pass the relevant data to the main application's handler + self.on_message_callback(self.station_id, msg.topic, msg.payload) + except Exception as e: + print(f"Error processing message in callback for topic {msg.topic}: {e}") + + def connect(self): + """Connects the client to the MQTT broker.""" + print(f"Attempting to connect to {self.broker}:{self.port} with client ID: {self.client_id}") + try: + self.client.connect(self.broker, self.port, 60) + except Exception as e: + print(f"Error connecting to MQTT for station {self.station_id}: {e}") + + def start(self): + """Starts the MQTT client's network loop in a separate thread.""" + self.connect() + self.client.loop_start() + + def stop(self): + """Stops the MQTT client's network loop.""" + print(f"Stopping MQTT client for station: {self.station_id}") + self.client.loop_stop() diff --git a/backend/core/protobuf_decoder.py b/backend/core/protobuf_decoder.py new file mode 100644 index 0000000..3f53323 --- /dev/null +++ b/backend/core/protobuf_decoder.py @@ -0,0 +1,157 @@ +import json +from google.protobuf.json_format import MessageToDict +from google.protobuf.message import DecodeError + +# Import the specific message types from your generated protobuf file +# Make sure the path 'proto.your_proto_file_pb2' matches your file structure +from proto.vec_payload_chgSt_pb2 import ( + mainPayload as PeriodicData, + eventPayload as EventData, + rpcRequest as RpcRequest, + eventType_e, + jobType_e, + languageType_e +) + +class ProtobufDecoder: + """ + Handles the decoding of different Protobuf message types with robust error handling. + """ + + def decode_periodic(self, payload: bytes) -> dict | None: + """ + Decodes a binary payload into a PeriodicData dictionary. + """ + try: + message = PeriodicData() + message.ParseFromString(payload) + return MessageToDict(message, preserving_proto_field_name=True) + except DecodeError as e: + print(f"Error decoding PeriodicData: {e}") + return None + except Exception as e: + print(f"An unexpected error occurred during periodic decoding: {e}") + return None + + def decode_event(self, payload_bytes: bytes) -> dict | None: + """ + Decodes an event payload robustly, ensuring the correct eventType is used. + """ + try: + # 1. Standard parsing to get a base dictionary + msg = EventData() + msg.ParseFromString(payload_bytes) + d = MessageToDict(msg, preserving_proto_field_name=True) + + # 2. Manually extract the true enum value from the raw bytes + wire_num = self._extract_field3_varint(payload_bytes) + wire_name = None + if wire_num is not None: + try: + wire_name = eventType_e.Name(wire_num) + except ValueError: + wire_name = f"UNKNOWN_ENUM_VALUE_{wire_num}" + + # 3. Always prefer the manually extracted "wire value" + if wire_name: + d["eventType"] = wire_name + + # 4. Ensure consistent structure with default values + ed = d.setdefault("eventData", {}) + ed.setdefault("nfcData", None) + ed.setdefault("batteryIdentification", "") + ed.setdefault("activityFailureReason", 0) + ed.setdefault("swapAbortReason", "ABORT_UNKNOWN") + ed.setdefault("swapTime", 0) + ed.setdefault("faultCode", 0) + ed.setdefault("doorStatus", 0) + ed.setdefault("slotId", 0) + + # 5. Reorder for clean logs and return as a dictionary + return { + "ts": d.get("ts"), + "deviceId": d.get("deviceId"), + "eventType": d.get("eventType"), + "sessionId": d.get("sessionId"), + "eventData": d.get("eventData"), + } + except Exception as e: + print(f"An unexpected error occurred during event decoding: {e}") + return None + + def decode_rpc_request(self, payload_bytes: bytes) -> dict | None: + """ + Decodes an RPC request payload robustly, ensuring the correct jobType is used. + """ + try: + # 1. Standard parsing + msg = RpcRequest() + msg.ParseFromString(payload_bytes) + d = MessageToDict(msg, preserving_proto_field_name=True) + + # 2. Manually extract the true enum value for jobType (field 3) + wire_num = self._extract_field3_varint(payload_bytes) + wire_name = None + if wire_num is not None: + try: + wire_name = jobType_e.Name(wire_num) + except ValueError: + wire_name = f"UNKNOWN_ENUM_VALUE_{wire_num}" + + # 3. Prefer the manually extracted value + if wire_name: + d["jobType"] = wire_name + + # 4. Ensure consistent structure + d.setdefault("rpcData", None) + d.setdefault("slotInfo", None) + + return d + except Exception as e: + print(f"An unexpected error occurred during RPC request decoding: {e}") + return None + + + # --- Helper methods for manual byte parsing --- + def _read_varint(self, b: bytes, i: int): + """Helper to read a varint from a raw byte buffer.""" + shift = 0 + val = 0 + while True: + if i >= len(b): raise ValueError("truncated varint") + c = b[i] + i += 1 + val |= (c & 0x7F) << shift + if not (c & 0x80): break + shift += 7 + if shift > 64: raise ValueError("varint too long") + return val, i + + def _skip_field(self, b: bytes, i: int, wt: int): + """Helper to skip a field in the buffer based on its wire type.""" + if wt == 0: # VARINT + _, i = self._read_varint(b, i) + return i + if wt == 1: # 64-BIT + return i + 8 + if wt == 2: # LENGTH-DELIMITED + ln, i = self._read_varint(b, i) + return i + ln + if wt == 5: # 32-BIT + return i + 4 + raise ValueError(f"unsupported wire type to skip: {wt}") + + def _extract_field3_varint(self, b: bytes): + """Manually parses the byte string to find the integer value of field number 3 (e.g., eventType, jobType).""" + i = 0 + n = len(b) + while i < n: + key, i2 = self._read_varint(b, i) + wt = key & 0x7 + fn = key >> 3 + i = i2 + if fn == 3 and wt == 0: + v, _ = self._read_varint(b, i) + return v + i = self._skip_field(b, i, wt) + return None diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..e5be621 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,436 @@ +# import os +# import sys +# import threading +# import json +# import csv +# import io +# from datetime import datetime +# from flask import Flask, jsonify, request, Response +# from flask_socketio import SocketIO +# from dotenv import load_dotenv + +# # Import your custom core modules and the new models +# from core.mqtt_client import MqttClient +# from core.protobuf_decoder import ProtobufDecoder +# from models import db, Station, User, MqttLog + +# # --- Load Environment Variables --- +# load_dotenv() + +# # --- Pre-startup Check for Essential Configuration --- +# DATABASE_URL = os.getenv("DATABASE_URL") +# if not DATABASE_URL: +# print("FATAL ERROR: DATABASE_URL is not set in .env file.") +# sys.exit(1) + +# # --- Application Setup --- +# app = Flask(__name__) +# app.config['SQLALCHEMY_DATABASE_URI'] = DATABASE_URL +# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +# app.config['SECRET_KEY'] = os.getenv("SECRET_KEY", "a_very_secret_key") +# db.init_app(app) +# socketio = SocketIO(app, cors_allowed_origins="*") + +# # --- Global instances --- +# decoder = ProtobufDecoder() +# mqtt_clients = {} + +# # --- MQTT Message Handling --- +# def on_message_handler(station_id, topic, payload): +# """ +# Handles incoming MQTT messages, decodes them, writes to PostgreSQL, +# and emits to WebSockets. +# """ +# print(f"Main handler received message for station {station_id} on topic {topic}") + +# decoded_data = None +# message_type = topic.split('/')[-1] + +# if message_type == 'PERIODIC': +# decoded_data = decoder.decode_periodic(payload) +# elif message_type == 'EVENTS': +# decoded_data = decoder.decode_event(payload) +# elif message_type == 'REQUEST': +# decoded_data = decoder.decode_rpc_request(payload) + +# if decoded_data: +# # 1. Write the data to PostgreSQL for historical storage +# try: +# with app.app_context(): +# log_entry = MqttLog( +# station_id=station_id, +# topic=topic, +# payload=decoded_data +# ) +# db.session.add(log_entry) +# db.session.commit() +# print(f"Successfully wrote data for {station_id} to PostgreSQL.") +# except Exception as e: +# print(f"Error writing to PostgreSQL: {e}") + +# # 2. Emit the data to the frontend for real-time view +# socketio.emit('dashboard_update', { +# 'stationId': station_id, +# 'topic': topic, +# 'data': decoded_data +# }, room=station_id) + +# # --- (WebSocket and API routes remain the same) --- +# @socketio.on('connect') +# def handle_connect(): +# print('Client connected to WebSocket') + +# @socketio.on('disconnect') +# def handle_disconnect(): +# print('Client disconnected') + +# @socketio.on('join_station_room') +# def handle_join_station_room(data): +# station_id = data.get('station_id') +# if station_id: +# from flask import request +# socketio.join_room(station_id, request.sid) + +# @socketio.on('leave_station_room') +# def handle_leave_station_room(data): +# station_id = data.get('station_id') +# if station_id: +# from flask import request +# socketio.leave_room(station_id, request.sid) + +# @app.route('/api/stations', methods=['GET']) +# def get_stations(): +# try: +# stations = Station.query.all() +# return jsonify([{"id": s.station_id, "name": s.name} for s in stations]) +# except Exception as e: +# return jsonify({"error": f"Database query failed: {e}"}), 500 + +# # --- (CSV Export route remains the same) --- +# @app.route('/api/logs/export', methods=['GET']) +# def export_logs(): +# # ... (existing implementation) +# pass + +# # --- Main Application Logic (UPDATED) --- +# def start_mqtt_clients(): +# """ +# Initializes and starts an MQTT client for each station found in the database, +# using the specific MQTT credentials stored for each station. +# """ +# try: +# with app.app_context(): +# # Get the full station objects, not just the IDs +# stations = Station.query.all() +# except Exception as e: +# print(f"CRITICAL: Could not query stations from the database in MQTT thread: {e}") +# return + +# for station in stations: +# if station.station_id not in mqtt_clients: +# print(f"Creating and starting MQTT client for station: {station.name} ({station.station_id})") + +# # Use the specific details from each station object in the database +# client = MqttClient( +# broker=station.mqtt_broker, +# port=station.mqtt_port, +# user=station.mqtt_user, +# password=station.mqtt_password, +# station_id=station.station_id, +# on_message_callback=on_message_handler +# ) +# client.start() +# mqtt_clients[station.station_id] = client + +# if __name__ == '__main__': +# try: +# with app.app_context(): +# db.create_all() +# if not Station.query.first(): +# print("No stations found. Adding a default station with default MQTT config.") +# # Add a default station with MQTT details for first-time setup +# default_station = Station( +# station_id="V16000868210069259709", +# name="Test Station 2", +# mqtt_broker="mqtt-dev.upgrid.in", +# mqtt_port=1883, +# mqtt_user="guest", +# mqtt_password="password" +# ) +# db.session.add(default_station) +# db.session.commit() +# except Exception as e: +# print(f"FATAL ERROR: Could not connect to PostgreSQL: {e}") +# sys.exit(1) + +# mqtt_thread = threading.Thread(target=start_mqtt_clients, daemon=True) +# mqtt_thread.start() + +# print(f"Starting Flask-SocketIO server on http://localhost:5000") +# socketio.run(app, host='0.0.0.0', port=5000) + + + + + + + + + + + + + + + + + + + + + +import os +import sys +import threading +import json +import csv +import io +from datetime import datetime +from flask import Flask, jsonify, request, Response +from flask_socketio import SocketIO +from dotenv import load_dotenv + +# Import your custom core modules and the new models +from core.mqtt_client import MqttClient +from core.protobuf_decoder import ProtobufDecoder +from models import db, Station, User, MqttLog + +# --- Load Environment Variables --- +load_dotenv() + +# --- Pre-startup Check for Essential Configuration --- +DATABASE_URL = os.getenv("DATABASE_URL") +if not DATABASE_URL: + print("FATAL ERROR: DATABASE_URL is not set in .env file.") + sys.exit(1) + +# --- Application Setup --- +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = DATABASE_URL +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['SECRET_KEY'] = os.getenv("SECRET_KEY", "a_very_secret_key") +db.init_app(app) +socketio = SocketIO(app, cors_allowed_origins="*") + +# --- Global instances --- +decoder = ProtobufDecoder() +mqtt_clients = {} + +# --- MQTT Message Handling (UPDATED) --- +def on_message_handler(station_id, topic, payload): + """ + Handles incoming MQTT messages, decodes them, writes to PostgreSQL, + and emits to WebSockets. + """ + print(f"Main handler received message for station {station_id} on topic {topic}") + + decoded_data = None + message_type = topic.split('/')[-1] + + if message_type == 'PERIODIC': + decoded_data = decoder.decode_periodic(payload) + elif message_type == 'EVENTS': + decoded_data = decoder.decode_event(payload) + elif message_type == 'REQUEST': + decoded_data = decoder.decode_rpc_request(payload) + + if decoded_data: + # 1. Write the data to PostgreSQL for historical storage + try: + with app.app_context(): + log_entry = MqttLog( + station_id=station_id, + topic=topic, + topic_type=message_type, # <-- Save the new topic_type + payload=decoded_data + ) + db.session.add(log_entry) + db.session.commit() + print(f"Successfully wrote data for {station_id} to PostgreSQL.") + except Exception as e: + print(f"Error writing to PostgreSQL: {e}") + + # 2. Emit the data to the frontend for real-time view + socketio.emit('dashboard_update', { + 'stationId': station_id, + 'topic': topic, + 'data': decoded_data + }, room=station_id) + +# --- (WebSocket and API routes remain the same) --- +@socketio.on('connect') +def handle_connect(): + print('Client connected to WebSocket') + +# ... (other socketio handlers) + +@app.route('/api/stations', methods=['GET']) +def get_stations(): + try: + stations = Station.query.all() + return jsonify([{"id": s.station_id, "name": s.name} for s in stations]) + except Exception as e: + return jsonify({"error": f"Database query failed: {e}"}), 500 + +# --- CSV Export route (UPDATED) --- +def _format_periodic_row(payload, num_slots=9): + """ + Flattens a periodic payload dictionary into a single list for a CSV row. + """ + row = [ + datetime.fromtimestamp(payload.get("ts")).strftime("%Y-%m-%d %H:%M:%S"), + payload.get("deviceId", ""), + payload.get("stationDiagnosticCode", "") + ] + + slots_data = payload.get("slotLevelPayload", []) + slot_map = {s.get('slotId', i+1): s for i, s in enumerate(slots_data)} + + slot_fields_keys = [ + "batteryIdentification", "batteryPresent", "chargerPresent", "doorStatus", "doorLockStatus", + "voltage", "current", "soc", "batteryMaxTemp", "slotTemperature", + "batteryFaultCode", "chargerFaultCode", "batteryMode", "chargerMode" + ] + + for i in range(1, num_slots + 1): + slot = slot_map.get(i) + if slot: + row.extend([ + slot.get('batteryIdentification', ''), + slot.get("batteryPresent", 0), + slot.get("chargerPresent", 0), + slot.get("doorStatus", 0), + slot.get("doorLockStatus", 0), + slot.get('voltage', 0) / 1000.0, + slot.get('current', 0) / 1000.0, + slot.get('soc', 0), + slot.get('batteryMaxTemp', 0) / 10.0, + slot.get('slotTemperature', 0) / 10.0, + slot.get('batteryFaultCode', 0), + slot.get('chargerFaultCode', 0), + slot.get('batteryMode', 0), + slot.get('chargerMode', 0) + ]) + else: + row.extend([''] * len(slot_fields_keys)) + + row.append('') # Placeholder for RawHexPayload + return row + +@app.route('/api/logs/export', methods=['GET']) +def export_logs(): + station_id = request.args.get('station_id') + start_date_str = request.args.get('start_date') + end_date_str = request.args.get('end_date') + log_type = request.args.get('log_type', 'PERIODIC') + + if not all([station_id, start_date_str, end_date_str]): + return jsonify({"error": "Missing required parameters: station_id, start_date, end_date"}), 400 + + try: + start_date = datetime.strptime(start_date_str, '%Y-%m-%d') + end_date = datetime.strptime(end_date_str, '%Y-%m-%d').replace(hour=23, minute=59, second=59) + except ValueError: + return jsonify({"error": "Invalid date format. Use YYYY-MM-DD."}), 400 + + # UPDATED QUERY: Filter by the new 'topic_type' column for better performance + query = MqttLog.query.filter( + MqttLog.station_id == station_id, + MqttLog.timestamp.between(start_date, end_date), + MqttLog.topic_type == log_type + ) + + logs = query.order_by(MqttLog.timestamp.asc()).all() + + output = io.StringIO() + writer = csv.writer(output) + + if log_type == 'PERIODIC': + base_header = ["Timestamp", "DeviceID", "StationDiagnosticCode"] + slot_fields = [ + "BatteryID", "BatteryPresent", "ChargerPresent", "DoorStatus", "DoorLockStatus", + "Voltage_V", "Current_A", "SOC_Percent", "BatteryTemp_C", "SlotTemp_C", + "BatteryFaultCode", "ChargerFaultCode", "BatteryMode", "ChargerMode" + ] + slot_header = [f"Slot{i}_{field}" for i in range(1, 10) for field in slot_fields] + header = base_header + slot_header + ["RawHexPayload"] + writer.writerow(header) + + for log in logs: + writer.writerow(_format_periodic_row(log.payload)) + else: # For EVENTS_RPC + header = ["Timestamp", "Topic", "Payload_JSON"] + writer.writerow(header) + for log in logs: + writer.writerow([log.timestamp, log.topic, json.dumps(log.payload)]) + + output.seek(0) + return Response( + output, + mimetype="text/csv", + headers={"Content-Disposition": f"attachment;filename=logs_{station_id}_{log_type}_{start_date_str}_to_{end_date_str}.csv"} + ) + +# --- Main Application Logic --- +def start_mqtt_clients(): + """ + Initializes and starts an MQTT client for each station found in the database, + using the specific MQTT credentials stored for each station. + """ + try: + with app.app_context(): + stations = Station.query.all() + except Exception as e: + print(f"CRITICAL: Could not query stations from the database in MQTT thread: {e}") + return + + for station in stations: + if station.station_id not in mqtt_clients: + print(f"Creating and starting MQTT client for station: {station.name} ({station.station_id})") + + client = MqttClient( + broker=station.mqtt_broker, + port=station.mqtt_port, + user=station.mqtt_user, + password=station.mqtt_password, + station_id=station.station_id, + on_message_callback=on_message_handler + ) + client.start() + mqtt_clients[station.station_id] = client + +if __name__ == '__main__': + try: + with app.app_context(): + db.create_all() + if not Station.query.first(): + print("No stations found. Adding a default station with default MQTT config.") + default_station = Station( + station_id="V16000868210069259709", + name="Test Station 2", + mqtt_broker="mqtt-dev.upgrid.in", + mqtt_port=1883, + mqtt_user="guest", + mqtt_password="password" + ) + db.session.add(default_station) + db.session.commit() + except Exception as e: + print(f"FATAL ERROR: Could not connect to PostgreSQL: {e}") + sys.exit(1) + + mqtt_thread = threading.Thread(target=start_mqtt_clients, daemon=True) + mqtt_thread.start() + + print(f"Starting Flask-SocketIO server on http://localhost:5000") + socketio.run(app, host='0.0.0.0', port=5000) diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..29be586 --- /dev/null +++ b/backend/models.py @@ -0,0 +1,35 @@ +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy.dialects.postgresql import JSONB + +# Create a SQLAlchemy instance. This will be linked to the Flask app in main.py. +db = SQLAlchemy() + +class User(db.Model): + """Represents a user in the database.""" + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password_hash = db.Column(db.String(120), nullable=False) + +class Station(db.Model): + id = db.Column(db.Integer, primary_key=True) + station_id = db.Column(db.String(120), unique=True, nullable=False) + name = db.Column(db.String(120), nullable=True) + location = db.Column(db.String(200), nullable=True) + + # --- ADD THESE NEW FIELDS --- + mqtt_broker = db.Column(db.String(255), nullable=False) + mqtt_port = db.Column(db.Integer, nullable=False) + mqtt_user = db.Column(db.String(120), nullable=True) + mqtt_password = db.Column(db.String(120), nullable=True) + +# --- Table for MQTT Logs (without raw payload) --- +class MqttLog(db.Model): + """Represents a single MQTT message payload for historical logging.""" + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, server_default=db.func.now()) + station_id = db.Column(db.String(120), nullable=False, index=True) + topic = db.Column(db.String(255), nullable=False) + # --- NEW: Added topic_type for efficient filtering --- + topic_type = db.Column(db.String(50), nullable=False, index=True) + # JSONB is a highly efficient way to store JSON data in PostgreSQL + payload = db.Column(JSONB) diff --git a/backend/proto/__init__.py b/backend/proto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/proto/__pycache__/__init__.cpython-313.pyc b/backend/proto/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..25d384d Binary files /dev/null and b/backend/proto/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/proto/__pycache__/vec_payload_chgSt_pb2.cpython-313.pyc b/backend/proto/__pycache__/vec_payload_chgSt_pb2.cpython-313.pyc new file mode 100644 index 0000000..c6475c5 Binary files /dev/null and b/backend/proto/__pycache__/vec_payload_chgSt_pb2.cpython-313.pyc differ diff --git a/backend/proto/vec_payload_chgSt.proto b/backend/proto/vec_payload_chgSt.proto new file mode 100644 index 0000000..521b42c --- /dev/null +++ b/backend/proto/vec_payload_chgSt.proto @@ -0,0 +1,162 @@ +syntax = "proto2"; + +enum eventType_e { + EVENT_SWAP_START = 0x200; + EVENT_BATTERY_ENTRY = 0x201; + EVENT_BATTERY_EXIT = 0x202; + EVENT_ACTIVITY_FAILED = 0x203; + EVENT_SWAP_ABORTED = 0x204; + EVENT_BATFAULT_ALARM = 0x205; + EVENT_SLOT_LOCK_ENEGAGED = 0x206; + EVENT_SWAP_ENDED = 0x207; + EVENT_CHGFAULT_ALARM = 0x208; + EVENT_NFC_SCAN = 0x209; + EVENT_SLOT_LOCK_DISENEGAGED = 0x20A; + EVENT_REVERSE_SWAP = 0x20B; +} + +enum jobType_e { + JOBTYPE_NONE = 0; + JOBTYPE_GET_STATUS_OF_A_JOB = 0x01; + JOBTYPE_SWAP_START = 0x100; + JOBTYPE_CHARGER_ENABLE_DISABLE = 0x101; + JOBTYPE_GATE_OPEN_CLOSE = 0x102; + JOBTYPE_TRANSACTION_ABORT = 0x103; + JOBTYPE_REBOOT = 0x104; + JOBTYPE_SWAP_DENY = 0x105; + JOBTYPE_LANGUAGE_UPDATE = 0x106; +} + +enum jobResult_e { + JOB_RESULT_UNKNOWN = 0; + JOB_RESULT_SUCCESS = 1; + JOB_RESULT_REJECTED = 2; + JOB_RESULT_TIMEOUT = 3; +} + +enum jobStatus_e { + JOB_STATUS_IDLE = 0; + JOB_STATUS_PENDING = 1; + JOB_STATUS_EXECUTING = 2; + JOB_STATUS_EXECUTED = 3; +} + +message slotLevelPayload{ + optional uint32 batteryPresent = 1; + optional uint32 chargerPresent = 2; + optional uint32 doorLockStatus = 3; + optional uint32 doorStatus = 4; + optional uint32 voltage = 5; + optional int32 current = 6; + optional uint32 batteryFaultCode = 7; + optional uint32 chargerFaultCode = 8; + optional int32 batteryMaxTemp = 9; + optional int32 chargerMaxTemp= 10; + optional string batteryIdentification = 11; + optional uint32 batteryMode = 12; + optional uint32 chargerMode = 13; + optional int32 slotTemperature = 14; + optional uint32 gasSensor = 15; + optional uint32 soc=16; + optional uint32 ts = 17; +} + +message mainPayload{ + required uint32 ts = 1; + required string deviceId = 2; + required string sessionId = 3; + repeated slotLevelPayload slotLevelPayload = 4; + optional uint32 backupSupplyStatus = 5; + repeated uint32 switchStatus = 6; + optional uint32 stationStatus = 7; + optional uint32 stationDiagnosticCode = 8; + repeated float coordinates = 9; +} + +enum swapAbortReason_e{ + ABORT_UNKNOWN=0; + ABORT_BAT_EXIT_TIMEOUT=1; + ABORT_BAT_ENTRY_TIMEOUT=2; + ABORT_DOOR_CLOSE_TIMEOUT=3; + ABORT_DOOR_OPEN_TIMEOUT=4; + ABORT_INVALID_PARAM=5; + ABORT_REMOTE_REQUESTED=6; + ABORT_INVALID_BATTERY=7; +} + +enum swapDenyReason_e{ + SWAP_DENY_INSUFFICIENT_BAL=1; + SWAP_DENY_INVALID_NFC=2; + SWAP_DENY_BATTERY_UNAVAILABLE=3; +} + +enum languageType_e{ + LANGUAGE_TYPE_ENGLISH = 1; + LANGUAGE_TYPE_HINDI = 2; + LANGUAGE_TYPE_KANNADA = 3; + LANGUAGE_TYPE_TELUGU = 4; +} + +message nfcPayload_s{ + required string manufacturingData = 1; + required string customData = 2; +} + +message eventData_s { + optional nfcPayload_s nfcData = 1; + optional string batteryIdentification = 2; + optional uint32 activityFailureReason = 3; + optional swapAbortReason_e swapAbortReason = 4; + optional uint32 swapTime = 5; + optional uint32 faultCode = 6; + optional uint32 doorStatus = 7; + optional uint32 slotId = 8; +} + +message eventPayload { + required uint32 ts = 1; + required string deviceId = 2; + required eventType_e eventType = 3; + required string sessionId = 4; + optional eventData_s eventData = 5; +} + +message rpcData_s { + optional string sessionId = 1; + repeated uint32 slotsData = 2; +} + +message slotControl_s { + required uint32 slotId = 1; + required uint32 state = 2; +} + +message getJobStatusByJobId_s{ + required string jobId = 1; +} + +message rpcRequest { + required uint32 ts = 1; + required string jobId = 2; + required jobType_e jobType = 3; + optional rpcData_s rpcData = 4; + optional slotControl_s slotInfo = 5; + optional swapDenyReason_e swapDeny = 8; + optional getJobStatusByJobId_s getJobStatusByJobId = 9; + optional languageType_e languageType = 10; +} + +message jobStatusByJobIdResponse_s{ + required string jobId = 1; + required jobStatus_e jobStatus = 2; + required jobResult_e jobResult = 3; +} + +message rpcResponse { + required uint32 ts = 1; + required string deviceId = 2; + required string jobId = 3; + required jobStatus_e jobStatus = 4; + required jobResult_e jobResult = 5; + optional jobStatusByJobIdResponse_s jobStatusByJobIdResponse = 6; +} \ No newline at end of file diff --git a/backend/proto/vec_payload_chgSt_pb2.py b/backend/proto/vec_payload_chgSt_pb2.py new file mode 100644 index 0000000..a18742f --- /dev/null +++ b/backend/proto/vec_payload_chgSt_pb2.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: vec_payload_chgSt.proto +# Protobuf Python Version: 6.32.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 32, + 0, + '', + 'vec_payload_chgSt.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17vec_payload_chgSt.proto\"\x82\x03\n\x10slotLevelPayload\x12\x16\n\x0e\x62\x61tteryPresent\x18\x01 \x01(\r\x12\x16\n\x0e\x63hargerPresent\x18\x02 \x01(\r\x12\x16\n\x0e\x64oorLockStatus\x18\x03 \x01(\r\x12\x12\n\ndoorStatus\x18\x04 \x01(\r\x12\x0f\n\x07voltage\x18\x05 \x01(\r\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x05\x12\x18\n\x10\x62\x61tteryFaultCode\x18\x07 \x01(\r\x12\x18\n\x10\x63hargerFaultCode\x18\x08 \x01(\r\x12\x16\n\x0e\x62\x61tteryMaxTemp\x18\t \x01(\x05\x12\x16\n\x0e\x63hargerMaxTemp\x18\n \x01(\x05\x12\x1d\n\x15\x62\x61tteryIdentification\x18\x0b \x01(\t\x12\x13\n\x0b\x62\x61tteryMode\x18\x0c \x01(\r\x12\x13\n\x0b\x63hargerMode\x18\r \x01(\r\x12\x17\n\x0fslotTemperature\x18\x0e \x01(\x05\x12\x11\n\tgasSensor\x18\x0f \x01(\r\x12\x0b\n\x03soc\x18\x10 \x01(\r\x12\n\n\x02ts\x18\x11 \x01(\r\"\xe8\x01\n\x0bmainPayload\x12\n\n\x02ts\x18\x01 \x02(\r\x12\x10\n\x08\x64\x65viceId\x18\x02 \x02(\t\x12\x11\n\tsessionId\x18\x03 \x02(\t\x12+\n\x10slotLevelPayload\x18\x04 \x03(\x0b\x32\x11.slotLevelPayload\x12\x1a\n\x12\x62\x61\x63kupSupplyStatus\x18\x05 \x01(\r\x12\x14\n\x0cswitchStatus\x18\x06 \x03(\r\x12\x15\n\rstationStatus\x18\x07 \x01(\r\x12\x1d\n\x15stationDiagnosticCode\x18\x08 \x01(\r\x12\x13\n\x0b\x63oordinates\x18\t \x03(\x02\"=\n\x0cnfcPayload_s\x12\x19\n\x11manufacturingData\x18\x01 \x02(\t\x12\x12\n\ncustomData\x18\x02 \x02(\t\"\xe1\x01\n\x0b\x65ventData_s\x12\x1e\n\x07nfcData\x18\x01 \x01(\x0b\x32\r.nfcPayload_s\x12\x1d\n\x15\x62\x61tteryIdentification\x18\x02 \x01(\t\x12\x1d\n\x15\x61\x63tivityFailureReason\x18\x03 \x01(\r\x12+\n\x0fswapAbortReason\x18\x04 \x01(\x0e\x32\x12.swapAbortReason_e\x12\x10\n\x08swapTime\x18\x05 \x01(\r\x12\x11\n\tfaultCode\x18\x06 \x01(\r\x12\x12\n\ndoorStatus\x18\x07 \x01(\r\x12\x0e\n\x06slotId\x18\x08 \x01(\r\"\x81\x01\n\x0c\x65ventPayload\x12\n\n\x02ts\x18\x01 \x02(\r\x12\x10\n\x08\x64\x65viceId\x18\x02 \x02(\t\x12\x1f\n\teventType\x18\x03 \x02(\x0e\x32\x0c.eventType_e\x12\x11\n\tsessionId\x18\x04 \x02(\t\x12\x1f\n\teventData\x18\x05 \x01(\x0b\x32\x0c.eventData_s\"1\n\trpcData_s\x12\x11\n\tsessionId\x18\x01 \x01(\t\x12\x11\n\tslotsData\x18\x02 \x03(\r\".\n\rslotControl_s\x12\x0e\n\x06slotId\x18\x01 \x02(\r\x12\r\n\x05state\x18\x02 \x02(\r\"&\n\x15getJobStatusByJobId_s\x12\r\n\x05jobId\x18\x01 \x02(\t\"\x84\x02\n\nrpcRequest\x12\n\n\x02ts\x18\x01 \x02(\r\x12\r\n\x05jobId\x18\x02 \x02(\t\x12\x1b\n\x07jobType\x18\x03 \x02(\x0e\x32\n.jobType_e\x12\x1b\n\x07rpcData\x18\x04 \x01(\x0b\x32\n.rpcData_s\x12 \n\x08slotInfo\x18\x05 \x01(\x0b\x32\x0e.slotControl_s\x12#\n\x08swapDeny\x18\x08 \x01(\x0e\x32\x11.swapDenyReason_e\x12\x33\n\x13getJobStatusByJobId\x18\t \x01(\x0b\x32\x16.getJobStatusByJobId_s\x12%\n\x0clanguageType\x18\n \x01(\x0e\x32\x0f.languageType_e\"m\n\x1ajobStatusByJobIdResponse_s\x12\r\n\x05jobId\x18\x01 \x02(\t\x12\x1f\n\tjobStatus\x18\x02 \x02(\x0e\x32\x0c.jobStatus_e\x12\x1f\n\tjobResult\x18\x03 \x02(\x0e\x32\x0c.jobResult_e\"\xbb\x01\n\x0brpcResponse\x12\n\n\x02ts\x18\x01 \x02(\r\x12\x10\n\x08\x64\x65viceId\x18\x02 \x02(\t\x12\r\n\x05jobId\x18\x03 \x02(\t\x12\x1f\n\tjobStatus\x18\x04 \x02(\x0e\x32\x0c.jobStatus_e\x12\x1f\n\tjobResult\x18\x05 \x02(\x0e\x32\x0c.jobResult_e\x12=\n\x18jobStatusByJobIdResponse\x18\x06 \x01(\x0b\x32\x1b.jobStatusByJobIdResponse_s*\xc8\x02\n\x0b\x65ventType_e\x12\x15\n\x10\x45VENT_SWAP_START\x10\x80\x04\x12\x18\n\x13\x45VENT_BATTERY_ENTRY\x10\x81\x04\x12\x17\n\x12\x45VENT_BATTERY_EXIT\x10\x82\x04\x12\x1a\n\x15\x45VENT_ACTIVITY_FAILED\x10\x83\x04\x12\x17\n\x12\x45VENT_SWAP_ABORTED\x10\x84\x04\x12\x19\n\x14\x45VENT_BATFAULT_ALARM\x10\x85\x04\x12\x1d\n\x18\x45VENT_SLOT_LOCK_ENEGAGED\x10\x86\x04\x12\x15\n\x10\x45VENT_SWAP_ENDED\x10\x87\x04\x12\x19\n\x14\x45VENT_CHGFAULT_ALARM\x10\x88\x04\x12\x13\n\x0e\x45VENT_NFC_SCAN\x10\x89\x04\x12 \n\x1b\x45VENT_SLOT_LOCK_DISENEGAGED\x10\x8a\x04\x12\x17\n\x12\x45VENT_REVERSE_SWAP\x10\x8b\x04*\x85\x02\n\tjobType_e\x12\x10\n\x0cJOBTYPE_NONE\x10\x00\x12\x1f\n\x1bJOBTYPE_GET_STATUS_OF_A_JOB\x10\x01\x12\x17\n\x12JOBTYPE_SWAP_START\x10\x80\x02\x12#\n\x1eJOBTYPE_CHARGER_ENABLE_DISABLE\x10\x81\x02\x12\x1c\n\x17JOBTYPE_GATE_OPEN_CLOSE\x10\x82\x02\x12\x1e\n\x19JOBTYPE_TRANSACTION_ABORT\x10\x83\x02\x12\x13\n\x0eJOBTYPE_REBOOT\x10\x84\x02\x12\x16\n\x11JOBTYPE_SWAP_DENY\x10\x85\x02\x12\x1c\n\x17JOBTYPE_LANGUAGE_UPDATE\x10\x86\x02*n\n\x0bjobResult_e\x12\x16\n\x12JOB_RESULT_UNKNOWN\x10\x00\x12\x16\n\x12JOB_RESULT_SUCCESS\x10\x01\x12\x17\n\x13JOB_RESULT_REJECTED\x10\x02\x12\x16\n\x12JOB_RESULT_TIMEOUT\x10\x03*m\n\x0bjobStatus_e\x12\x13\n\x0fJOB_STATUS_IDLE\x10\x00\x12\x16\n\x12JOB_STATUS_PENDING\x10\x01\x12\x18\n\x14JOB_STATUS_EXECUTING\x10\x02\x12\x17\n\x13JOB_STATUS_EXECUTED\x10\x03*\xea\x01\n\x11swapAbortReason_e\x12\x11\n\rABORT_UNKNOWN\x10\x00\x12\x1a\n\x16\x41\x42ORT_BAT_EXIT_TIMEOUT\x10\x01\x12\x1b\n\x17\x41\x42ORT_BAT_ENTRY_TIMEOUT\x10\x02\x12\x1c\n\x18\x41\x42ORT_DOOR_CLOSE_TIMEOUT\x10\x03\x12\x1b\n\x17\x41\x42ORT_DOOR_OPEN_TIMEOUT\x10\x04\x12\x17\n\x13\x41\x42ORT_INVALID_PARAM\x10\x05\x12\x1a\n\x16\x41\x42ORT_REMOTE_REQUESTED\x10\x06\x12\x19\n\x15\x41\x42ORT_INVALID_BATTERY\x10\x07*p\n\x10swapDenyReason_e\x12\x1e\n\x1aSWAP_DENY_INSUFFICIENT_BAL\x10\x01\x12\x19\n\x15SWAP_DENY_INVALID_NFC\x10\x02\x12!\n\x1dSWAP_DENY_BATTERY_UNAVAILABLE\x10\x03*y\n\x0elanguageType_e\x12\x19\n\x15LANGUAGE_TYPE_ENGLISH\x10\x01\x12\x17\n\x13LANGUAGE_TYPE_HINDI\x10\x02\x12\x19\n\x15LANGUAGE_TYPE_KANNADA\x10\x03\x12\x18\n\x14LANGUAGE_TYPE_TELUGU\x10\x04') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'vec_payload_chgSt_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_EVENTTYPE_E']._serialized_start=1778 + _globals['_EVENTTYPE_E']._serialized_end=2106 + _globals['_JOBTYPE_E']._serialized_start=2109 + _globals['_JOBTYPE_E']._serialized_end=2370 + _globals['_JOBRESULT_E']._serialized_start=2372 + _globals['_JOBRESULT_E']._serialized_end=2482 + _globals['_JOBSTATUS_E']._serialized_start=2484 + _globals['_JOBSTATUS_E']._serialized_end=2593 + _globals['_SWAPABORTREASON_E']._serialized_start=2596 + _globals['_SWAPABORTREASON_E']._serialized_end=2830 + _globals['_SWAPDENYREASON_E']._serialized_start=2832 + _globals['_SWAPDENYREASON_E']._serialized_end=2944 + _globals['_LANGUAGETYPE_E']._serialized_start=2946 + _globals['_LANGUAGETYPE_E']._serialized_end=3067 + _globals['_SLOTLEVELPAYLOAD']._serialized_start=28 + _globals['_SLOTLEVELPAYLOAD']._serialized_end=414 + _globals['_MAINPAYLOAD']._serialized_start=417 + _globals['_MAINPAYLOAD']._serialized_end=649 + _globals['_NFCPAYLOAD_S']._serialized_start=651 + _globals['_NFCPAYLOAD_S']._serialized_end=712 + _globals['_EVENTDATA_S']._serialized_start=715 + _globals['_EVENTDATA_S']._serialized_end=940 + _globals['_EVENTPAYLOAD']._serialized_start=943 + _globals['_EVENTPAYLOAD']._serialized_end=1072 + _globals['_RPCDATA_S']._serialized_start=1074 + _globals['_RPCDATA_S']._serialized_end=1123 + _globals['_SLOTCONTROL_S']._serialized_start=1125 + _globals['_SLOTCONTROL_S']._serialized_end=1171 + _globals['_GETJOBSTATUSBYJOBID_S']._serialized_start=1173 + _globals['_GETJOBSTATUSBYJOBID_S']._serialized_end=1211 + _globals['_RPCREQUEST']._serialized_start=1214 + _globals['_RPCREQUEST']._serialized_end=1474 + _globals['_JOBSTATUSBYJOBIDRESPONSE_S']._serialized_start=1476 + _globals['_JOBSTATUSBYJOBIDRESPONSE_S']._serialized_end=1585 + _globals['_RPCRESPONSE']._serialized_start=1588 + _globals['_RPCRESPONSE']._serialized_end=1775 +# @@protoc_insertion_point(module_scope) diff --git a/backend/proto/vec_payload_chgSt_pb2.pyi b/backend/proto/vec_payload_chgSt_pb2.pyi new file mode 100644 index 0000000..64a2210 --- /dev/null +++ b/backend/proto/vec_payload_chgSt_pb2.pyi @@ -0,0 +1,287 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class eventType_e(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + EVENT_SWAP_START: _ClassVar[eventType_e] + EVENT_BATTERY_ENTRY: _ClassVar[eventType_e] + EVENT_BATTERY_EXIT: _ClassVar[eventType_e] + EVENT_ACTIVITY_FAILED: _ClassVar[eventType_e] + EVENT_SWAP_ABORTED: _ClassVar[eventType_e] + EVENT_BATFAULT_ALARM: _ClassVar[eventType_e] + EVENT_SLOT_LOCK_ENEGAGED: _ClassVar[eventType_e] + EVENT_SWAP_ENDED: _ClassVar[eventType_e] + EVENT_CHGFAULT_ALARM: _ClassVar[eventType_e] + EVENT_NFC_SCAN: _ClassVar[eventType_e] + EVENT_SLOT_LOCK_DISENEGAGED: _ClassVar[eventType_e] + EVENT_REVERSE_SWAP: _ClassVar[eventType_e] + +class jobType_e(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + JOBTYPE_NONE: _ClassVar[jobType_e] + JOBTYPE_GET_STATUS_OF_A_JOB: _ClassVar[jobType_e] + JOBTYPE_SWAP_START: _ClassVar[jobType_e] + JOBTYPE_CHARGER_ENABLE_DISABLE: _ClassVar[jobType_e] + JOBTYPE_GATE_OPEN_CLOSE: _ClassVar[jobType_e] + JOBTYPE_TRANSACTION_ABORT: _ClassVar[jobType_e] + JOBTYPE_REBOOT: _ClassVar[jobType_e] + JOBTYPE_SWAP_DENY: _ClassVar[jobType_e] + JOBTYPE_LANGUAGE_UPDATE: _ClassVar[jobType_e] + +class jobResult_e(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + JOB_RESULT_UNKNOWN: _ClassVar[jobResult_e] + JOB_RESULT_SUCCESS: _ClassVar[jobResult_e] + JOB_RESULT_REJECTED: _ClassVar[jobResult_e] + JOB_RESULT_TIMEOUT: _ClassVar[jobResult_e] + +class jobStatus_e(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + JOB_STATUS_IDLE: _ClassVar[jobStatus_e] + JOB_STATUS_PENDING: _ClassVar[jobStatus_e] + JOB_STATUS_EXECUTING: _ClassVar[jobStatus_e] + JOB_STATUS_EXECUTED: _ClassVar[jobStatus_e] + +class swapAbortReason_e(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ABORT_UNKNOWN: _ClassVar[swapAbortReason_e] + ABORT_BAT_EXIT_TIMEOUT: _ClassVar[swapAbortReason_e] + ABORT_BAT_ENTRY_TIMEOUT: _ClassVar[swapAbortReason_e] + ABORT_DOOR_CLOSE_TIMEOUT: _ClassVar[swapAbortReason_e] + ABORT_DOOR_OPEN_TIMEOUT: _ClassVar[swapAbortReason_e] + ABORT_INVALID_PARAM: _ClassVar[swapAbortReason_e] + ABORT_REMOTE_REQUESTED: _ClassVar[swapAbortReason_e] + ABORT_INVALID_BATTERY: _ClassVar[swapAbortReason_e] + +class swapDenyReason_e(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + SWAP_DENY_INSUFFICIENT_BAL: _ClassVar[swapDenyReason_e] + SWAP_DENY_INVALID_NFC: _ClassVar[swapDenyReason_e] + SWAP_DENY_BATTERY_UNAVAILABLE: _ClassVar[swapDenyReason_e] + +class languageType_e(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + LANGUAGE_TYPE_ENGLISH: _ClassVar[languageType_e] + LANGUAGE_TYPE_HINDI: _ClassVar[languageType_e] + LANGUAGE_TYPE_KANNADA: _ClassVar[languageType_e] + LANGUAGE_TYPE_TELUGU: _ClassVar[languageType_e] +EVENT_SWAP_START: eventType_e +EVENT_BATTERY_ENTRY: eventType_e +EVENT_BATTERY_EXIT: eventType_e +EVENT_ACTIVITY_FAILED: eventType_e +EVENT_SWAP_ABORTED: eventType_e +EVENT_BATFAULT_ALARM: eventType_e +EVENT_SLOT_LOCK_ENEGAGED: eventType_e +EVENT_SWAP_ENDED: eventType_e +EVENT_CHGFAULT_ALARM: eventType_e +EVENT_NFC_SCAN: eventType_e +EVENT_SLOT_LOCK_DISENEGAGED: eventType_e +EVENT_REVERSE_SWAP: eventType_e +JOBTYPE_NONE: jobType_e +JOBTYPE_GET_STATUS_OF_A_JOB: jobType_e +JOBTYPE_SWAP_START: jobType_e +JOBTYPE_CHARGER_ENABLE_DISABLE: jobType_e +JOBTYPE_GATE_OPEN_CLOSE: jobType_e +JOBTYPE_TRANSACTION_ABORT: jobType_e +JOBTYPE_REBOOT: jobType_e +JOBTYPE_SWAP_DENY: jobType_e +JOBTYPE_LANGUAGE_UPDATE: jobType_e +JOB_RESULT_UNKNOWN: jobResult_e +JOB_RESULT_SUCCESS: jobResult_e +JOB_RESULT_REJECTED: jobResult_e +JOB_RESULT_TIMEOUT: jobResult_e +JOB_STATUS_IDLE: jobStatus_e +JOB_STATUS_PENDING: jobStatus_e +JOB_STATUS_EXECUTING: jobStatus_e +JOB_STATUS_EXECUTED: jobStatus_e +ABORT_UNKNOWN: swapAbortReason_e +ABORT_BAT_EXIT_TIMEOUT: swapAbortReason_e +ABORT_BAT_ENTRY_TIMEOUT: swapAbortReason_e +ABORT_DOOR_CLOSE_TIMEOUT: swapAbortReason_e +ABORT_DOOR_OPEN_TIMEOUT: swapAbortReason_e +ABORT_INVALID_PARAM: swapAbortReason_e +ABORT_REMOTE_REQUESTED: swapAbortReason_e +ABORT_INVALID_BATTERY: swapAbortReason_e +SWAP_DENY_INSUFFICIENT_BAL: swapDenyReason_e +SWAP_DENY_INVALID_NFC: swapDenyReason_e +SWAP_DENY_BATTERY_UNAVAILABLE: swapDenyReason_e +LANGUAGE_TYPE_ENGLISH: languageType_e +LANGUAGE_TYPE_HINDI: languageType_e +LANGUAGE_TYPE_KANNADA: languageType_e +LANGUAGE_TYPE_TELUGU: languageType_e + +class slotLevelPayload(_message.Message): + __slots__ = ("batteryPresent", "chargerPresent", "doorLockStatus", "doorStatus", "voltage", "current", "batteryFaultCode", "chargerFaultCode", "batteryMaxTemp", "chargerMaxTemp", "batteryIdentification", "batteryMode", "chargerMode", "slotTemperature", "gasSensor", "soc", "ts") + BATTERYPRESENT_FIELD_NUMBER: _ClassVar[int] + CHARGERPRESENT_FIELD_NUMBER: _ClassVar[int] + DOORLOCKSTATUS_FIELD_NUMBER: _ClassVar[int] + DOORSTATUS_FIELD_NUMBER: _ClassVar[int] + VOLTAGE_FIELD_NUMBER: _ClassVar[int] + CURRENT_FIELD_NUMBER: _ClassVar[int] + BATTERYFAULTCODE_FIELD_NUMBER: _ClassVar[int] + CHARGERFAULTCODE_FIELD_NUMBER: _ClassVar[int] + BATTERYMAXTEMP_FIELD_NUMBER: _ClassVar[int] + CHARGERMAXTEMP_FIELD_NUMBER: _ClassVar[int] + BATTERYIDENTIFICATION_FIELD_NUMBER: _ClassVar[int] + BATTERYMODE_FIELD_NUMBER: _ClassVar[int] + CHARGERMODE_FIELD_NUMBER: _ClassVar[int] + SLOTTEMPERATURE_FIELD_NUMBER: _ClassVar[int] + GASSENSOR_FIELD_NUMBER: _ClassVar[int] + SOC_FIELD_NUMBER: _ClassVar[int] + TS_FIELD_NUMBER: _ClassVar[int] + batteryPresent: int + chargerPresent: int + doorLockStatus: int + doorStatus: int + voltage: int + current: int + batteryFaultCode: int + chargerFaultCode: int + batteryMaxTemp: int + chargerMaxTemp: int + batteryIdentification: str + batteryMode: int + chargerMode: int + slotTemperature: int + gasSensor: int + soc: int + ts: int + def __init__(self, batteryPresent: _Optional[int] = ..., chargerPresent: _Optional[int] = ..., doorLockStatus: _Optional[int] = ..., doorStatus: _Optional[int] = ..., voltage: _Optional[int] = ..., current: _Optional[int] = ..., batteryFaultCode: _Optional[int] = ..., chargerFaultCode: _Optional[int] = ..., batteryMaxTemp: _Optional[int] = ..., chargerMaxTemp: _Optional[int] = ..., batteryIdentification: _Optional[str] = ..., batteryMode: _Optional[int] = ..., chargerMode: _Optional[int] = ..., slotTemperature: _Optional[int] = ..., gasSensor: _Optional[int] = ..., soc: _Optional[int] = ..., ts: _Optional[int] = ...) -> None: ... + +class mainPayload(_message.Message): + __slots__ = ("ts", "deviceId", "sessionId", "slotLevelPayload", "backupSupplyStatus", "switchStatus", "stationStatus", "stationDiagnosticCode", "coordinates") + TS_FIELD_NUMBER: _ClassVar[int] + DEVICEID_FIELD_NUMBER: _ClassVar[int] + SESSIONID_FIELD_NUMBER: _ClassVar[int] + SLOTLEVELPAYLOAD_FIELD_NUMBER: _ClassVar[int] + BACKUPSUPPLYSTATUS_FIELD_NUMBER: _ClassVar[int] + SWITCHSTATUS_FIELD_NUMBER: _ClassVar[int] + STATIONSTATUS_FIELD_NUMBER: _ClassVar[int] + STATIONDIAGNOSTICCODE_FIELD_NUMBER: _ClassVar[int] + COORDINATES_FIELD_NUMBER: _ClassVar[int] + ts: int + deviceId: str + sessionId: str + slotLevelPayload: _containers.RepeatedCompositeFieldContainer[slotLevelPayload] + backupSupplyStatus: int + switchStatus: _containers.RepeatedScalarFieldContainer[int] + stationStatus: int + stationDiagnosticCode: int + coordinates: _containers.RepeatedScalarFieldContainer[float] + def __init__(self, ts: _Optional[int] = ..., deviceId: _Optional[str] = ..., sessionId: _Optional[str] = ..., slotLevelPayload: _Optional[_Iterable[_Union[slotLevelPayload, _Mapping]]] = ..., backupSupplyStatus: _Optional[int] = ..., switchStatus: _Optional[_Iterable[int]] = ..., stationStatus: _Optional[int] = ..., stationDiagnosticCode: _Optional[int] = ..., coordinates: _Optional[_Iterable[float]] = ...) -> None: ... + +class nfcPayload_s(_message.Message): + __slots__ = ("manufacturingData", "customData") + MANUFACTURINGDATA_FIELD_NUMBER: _ClassVar[int] + CUSTOMDATA_FIELD_NUMBER: _ClassVar[int] + manufacturingData: str + customData: str + def __init__(self, manufacturingData: _Optional[str] = ..., customData: _Optional[str] = ...) -> None: ... + +class eventData_s(_message.Message): + __slots__ = ("nfcData", "batteryIdentification", "activityFailureReason", "swapAbortReason", "swapTime", "faultCode", "doorStatus", "slotId") + NFCDATA_FIELD_NUMBER: _ClassVar[int] + BATTERYIDENTIFICATION_FIELD_NUMBER: _ClassVar[int] + ACTIVITYFAILUREREASON_FIELD_NUMBER: _ClassVar[int] + SWAPABORTREASON_FIELD_NUMBER: _ClassVar[int] + SWAPTIME_FIELD_NUMBER: _ClassVar[int] + FAULTCODE_FIELD_NUMBER: _ClassVar[int] + DOORSTATUS_FIELD_NUMBER: _ClassVar[int] + SLOTID_FIELD_NUMBER: _ClassVar[int] + nfcData: nfcPayload_s + batteryIdentification: str + activityFailureReason: int + swapAbortReason: swapAbortReason_e + swapTime: int + faultCode: int + doorStatus: int + slotId: int + def __init__(self, nfcData: _Optional[_Union[nfcPayload_s, _Mapping]] = ..., batteryIdentification: _Optional[str] = ..., activityFailureReason: _Optional[int] = ..., swapAbortReason: _Optional[_Union[swapAbortReason_e, str]] = ..., swapTime: _Optional[int] = ..., faultCode: _Optional[int] = ..., doorStatus: _Optional[int] = ..., slotId: _Optional[int] = ...) -> None: ... + +class eventPayload(_message.Message): + __slots__ = ("ts", "deviceId", "eventType", "sessionId", "eventData") + TS_FIELD_NUMBER: _ClassVar[int] + DEVICEID_FIELD_NUMBER: _ClassVar[int] + EVENTTYPE_FIELD_NUMBER: _ClassVar[int] + SESSIONID_FIELD_NUMBER: _ClassVar[int] + EVENTDATA_FIELD_NUMBER: _ClassVar[int] + ts: int + deviceId: str + eventType: eventType_e + sessionId: str + eventData: eventData_s + def __init__(self, ts: _Optional[int] = ..., deviceId: _Optional[str] = ..., eventType: _Optional[_Union[eventType_e, str]] = ..., sessionId: _Optional[str] = ..., eventData: _Optional[_Union[eventData_s, _Mapping]] = ...) -> None: ... + +class rpcData_s(_message.Message): + __slots__ = ("sessionId", "slotsData") + SESSIONID_FIELD_NUMBER: _ClassVar[int] + SLOTSDATA_FIELD_NUMBER: _ClassVar[int] + sessionId: str + slotsData: _containers.RepeatedScalarFieldContainer[int] + def __init__(self, sessionId: _Optional[str] = ..., slotsData: _Optional[_Iterable[int]] = ...) -> None: ... + +class slotControl_s(_message.Message): + __slots__ = ("slotId", "state") + SLOTID_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + slotId: int + state: int + def __init__(self, slotId: _Optional[int] = ..., state: _Optional[int] = ...) -> None: ... + +class getJobStatusByJobId_s(_message.Message): + __slots__ = ("jobId",) + JOBID_FIELD_NUMBER: _ClassVar[int] + jobId: str + def __init__(self, jobId: _Optional[str] = ...) -> None: ... + +class rpcRequest(_message.Message): + __slots__ = ("ts", "jobId", "jobType", "rpcData", "slotInfo", "swapDeny", "getJobStatusByJobId", "languageType") + TS_FIELD_NUMBER: _ClassVar[int] + JOBID_FIELD_NUMBER: _ClassVar[int] + JOBTYPE_FIELD_NUMBER: _ClassVar[int] + RPCDATA_FIELD_NUMBER: _ClassVar[int] + SLOTINFO_FIELD_NUMBER: _ClassVar[int] + SWAPDENY_FIELD_NUMBER: _ClassVar[int] + GETJOBSTATUSBYJOBID_FIELD_NUMBER: _ClassVar[int] + LANGUAGETYPE_FIELD_NUMBER: _ClassVar[int] + ts: int + jobId: str + jobType: jobType_e + rpcData: rpcData_s + slotInfo: slotControl_s + swapDeny: swapDenyReason_e + getJobStatusByJobId: getJobStatusByJobId_s + languageType: languageType_e + def __init__(self, ts: _Optional[int] = ..., jobId: _Optional[str] = ..., jobType: _Optional[_Union[jobType_e, str]] = ..., rpcData: _Optional[_Union[rpcData_s, _Mapping]] = ..., slotInfo: _Optional[_Union[slotControl_s, _Mapping]] = ..., swapDeny: _Optional[_Union[swapDenyReason_e, str]] = ..., getJobStatusByJobId: _Optional[_Union[getJobStatusByJobId_s, _Mapping]] = ..., languageType: _Optional[_Union[languageType_e, str]] = ...) -> None: ... + +class jobStatusByJobIdResponse_s(_message.Message): + __slots__ = ("jobId", "jobStatus", "jobResult") + JOBID_FIELD_NUMBER: _ClassVar[int] + JOBSTATUS_FIELD_NUMBER: _ClassVar[int] + JOBRESULT_FIELD_NUMBER: _ClassVar[int] + jobId: str + jobStatus: jobStatus_e + jobResult: jobResult_e + def __init__(self, jobId: _Optional[str] = ..., jobStatus: _Optional[_Union[jobStatus_e, str]] = ..., jobResult: _Optional[_Union[jobResult_e, str]] = ...) -> None: ... + +class rpcResponse(_message.Message): + __slots__ = ("ts", "deviceId", "jobId", "jobStatus", "jobResult", "jobStatusByJobIdResponse") + TS_FIELD_NUMBER: _ClassVar[int] + DEVICEID_FIELD_NUMBER: _ClassVar[int] + JOBID_FIELD_NUMBER: _ClassVar[int] + JOBSTATUS_FIELD_NUMBER: _ClassVar[int] + JOBRESULT_FIELD_NUMBER: _ClassVar[int] + JOBSTATUSBYJOBIDRESPONSE_FIELD_NUMBER: _ClassVar[int] + ts: int + deviceId: str + jobId: str + jobStatus: jobStatus_e + jobResult: jobResult_e + jobStatusByJobIdResponse: jobStatusByJobIdResponse_s + def __init__(self, ts: _Optional[int] = ..., deviceId: _Optional[str] = ..., jobId: _Optional[str] = ..., jobStatus: _Optional[_Union[jobStatus_e, str]] = ..., jobResult: _Optional[_Union[jobResult_e, str]] = ..., jobStatusByJobIdResponse: _Optional[_Union[jobStatusByJobIdResponse_s, _Mapping]] = ...) -> None: ... diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..56eeba8 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,7 @@ +Flask +Flask-SocketIO +Flask-SQLAlchemy +psycopg2-binary +paho-mqtt +protobuf +python-dotenv \ No newline at end of file