feat(backend): implemented the backend of station dashboard web app

dev
Kirubakaran 2025-08-24 22:09:18 +05:30
commit ea1e8a9266
18 changed files with 1244 additions and 0 deletions

15
backend/.env Normal file
View File

@ -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://<user>:<password>@<host>:<port>/<dbname>
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"

Binary file not shown.

0
backend/core/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

View File

@ -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()

View File

@ -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

436
backend/main.py Normal file
View File

@ -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)

35
backend/models.py Normal file
View File

@ -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)

View File

Binary file not shown.

View File

@ -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;
}

File diff suppressed because one or more lines are too long

View File

@ -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: ...

7
backend/requirements.txt Normal file
View File

@ -0,0 +1,7 @@
Flask
Flask-SocketIO
Flask-SQLAlchemy
psycopg2-binary
paho-mqtt
protobuf
python-dotenv