initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Ignore the virtual environment folder
|
||||||
|
track/
|
||||||
9
collors info.txt
Normal file
9
collors info.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
To update the colors to the specified values, we will convert the hex color codes to RGBA format (values between 0 and 1) and update the `server_box_color` property in the `HomeScreen` class.
|
||||||
|
|
||||||
|
Here are the RGBA equivalents of the provided hex colors:
|
||||||
|
|
||||||
|
- **Yellow (#FB8D14)**: `(0.984, 0.553, 0.078, 1)`
|
||||||
|
- **Red (#E8083E)**: `(0.909, 0.031, 0.243, 1)`
|
||||||
|
- **Green (#02864A)**: `(0.008, 0.525, 0.290, 1)`
|
||||||
|
The RGBA equivalent of `#573CFA` is `(0.341, 0.235, 0.980, 1)`.
|
||||||
|
|
||||||
358
main.py
Normal file
358
main.py
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
import kivy
|
||||||
|
from kivy.app import App
|
||||||
|
from kivy.uix.screenmanager import ScreenManager, Screen
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
from kivy.clock import Clock
|
||||||
|
from kivy.properties import StringProperty, ListProperty
|
||||||
|
from utils import get_devices_from_server
|
||||||
|
from utils import check_server_settings # Import the refactored function
|
||||||
|
from utils import test_connection # Import the refactored function
|
||||||
|
from datetime import date
|
||||||
|
from kivy.uix.popup import Popup
|
||||||
|
from kivy.uix.gridlayout import GridLayout
|
||||||
|
from kivy.uix.button import Button
|
||||||
|
from kivy.uix.label import Label
|
||||||
|
kivy.require("2.0.0") # Ensure the correct Kivy version is used
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
RESOURCES_FOLDER = "resources"
|
||||||
|
CREDENTIALS_FILE = os.path.join(RESOURCES_FOLDER, "credentials.enc")
|
||||||
|
KEY_FILE = os.path.join(RESOURCES_FOLDER, "key.key")
|
||||||
|
|
||||||
|
|
||||||
|
# Utility functions for encryption
|
||||||
|
def generate_key():
|
||||||
|
"""Generate and save a key for encryption."""
|
||||||
|
if not os.path.exists(KEY_FILE):
|
||||||
|
key = Fernet.generate_key()
|
||||||
|
with open(KEY_FILE, "wb") as key_file:
|
||||||
|
key_file.write(key)
|
||||||
|
|
||||||
|
|
||||||
|
def load_key():
|
||||||
|
"""Load the encryption key."""
|
||||||
|
with open(KEY_FILE, "rb") as key_file:
|
||||||
|
return key_file.read()
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_data(data):
|
||||||
|
"""Encrypt data using the encryption key."""
|
||||||
|
key = load_key()
|
||||||
|
fernet = Fernet(key)
|
||||||
|
return fernet.encrypt(data.encode())
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_data(data):
|
||||||
|
"""Decrypt data using the encryption key."""
|
||||||
|
key = load_key()
|
||||||
|
fernet = Fernet(key)
|
||||||
|
return fernet.decrypt(data).decode()
|
||||||
|
|
||||||
|
|
||||||
|
# Login Screen
|
||||||
|
class LoginScreen(Screen):
|
||||||
|
def login(self):
|
||||||
|
"""Handle login and save credentials."""
|
||||||
|
username = self.ids.username_input.text.strip()
|
||||||
|
password = self.ids.password_input.text.strip()
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
self.manager.get_screen("home").ids.result_label.text = "Please fill in all fields."
|
||||||
|
return
|
||||||
|
|
||||||
|
# Encrypt and save credentials
|
||||||
|
credentials = {"username": username, "password": password}
|
||||||
|
encrypted_data = encrypt_data(json.dumps(credentials))
|
||||||
|
with open(CREDENTIALS_FILE, "wb") as file:
|
||||||
|
file.write(encrypted_data)
|
||||||
|
|
||||||
|
# Navigate to the home screen
|
||||||
|
self.manager.current = "home"
|
||||||
|
|
||||||
|
|
||||||
|
# Settings Screen (renamed from MainScreen)
|
||||||
|
class SettingsScreen(Screen):
|
||||||
|
server_response = "Waiting to test connection..."
|
||||||
|
|
||||||
|
def on_pre_enter(self):
|
||||||
|
"""Load existing settings into the input fields when the screen is entered."""
|
||||||
|
settings_file = os.path.join(RESOURCES_FOLDER, "server_settings.enc")
|
||||||
|
if os.path.exists(settings_file):
|
||||||
|
try:
|
||||||
|
with open(settings_file, "rb") as file:
|
||||||
|
encrypted_data = file.read()
|
||||||
|
decrypted_data = decrypt_data(encrypted_data)
|
||||||
|
settings_data = json.loads(decrypted_data)
|
||||||
|
|
||||||
|
# Populate the input fields with the existing settings
|
||||||
|
self.ids.server_url_input.text = settings_data.get("server_url", "")
|
||||||
|
self.ids.username_input.text = settings_data.get("username", "")
|
||||||
|
self.ids.password_input.text = settings_data.get("password", "")
|
||||||
|
self.ids.token_input.text = settings_data.get("token", "") # Populate the token field
|
||||||
|
except Exception as e:
|
||||||
|
self.ids.result_label.text = f"Failed to load settings: {str(e)}"
|
||||||
|
else:
|
||||||
|
# Clear the input fields if no settings exist
|
||||||
|
self.ids.server_url_input.text = ""
|
||||||
|
self.ids.username_input.text = ""
|
||||||
|
self.ids.password_input.text = ""
|
||||||
|
self.ids.token_input.text = ""
|
||||||
|
|
||||||
|
def test_connection(self):
|
||||||
|
"""Test the connection with the Traccar server."""
|
||||||
|
# Get input values from the screen
|
||||||
|
server_url = self.ids.server_url_input.text.strip()
|
||||||
|
username = self.ids.username_input.text.strip()
|
||||||
|
password = self.ids.password_input.text.strip()
|
||||||
|
token = self.ids.token_input.text.strip() # Get the token input
|
||||||
|
|
||||||
|
# Call the refactored function
|
||||||
|
result = test_connection(server_url, username, password, token)
|
||||||
|
|
||||||
|
# Update the UI based on the result
|
||||||
|
self.server_response = result["message"]
|
||||||
|
self.ids.result_label.text = self.server_response
|
||||||
|
|
||||||
|
def save_settings(self):
|
||||||
|
"""Save the server settings to an encrypted file and navigate to the HomeScreen."""
|
||||||
|
server_url = self.ids.server_url_input.text.strip()
|
||||||
|
username = self.ids.username_input.text.strip()
|
||||||
|
password = self.ids.password_input.text.strip()
|
||||||
|
token = self.ids.token_input.text.strip() # Get the token input
|
||||||
|
|
||||||
|
if not server_url or not username or not password or not token:
|
||||||
|
self.ids.result_label.text = "Please fill in all fields."
|
||||||
|
return
|
||||||
|
|
||||||
|
settings_data = {
|
||||||
|
"server_url": server_url,
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"token": token, # Include the token in the data
|
||||||
|
}
|
||||||
|
encrypted_data = encrypt_data(json.dumps(settings_data))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Save the encrypted data to the server_settings.enc file
|
||||||
|
settings_file = os.path.join(RESOURCES_FOLDER, "server_settings.enc")
|
||||||
|
with open(settings_file, "wb") as file:
|
||||||
|
file.write(encrypted_data)
|
||||||
|
|
||||||
|
self.ids.result_label.text = "Settings saved successfully!"
|
||||||
|
# Navigate back to the HomeScreen
|
||||||
|
self.manager.current = "home"
|
||||||
|
except Exception as e:
|
||||||
|
self.ids.result_label.text = f"Failed to save settings: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
|
# Home Screen
|
||||||
|
class HomeScreen(Screen):
|
||||||
|
server_info_text = StringProperty("LOADING DATA...") # Default text for the label
|
||||||
|
server_box_color = ListProperty([0.984, 0.553, 0.078, 1]) # Default yellow color (#FB8D14)
|
||||||
|
device_mapping = {} # Store the mapping of device names to IDs
|
||||||
|
|
||||||
|
def on_pre_enter(self):
|
||||||
|
"""Start the flow for checking server information and connection."""
|
||||||
|
self.server_box_color = [0.984, 0.553, 0.078, 1] # Yellow color (#FB8D14)
|
||||||
|
self.server_info_text = "LOADING DATA..."
|
||||||
|
Clock.schedule_once(self.check_server_settings, 1) # Wait 1 second before checking settings
|
||||||
|
|
||||||
|
def check_server_settings(self, dt):
|
||||||
|
"""Check server settings and update the UI."""
|
||||||
|
settings = check_server_settings() # Call the refactored function
|
||||||
|
if settings:
|
||||||
|
server_url = settings["server_url"]
|
||||||
|
username = settings["username"]
|
||||||
|
password = settings["password"]
|
||||||
|
token = settings["token"]
|
||||||
|
|
||||||
|
# Update the label to indicate checking connection
|
||||||
|
self.server_info_text = f"CHECKING server: {server_url}"
|
||||||
|
self.server_box_color = [0.984, 0.553, 0.078, 1] # Keep yellow color (#FB8D14)
|
||||||
|
self.ids.devices_spinner.text = "Loading devices..." # Show loading text
|
||||||
|
|
||||||
|
# Test the connection after loading settings
|
||||||
|
Clock.schedule_once(lambda dt: self.test_connection(server_url, username, password, token), 1)
|
||||||
|
else:
|
||||||
|
self.server_info_text = "Go to settings and set Traccar Server"
|
||||||
|
self.server_box_color = [0.909, 0.031, 0.243, 1] # Red color (#E8083E)
|
||||||
|
self.ids.devices_spinner.text = "No devices available"
|
||||||
|
|
||||||
|
def test_connection(self, server_url, username, password, token):
|
||||||
|
"""Test the connection with the Traccar server."""
|
||||||
|
try:
|
||||||
|
# Test the connection with the server
|
||||||
|
headers = {"Authorization": f"Bearer {token}"} if token else None
|
||||||
|
auth = None if token else (username, password)
|
||||||
|
|
||||||
|
print(f"Testing connection to {server_url}")
|
||||||
|
response = requests.get(f"{server_url}/api/server", headers=headers, auth=auth, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
# Update the label and box color for a successful connection
|
||||||
|
self.server_info_text = f"Connected to {server_url}"
|
||||||
|
self.server_box_color = [0.008, 0.525, 0.290, 1] # Green color (#02864A)
|
||||||
|
print(f"Connection successful: {self.server_info_text}")
|
||||||
|
|
||||||
|
# Fetch devices and populate the dropdown
|
||||||
|
devices = get_devices_from_server()
|
||||||
|
if devices:
|
||||||
|
self.device_mapping = devices # Store the mapping of device names to IDs
|
||||||
|
device_names = list(devices.keys()) # Get the list of device names
|
||||||
|
self.ids.devices_spinner.values = device_names # Populate the dropdown
|
||||||
|
self.ids.devices_spinner.text = "Select a device" # Default text after loading
|
||||||
|
else:
|
||||||
|
self.ids.devices_spinner.text = "No devices found"
|
||||||
|
self.ids.devices_spinner.values = []
|
||||||
|
else:
|
||||||
|
# Update the label and box color for a failed connection
|
||||||
|
self.server_info_text = f"Error: {response.status_code} - {response.reason}"
|
||||||
|
self.server_box_color = [0.909, 0.031, 0.243, 1] # Red color (#E8083E)
|
||||||
|
print(f"Connection failed: {self.server_info_text}")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
# Update the label and box color for a connection error
|
||||||
|
self.server_info_text = f"Connection failed: {str(e)}"
|
||||||
|
self.server_box_color = [0.909, 0.031, 0.243, 1] # Red color (#E8083E)
|
||||||
|
print(f"Connection error: {str(e)}")
|
||||||
|
|
||||||
|
def on_device_selected(self, device_name):
|
||||||
|
"""Change the background color of the Spinner when a device is selected."""
|
||||||
|
if device_name != "Loading devices..." and device_name != "No devices found":
|
||||||
|
self.ids.devices_spinner.background_color = (0.008, 0.525, 0.290, 1) # Green color (#02864A)
|
||||||
|
print(f"Device selected: {device_name}")
|
||||||
|
else:
|
||||||
|
self.ids.devices_spinner.background_color = (1, 1, 1, 1) # Reset to white if no valid device is selected
|
||||||
|
|
||||||
|
def open_date_picker(self):
|
||||||
|
"""Open a popup to select a date."""
|
||||||
|
today = date.today()
|
||||||
|
selected_date = [None] # Use a mutable object to store the selected date
|
||||||
|
|
||||||
|
def on_date_selected(instance):
|
||||||
|
selected_date[0] = instance.text
|
||||||
|
# Update the button text with the selected date
|
||||||
|
self.ids.date_picker_button.text = f"Date: {today.year}-{today.month:02d}-{int(selected_date[0]):02d}"
|
||||||
|
# Change the background color of the button to green
|
||||||
|
self.ids.date_picker_button.background_color = (0.008, 0.525, 0.290, 1) # Green color (#02864A)
|
||||||
|
print(f"Date selected: {self.ids.date_picker_button.text}")
|
||||||
|
popup.dismiss()
|
||||||
|
|
||||||
|
# Create a popup with a grid layout for the days
|
||||||
|
layout = GridLayout(cols=7, spacing=5, padding=10)
|
||||||
|
for day in range(1, 32): # Assuming a maximum of 31 days in a month
|
||||||
|
try:
|
||||||
|
current_date = date(today.year, today.month, day)
|
||||||
|
button = Button(text=str(day), size_hint=(None, None), size=(40, 40))
|
||||||
|
button.bind(on_press=on_date_selected)
|
||||||
|
layout.add_widget(button)
|
||||||
|
except ValueError:
|
||||||
|
# Skip invalid dates (e.g., February 30)
|
||||||
|
pass
|
||||||
|
|
||||||
|
popup = Popup(title="Select a Date", content=layout, size_hint=(0.8, 0.8))
|
||||||
|
popup.open()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_trip_server_data(self):
|
||||||
|
"""Handle the Get trip server data button press."""
|
||||||
|
selected_device = self.ids.devices_spinner.text
|
||||||
|
selected_date = self.ids.date_picker_button.text
|
||||||
|
|
||||||
|
if selected_device == "Loading devices..." or selected_device == "No devices found":
|
||||||
|
print("No valid device selected.")
|
||||||
|
self.ids.result_label.text = "Please select a valid device."
|
||||||
|
return
|
||||||
|
|
||||||
|
if selected_date == "Select Date":
|
||||||
|
print("No valid date selected.")
|
||||||
|
self.ids.result_label.text = "Please select a valid date."
|
||||||
|
return
|
||||||
|
|
||||||
|
# Simulate fetching trip data from the server
|
||||||
|
print(f"Fetching trip data for device: {selected_device} on date: {selected_date}")
|
||||||
|
self.ids.result_label.text = f"Fetching trip data for {selected_device} on {selected_date}..."
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterScreen(Screen):
|
||||||
|
def create_user(self):
|
||||||
|
"""Handle user creation."""
|
||||||
|
username = self.ids.set_username_input.text.strip()
|
||||||
|
password = self.ids.set_password_input.text.strip()
|
||||||
|
confirm_password = self.ids.confirm_password_input.text.strip()
|
||||||
|
email = self.ids.set_email_input.text.strip()
|
||||||
|
|
||||||
|
if not username or not password or not confirm_password or not email:
|
||||||
|
self.ids.result_label.text = "Please fill in all fields."
|
||||||
|
return
|
||||||
|
|
||||||
|
if password != confirm_password:
|
||||||
|
self.ids.result_label.text = "Passwords do not match."
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if the username or email already exists
|
||||||
|
if self.user_exists(username, email):
|
||||||
|
self.ids.result_label.text = "User or email already exists."
|
||||||
|
return
|
||||||
|
|
||||||
|
# Save user data (encrypted)
|
||||||
|
user_data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"email": email,
|
||||||
|
}
|
||||||
|
encrypted_data = encrypt_data(json.dumps(user_data))
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(CREDENTIALS_FILE, "ab") as file: # Append encrypted data
|
||||||
|
file.write(encrypted_data + b"\n") # Add a newline for separation
|
||||||
|
except Exception as e:
|
||||||
|
self.ids.result_label.text = f"Failed to save user: {str(e)}"
|
||||||
|
return
|
||||||
|
|
||||||
|
self.ids.result_label.text = "User created successfully!"
|
||||||
|
# Navigate back to the login screen
|
||||||
|
self.manager.current = "login"
|
||||||
|
|
||||||
|
def user_exists(self, username, email):
|
||||||
|
"""Check if a username or email already exists in the credentials.enc file."""
|
||||||
|
try:
|
||||||
|
with open(CREDENTIALS_FILE, "rb") as file:
|
||||||
|
for line in file:
|
||||||
|
decrypted_data = decrypt_data(line.strip())
|
||||||
|
user = json.loads(decrypted_data)
|
||||||
|
if user["username"] == username or user["email"] == email:
|
||||||
|
return True
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
self.ids.result_label.text = f"Error checking user: {str(e)}"
|
||||||
|
return False
|
||||||
|
# Main App
|
||||||
|
class TraccarApp(App):
|
||||||
|
def build(self):
|
||||||
|
# Ensure resources folder exists
|
||||||
|
if not os.path.exists(RESOURCES_FOLDER):
|
||||||
|
os.makedirs(RESOURCES_FOLDER)
|
||||||
|
|
||||||
|
# Generate encryption key if it doesn't exist
|
||||||
|
generate_key()
|
||||||
|
|
||||||
|
# Screen manager
|
||||||
|
sm = ScreenManager()
|
||||||
|
sm.add_widget(LoginScreen(name="login"))
|
||||||
|
sm.add_widget(HomeScreen(name="home")) # Add the HomeScreen
|
||||||
|
sm.add_widget(SettingsScreen(name="settings")) # Add the renamed SettingsScreen
|
||||||
|
sm.add_widget(RegisterScreen(name="register")) # Add the RegisterScreen
|
||||||
|
|
||||||
|
# Debugging: Print all screen names
|
||||||
|
print("Screens added to ScreenManager:", [screen.name for screen in sm.screens])
|
||||||
|
|
||||||
|
return sm
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TraccarApp().run()
|
||||||
2381
openapi.yaml
Normal file
2381
openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
2
reqirements.txt
Normal file
2
reqirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
kivy
|
||||||
|
cryptography
|
||||||
1
resources/credentials.enc
Normal file
1
resources/credentials.enc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
gAAAAABoPZqrY8gNIQXxQ_TAwdtbkZWX-zhwrx7hLuBFXH0M47fThQkAAG0vi8pMJhRnFipZ7t2Nj7SoVhjQqwHo4WOs7IH7lJCKSReeQT-1MdB0fYG16foBJpxK29kz-F9YRjCELQ0m
|
||||||
1
resources/key.key
Normal file
1
resources/key.key
Normal file
@@ -0,0 +1 @@
|
|||||||
|
wetp_PNG9CC5432-W9H3rUbaqIurwldZxHlOgori5kY=
|
||||||
1
resources/server_settings.enc
Normal file
1
resources/server_settings.enc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
gAAAAABoPZo4DMcBt7anBtvn5wMfvWpsjR4Zi1BPdQhvnvSj0SyQmrQEF1uhsUjf0Y6Le3vBPKSplUypYURK_BUlJMbxUh2yI93KCcXdMJIy23TyNlVlt7dv1mPN3aQAAd4TqhA_dEVPAfBtT5ckcQAXqCRwd1RjRp06Qgz3XNHeLs14MxGet0rbWCLppopr6BTGulvbAdvCFoDqmRFYbfyWYlqqsN4-ZF0Q0qObOVPn7hqYHFTenK6tidi-JOx7Lx0jrCZWA2V0OgwCZOsTA-BYZeWK8RI2U645SJ_DBTBwK2avb6wDduJKOOIKJWKdUF5HaN3Ffc61KWxYO_8Eb21bYmNsDB31l9n-OgJLdzStT-WqLMmmw027YG_SZJsv-P2Ce0lCaw29s2Lbh_2VxWShJW6aDRxibZgHK3CoPajqR0MAkGJxT2w7pt7wT1zeMKHu51q3yPpV
|
||||||
BIN
resources/track.png
Normal file
BIN
resources/track.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
80
test_traccar_trip.py
Normal file
80
test_traccar_trip.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
def get_positions(server_url, token, device_id=None, from_time=None, to_time=None):
|
||||||
|
"""
|
||||||
|
Fetch position information from the Traccar server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_url (str): The URL of the Traccar server.
|
||||||
|
token (str): The authentication token.
|
||||||
|
device_id (int, optional): The ID of the device. If not provided, fetches positions for all devices.
|
||||||
|
from_time (str, optional): The start time in ISO 8601 format (e.g., '2024-04-05T00:00:00Z').
|
||||||
|
to_time (str, optional): The end time in ISO 8601 format (e.g., '2024-04-05T23:59:59Z').
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: The position information.
|
||||||
|
"""
|
||||||
|
# Ensure the server_url has a valid scheme
|
||||||
|
if not server_url.startswith("http://") and not server_url.startswith("https://"):
|
||||||
|
server_url = f"https://{server_url}" # Default to https:// if no scheme is provided
|
||||||
|
|
||||||
|
# Set the Authorization header with the token
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
# API endpoint for fetching positions
|
||||||
|
url = f"{server_url}/positions"
|
||||||
|
|
||||||
|
# Request payload
|
||||||
|
payload = {}
|
||||||
|
if device_id:
|
||||||
|
payload["deviceId"] = device_id
|
||||||
|
if from_time and to_time:
|
||||||
|
payload["from"] = from_time
|
||||||
|
payload["to"] = to_time
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Log the payload for debugging
|
||||||
|
print(f"Request Payload: {payload}")
|
||||||
|
|
||||||
|
# Make the API request
|
||||||
|
response = requests.get(url, params=payload, headers=headers)
|
||||||
|
|
||||||
|
# Log the response status and content for debugging
|
||||||
|
print(f"Response Status Code: {response.status_code}")
|
||||||
|
print(f"Response Content: {response.text}")
|
||||||
|
|
||||||
|
# Check if the response was successful
|
||||||
|
if response.status_code == 200:
|
||||||
|
positions = response.json()
|
||||||
|
print(f"Retrieved {len(positions)} positions:")
|
||||||
|
for position in positions:
|
||||||
|
print(position)
|
||||||
|
return positions
|
||||||
|
elif response.status_code == 400:
|
||||||
|
print("Bad Request: Please check the request payload and token.")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
print(f"Failed to fetch positions: {response.status_code} - {response.reason}")
|
||||||
|
return None
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching positions: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Test the function
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Manually enter the server URL and token
|
||||||
|
server_url = input("Enter the server URL (e.g., https://gps.moto-adv.com/api): ").strip()
|
||||||
|
token = input("Enter the authentication token: ").strip()
|
||||||
|
|
||||||
|
# Optional: Enter device ID and date range
|
||||||
|
device_id = input("Enter the device ID (leave blank for all devices): ").strip()
|
||||||
|
device_id = int(device_id) if device_id else None
|
||||||
|
from_time = input("Enter the start time (ISO 8601, e.g., 2024-04-05T00:00:00Z, leave blank for none): ").strip()
|
||||||
|
to_time = input("Enter the end time (ISO 8601, e.g., 2024-04-05T23:59:59Z, leave blank for none): ").strip()
|
||||||
|
|
||||||
|
positions = get_positions(server_url, token, device_id, from_time, to_time)
|
||||||
|
if positions:
|
||||||
|
print("Position data retrieved successfully!")
|
||||||
|
else:
|
||||||
|
print("No position data found or an error occurred.")
|
||||||
347
traccar.kv
Normal file
347
traccar.kv
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
<LoginScreen>:
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
padding: 20
|
||||||
|
spacing: 20
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.11, 0.10, 0.15, 1 # Background color: #1C1A27
|
||||||
|
Rectangle:
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
|
||||||
|
Image:
|
||||||
|
source: "resources/track.png"
|
||||||
|
size_hint: (1, 0.66) # 2/3 of the screen height
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: username_input
|
||||||
|
hint_text: "Username"
|
||||||
|
multiline: False
|
||||||
|
font_size: 20 # Text size
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_y: None # Fix height
|
||||||
|
size_hint_x: 0.8 # Center horizontally
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: password_input
|
||||||
|
hint_text: "Password"
|
||||||
|
multiline: False
|
||||||
|
password: True
|
||||||
|
font_size: 20 # Text size
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_y: None # Fix height
|
||||||
|
size_hint_x: 0.8 # Center horizontally
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "horizontal"
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50 # Fixed height for the button group
|
||||||
|
size_hint_x: 0.8 # Match the width of the TextInput fields
|
||||||
|
pos_hint: {"center_x": 0.5} # Center horizontally
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Login"
|
||||||
|
font_size: 20 # Match the font size of the TextInput fields
|
||||||
|
size_hint_x: 0.5 # Half the width of the BoxLayout
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50 # Fixed height for the button
|
||||||
|
background_color: 0.341, 0.235, 0.980, 1 # Purple color (#573CFA)
|
||||||
|
on_press: root.login()
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Register"
|
||||||
|
font_size: 20
|
||||||
|
size_hint_x: 0.5
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50
|
||||||
|
background_color: 0.341, 0.235, 0.980, 1 # Purple color (#573CFA)
|
||||||
|
on_press: app.root.current = "register" # Navigate to the RegisterScreen
|
||||||
|
|
||||||
|
<RegisterScreen>:
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
padding: 20
|
||||||
|
spacing: 20
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.11, 0.10, 0.15, 1 # Background color: #1C1A27
|
||||||
|
Rectangle:
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
|
||||||
|
Image:
|
||||||
|
source: "resources/track.png"
|
||||||
|
size_hint: (1, 0.66) # 2/3 of the screen height
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: set_username_input
|
||||||
|
hint_text: "Set Username"
|
||||||
|
multiline: False
|
||||||
|
font_size: 20 # Text size
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_y: None # Fix height
|
||||||
|
size_hint_x: 0.8 # Center horizontally
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: set_password_input
|
||||||
|
hint_text: "Set Password"
|
||||||
|
multiline: False
|
||||||
|
password: True
|
||||||
|
font_size: 20 # Text size
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_y: None # Fix height
|
||||||
|
size_hint_x: 0.8 # Center horizontally
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: confirm_password_input
|
||||||
|
hint_text: "Confirm Password"
|
||||||
|
multiline: False
|
||||||
|
password: True
|
||||||
|
font_size: 20 # Text size
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_y: None # Fix height
|
||||||
|
size_hint_x: 0.8 # Center horizontally
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: set_email_input
|
||||||
|
hint_text: "Set Email"
|
||||||
|
multiline: False
|
||||||
|
font_size: 20 # Text size
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_y: None # Fix height
|
||||||
|
size_hint_x: 0.8 # Center horizontally
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Create Username"
|
||||||
|
font_size: 20 # Match the font size of the TextInput fields
|
||||||
|
size_hint_x: 0.8 # Match the width of the TextInput fields
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50 # Fixed height for the button
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
on_press: root.create_user()
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Back to Login"
|
||||||
|
font_size: 20
|
||||||
|
size_hint_x: 0.8
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
on_press: app.root.current = "login"
|
||||||
|
|
||||||
|
Label:
|
||||||
|
id: result_label
|
||||||
|
text: ""
|
||||||
|
size_hint: (1, 0.2)
|
||||||
|
|
||||||
|
<HomeScreen>:
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
padding: 20
|
||||||
|
spacing: 20
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.11, 0.10, 0.15, 1 # Background color: #1C1A27
|
||||||
|
Rectangle:
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
|
||||||
|
# First row: Server settings
|
||||||
|
BoxLayout:
|
||||||
|
id: server_info_settings
|
||||||
|
orientation: "horizontal"
|
||||||
|
size_hint_y: None
|
||||||
|
height: 30
|
||||||
|
spacing: 10
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: root.server_box_color # Dynamic color for the box
|
||||||
|
Rectangle:
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
|
||||||
|
Label:
|
||||||
|
id: server_info_label
|
||||||
|
text: root.server_info_text # Dynamic text for the label
|
||||||
|
font_size: 14 # Reduced font size
|
||||||
|
size_hint_x: 0.8
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Settings"
|
||||||
|
size_hint_x: 0.2
|
||||||
|
background_color: 0.341, 0.235, 0.980, 1 # Purple color (#573CFA)
|
||||||
|
on_press: app.root.current = "settings"
|
||||||
|
|
||||||
|
# Second row: Frame for device and date selection
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
size_hint_y: None
|
||||||
|
height: 120 # Adjusted height for the frame
|
||||||
|
spacing: 10
|
||||||
|
padding: 10
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.2, 0.2, 0.2, 1 # Frame background color
|
||||||
|
Rectangle:
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
|
||||||
|
Label:
|
||||||
|
text: "Select device and the day of the data" # Main label
|
||||||
|
font_size: 14 # Reduced font size
|
||||||
|
size_hint_y: None
|
||||||
|
height: 20
|
||||||
|
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "horizontal"
|
||||||
|
size_hint_y: None
|
||||||
|
height: 30
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Spinner:
|
||||||
|
id: devices_spinner
|
||||||
|
text: "Loading devices..." # Default text
|
||||||
|
values: [] # Initially empty
|
||||||
|
size_hint: (0.5, None)
|
||||||
|
height: 25
|
||||||
|
font_size: 14 # Reduced font size
|
||||||
|
on_text: root.on_device_selected(self.text) # Trigger background color change
|
||||||
|
|
||||||
|
Button:
|
||||||
|
id: date_picker_button
|
||||||
|
text: "Select Date"
|
||||||
|
size_hint: (0.5, None)
|
||||||
|
height: 25
|
||||||
|
font_size: 14 # Reduced font size
|
||||||
|
on_press: root.open_date_picker()
|
||||||
|
|
||||||
|
# New row: Get trip server data button
|
||||||
|
Button:
|
||||||
|
id: get_trip_data_button
|
||||||
|
text: "Get trip server data"
|
||||||
|
size_hint: (1, None)
|
||||||
|
height: 30
|
||||||
|
font_size: 14
|
||||||
|
background_color: 0.341, 0.235, 0.980, 1 # Purple color (#573CFA)
|
||||||
|
on_press: root.get_trip_server_data()
|
||||||
|
|
||||||
|
# Third row: Result label
|
||||||
|
Label:
|
||||||
|
id: result_label
|
||||||
|
text: "Welcome to the Home Screen!"
|
||||||
|
font_size: 14 # Reduced font size
|
||||||
|
size_hint: (1, 0.8)
|
||||||
|
|
||||||
|
<SettingsScreen>:
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "vertical"
|
||||||
|
padding: 20
|
||||||
|
spacing: 20
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 0.11, 0.10, 0.15, 1 # Background color: #1C1A27
|
||||||
|
Rectangle:
|
||||||
|
pos: self.pos
|
||||||
|
size: self.size
|
||||||
|
|
||||||
|
Image:
|
||||||
|
source: "resources/track.png"
|
||||||
|
size_hint: (1, 0.66) # 2/3 of the screen height
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: server_url_input
|
||||||
|
hint_text: "Traccar Server URL"
|
||||||
|
multiline: False
|
||||||
|
font_size: 20
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_x: 0.8
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: username_input
|
||||||
|
hint_text: "Username"
|
||||||
|
multiline: False
|
||||||
|
font_size: 20
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_x: 0.8
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: password_input
|
||||||
|
hint_text: "Password"
|
||||||
|
multiline: False
|
||||||
|
password: True
|
||||||
|
font_size: 20
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_x: 0.8
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
TextInput:
|
||||||
|
id: token_input
|
||||||
|
hint_text: "Token"
|
||||||
|
multiline: False
|
||||||
|
font_size: 20
|
||||||
|
size_hint_y: None
|
||||||
|
height: self.minimum_height
|
||||||
|
size_hint_x: 0.8
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
|
||||||
|
BoxLayout:
|
||||||
|
orientation: "horizontal"
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50
|
||||||
|
size_hint_x: 0.8
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Test Connection"
|
||||||
|
size_hint_x: 0.5 # Half the width of the BoxLayout
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50
|
||||||
|
background_color: 0.341, 0.235, 0.980, 1 # Purple color (#573CFA)
|
||||||
|
on_press: root.test_connection()
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Save Settings"
|
||||||
|
size_hint_x: 0.5 # Half the width of the BoxLayout
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50
|
||||||
|
background_color: 0.008, 0.525, 0.290, 1 # Purple color (#573CFA)
|
||||||
|
on_press: root.save_settings()
|
||||||
|
|
||||||
|
Label:
|
||||||
|
id: result_label
|
||||||
|
text: "Waiting to test connection..."
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50
|
||||||
|
size_hint_x: 0.8
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: 1, 1, 1, 1 # White color
|
||||||
|
Line:
|
||||||
|
width: 1.5
|
||||||
|
rectangle: self.x, self.y, self.width, self.height
|
||||||
|
|
||||||
|
Button:
|
||||||
|
text: "Return to Home"
|
||||||
|
size_hint_y: None
|
||||||
|
height: 50
|
||||||
|
size_hint_x: 0.8
|
||||||
|
pos_hint: {"center_x": 0.5}
|
||||||
|
background_color: 0.341, 0.235, 0.980, 1 # Purple color (#573CFA)
|
||||||
|
on_press: app.root.current = "home"
|
||||||
220
utils.py
Normal file
220
utils.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import os # Import the os module
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
|
||||||
|
# Define the path to the server settings file
|
||||||
|
RESOURCES_FOLDER = "resources"
|
||||||
|
SERVER_SETTINGS_FILE = os.path.join(RESOURCES_FOLDER, "server_settings.enc")
|
||||||
|
|
||||||
|
def load_key():
|
||||||
|
"""Load the encryption key from the key file."""
|
||||||
|
key_file_path = os.path.join(RESOURCES_FOLDER, "key.key")
|
||||||
|
if not os.path.exists(key_file_path):
|
||||||
|
raise FileNotFoundError("Encryption key file not found.")
|
||||||
|
with open(key_file_path, "rb") as key_file:
|
||||||
|
return key_file.read()
|
||||||
|
|
||||||
|
def decrypt_data(encrypted_data):
|
||||||
|
"""Decrypt the encrypted data using the loaded key."""
|
||||||
|
key = load_key() # Load the key from the file
|
||||||
|
fernet = Fernet(key)
|
||||||
|
return fernet.decrypt(encrypted_data).decode()
|
||||||
|
|
||||||
|
def check_server_settings():
|
||||||
|
"""
|
||||||
|
Load server settings from a configuration file or encrypted storage.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing server settings (e.g., server_url, username, password, token).
|
||||||
|
"""
|
||||||
|
settings_file = os.path.join("resources", "server_settings.enc")
|
||||||
|
if not os.path.exists(settings_file):
|
||||||
|
print("Settings file not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(settings_file, "rb") as file:
|
||||||
|
encrypted_data = file.read()
|
||||||
|
decrypted_data = decrypt_data(encrypted_data)
|
||||||
|
settings = json.loads(decrypted_data)
|
||||||
|
return settings
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading settings: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_devices_from_server():
|
||||||
|
"""
|
||||||
|
Retrieve a list of devices from the Traccar server and create a mapping of device names to IDs.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary mapping device names to their IDs if the request is successful.
|
||||||
|
None: If the request fails.
|
||||||
|
"""
|
||||||
|
# Check if the server settings file exists
|
||||||
|
settings = check_server_settings()
|
||||||
|
if not settings:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Extract server details
|
||||||
|
server_url = settings.get("server_url")
|
||||||
|
token = settings.get("token") # Optional, if token is used for authentication
|
||||||
|
|
||||||
|
if not server_url:
|
||||||
|
print("Error: Missing server URL in server_settings.enc.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ensure the server_url has a valid scheme
|
||||||
|
if not server_url.startswith("http://") and not server_url.startswith("https://"):
|
||||||
|
server_url = f"https://{server_url}" # Default to https:// if no scheme is provided
|
||||||
|
|
||||||
|
# Determine authentication method
|
||||||
|
headers = {"Authorization": f"Bearer {token}"} if token else None
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
print("Error: Missing authentication details (token).")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Make a GET request to the /devices endpoint
|
||||||
|
response = requests.get(f"{server_url}/api/devices", headers=headers)
|
||||||
|
|
||||||
|
# Check the response status
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("Devices retrieved successfully!")
|
||||||
|
devices = response.json() # Get the list of devices
|
||||||
|
|
||||||
|
# Create a mapping of device names to IDs
|
||||||
|
device_mapping = {device.get("name", "Unnamed Device"): device.get("id", "Unknown ID") for device in devices}
|
||||||
|
|
||||||
|
# Debugging: Print the mapping
|
||||||
|
for name, device_id in device_mapping.items():
|
||||||
|
print(f"Device Name: {name}, Device ID: {device_id}")
|
||||||
|
|
||||||
|
return device_mapping # Return the mapping of device names to IDs
|
||||||
|
else:
|
||||||
|
print(f"Error: {response.status_code} - {response.reason}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error retrieving devices: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_route_info(device_id, selected_date):
|
||||||
|
"""
|
||||||
|
Fetch route information for a specific device and date from the Traccar server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (int): The ID of the device.
|
||||||
|
selected_date (str): The selected date in the format 'YYYY-MM-DD'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: The route information for the device on the selected date.
|
||||||
|
"""
|
||||||
|
# Load server settings
|
||||||
|
settings = check_server_settings()
|
||||||
|
if not settings:
|
||||||
|
print("Error: Unable to load server settings.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract server details
|
||||||
|
server_url = settings.get("server_url")
|
||||||
|
token = settings.get("token")
|
||||||
|
|
||||||
|
if not server_url:
|
||||||
|
print("Error: Missing server URL in settings.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
print("Error: Missing token in settings.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ensure the server_url has a valid scheme
|
||||||
|
if not server_url.startswith("http://") and not server_url.startswith("https://"):
|
||||||
|
server_url = f"https://{server_url}" # Default to https:// if no scheme is provided
|
||||||
|
|
||||||
|
# Set the Authorization header with the token
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
# Convert the selected date to ISO 8601 format for the API
|
||||||
|
start_time = f"{selected_date}T00:00:00Z"
|
||||||
|
end_time = f"{selected_date}T23:59:59Z"
|
||||||
|
|
||||||
|
# API endpoint for fetching route reports
|
||||||
|
url = f"{server_url}/reports/route"
|
||||||
|
|
||||||
|
# Request payload
|
||||||
|
payload = {
|
||||||
|
"deviceId": device_id,
|
||||||
|
"from": start_time,
|
||||||
|
"to": end_time,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Log the payload for debugging
|
||||||
|
print(f"Request Payload: {payload}")
|
||||||
|
|
||||||
|
# Make the API request
|
||||||
|
response = requests.get(url, params=payload, headers=headers)
|
||||||
|
|
||||||
|
# Log the response status and content for debugging
|
||||||
|
print(f"Response Status Code: {response.status_code}")
|
||||||
|
print(f"Response Content: {response.text}")
|
||||||
|
|
||||||
|
# Check if the request was successful
|
||||||
|
if response.status_code == 200:
|
||||||
|
route = response.json()
|
||||||
|
print(f"Route for device {device_id} on {selected_date}:")
|
||||||
|
for position in route:
|
||||||
|
print(position)
|
||||||
|
return route
|
||||||
|
elif response.status_code == 400:
|
||||||
|
print("Bad Request: Please check the request payload and token.")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
print(f"Failed to fetch route: {response.status_code} - {response.reason}")
|
||||||
|
return None
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching route: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def test_connection(server_url, username=None, password=None, token=None):
|
||||||
|
"""
|
||||||
|
Test the connection with the Traccar server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_url (str): The URL of the Traccar server.
|
||||||
|
username (str, optional): The username for basic authentication.
|
||||||
|
password (str, optional): The password for basic authentication.
|
||||||
|
token (str, optional): The token for bearer authentication.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the connection status and message.
|
||||||
|
"""
|
||||||
|
if not server_url:
|
||||||
|
return {"status": False, "message": "Please provide the server URL."}
|
||||||
|
|
||||||
|
if not token and (not username or not password):
|
||||||
|
return {"status": False, "message": "Please provide either a token or username and password."}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Determine authentication method
|
||||||
|
headers = {"Authorization": f"Bearer {token}"} if token else None
|
||||||
|
auth = None if token else (username, password)
|
||||||
|
|
||||||
|
# Make a GET request to the server
|
||||||
|
response = requests.get(f"{server_url}/api/server", headers=headers, auth=auth, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return {"status": True, "message": "Connection successful! Server is reachable."}
|
||||||
|
else:
|
||||||
|
return {"status": False, "message": f"Error: {response.status_code} - {response.reason}"}
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return {"status": False, "message": "Connection timed out. Please try again."}
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return {"status": False, "message": f"Connection failed: {str(e)}"}
|
||||||
|
|
||||||
|
# Call the function
|
||||||
|
test_device_id = 1 # Replace with the device ID from the spinner
|
||||||
|
test_date = "2025-06-01" # Replace with the selected date from the date picker
|
||||||
|
get_route_info(test_device_id, test_date)
|
||||||
Reference in New Issue
Block a user