initial commit

This commit is contained in:
2025-06-02 15:45:17 +03:00
commit 5baca9f3be
12 changed files with 3402 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Ignore the virtual environment folder
track/

9
collors info.txt Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

2
reqirements.txt Normal file
View File

@@ -0,0 +1,2 @@
kivy
cryptography

View File

@@ -0,0 +1 @@
gAAAAABoPZqrY8gNIQXxQ_TAwdtbkZWX-zhwrx7hLuBFXH0M47fThQkAAG0vi8pMJhRnFipZ7t2Nj7SoVhjQqwHo4WOs7IH7lJCKSReeQT-1MdB0fYG16foBJpxK29kz-F9YRjCELQ0m

1
resources/key.key Normal file
View File

@@ -0,0 +1 @@
wetp_PNG9CC5432-W9H3rUbaqIurwldZxHlOgori5kY=

View File

@@ -0,0 +1 @@
gAAAAABoPZo4DMcBt7anBtvn5wMfvWpsjR4Zi1BPdQhvnvSj0SyQmrQEF1uhsUjf0Y6Le3vBPKSplUypYURK_BUlJMbxUh2yI93KCcXdMJIy23TyNlVlt7dv1mPN3aQAAd4TqhA_dEVPAfBtT5ckcQAXqCRwd1RjRp06Qgz3XNHeLs14MxGet0rbWCLppopr6BTGulvbAdvCFoDqmRFYbfyWYlqqsN4-ZF0Q0qObOVPn7hqYHFTenK6tidi-JOx7Lx0jrCZWA2V0OgwCZOsTA-BYZeWK8RI2U645SJ_DBTBwK2avb6wDduJKOOIKJWKdUF5HaN3Ffc61KWxYO_8Eb21bYmNsDB31l9n-OgJLdzStT-WqLMmmw027YG_SZJsv-P2Ce0lCaw29s2Lbh_2VxWShJW6aDRxibZgHK3CoPajqR0MAkGJxT2w7pt7wT1zeMKHu51q3yPpV

BIN
resources/track.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

80
test_traccar_trip.py Normal file
View 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
View 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
View 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)