commit 5baca9f3beb37fd3674fed1947b788f077c42478 Author: ske087 Date: Mon Jun 2 15:45:17 2025 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65ad9b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore the virtual environment folder +track/ \ No newline at end of file diff --git a/collors info.txt b/collors info.txt new file mode 100644 index 0000000..1863272 --- /dev/null +++ b/collors info.txt @@ -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)`. + diff --git a/main.py b/main.py new file mode 100644 index 0000000..ee330bf --- /dev/null +++ b/main.py @@ -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() \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..86ff751 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,2381 @@ +openapi: 3.1.0 +info: + title: Traccar + description: Traccar GPS tracking server API documentation. To use the API you need to have a server instance. For testing purposes you can use one of free [demo servers](https://www.traccar.org/demo-server/). For production use you can install your own server or get a [subscription service](https://www.traccar.org/product/tracking-server/). + contact: + name: Traccar Support + url: https://www.traccar.org/ + email: support@traccar.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: '6.6' +servers: + - url: https://demo.traccar.org/api + description: Demo Server 1 + - url: https://demo2.traccar.org/api + description: Demo Server 2 + - url: https://demo3.traccar.org/api + description: Demo Server 3 + - url: https://demo4.traccar.org/api + description: Demo Server 4 + - url: https://server.traccar.org/api + description: Subscription Server + - url: http://{host}:{port}/api + description: Other Server + variables: + host: + default: localhost + port: + enum: + - '8082' + - '80' + default: '8082' +security: + - BasicAuth: [] + - ApiKey: [] +tags: + - name: Server + description: Server information + - name: Session + description: User session management + - name: Devices + description: Device management + - name: Groups + description: Group management + - name: Users + description: User management + - name: Permissions + description: User permissions and other object linking + - name: Positions + description: Retrieving raw location information + - name: Events + description: Retrieving event information + - name: Reports + description: Reports generation + - name: Notifications + description: User notifications management + - name: Geofences + description: Geofence management + - name: Commands + description: Sending commands to devices and stored command management + - name: Attributes + description: Computed attributes management + - name: Drivers + description: Drivers management + - name: Maintenance + description: Maintenance management + - name: Calendars + description: Calendar management + - name: Statistics + description: Retrieving server statistics +paths: + /commands: + get: + summary: Fetch a list of Saved Commands + tags: + - Commands + description: Without params, it returns a list of Saved Commands the user has access to + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + - name: deviceId + in: query + description: Standard users can use this only with _deviceId_s, they have access to + schema: + type: integer + - name: groupId + in: query + description: >- + Standard users can use this only with _groupId_s, they have access + to + schema: + type: integer + - name: refresh + in: query + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Command' + post: + summary: Create a Saved Command + tags: + - Commands + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + x-codegen-request-body-name: body + /commands/{id}: + put: + summary: Update a Saved Command + tags: + - Commands + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + x-codegen-request-body-name: body + delete: + summary: Delete a Saved Command + tags: + - Commands + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /commands/send: + get: + summary: Fetch a list of Saved Commands supported by Device at the moment + description: >- + Return a list of saved commands linked to Device and its groups, + filtered by current Device protocol support + tags: + - Commands + parameters: + - name: deviceId + in: query + description: >- + Standard users can use this only with _deviceId_s, they have access + to + schema: + type: integer + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Command' + '400': + description: Could happen when the user doesn't have permission for the device + content: {} + post: + summary: Dispatch commands to device + description: Dispatch a new command or Saved Command if _body.id_ set + tags: + - Commands + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + required: true + responses: + '200': + description: Command sent + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + '202': + description: Command queued + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + '400': + description: >- + Could happen when the user doesn't have permission or an incorrect + command _type_ for the device + content: {} + x-codegen-request-body-name: body + /commands/types: + get: + summary: >- + Fetch a list of available Commands for the Device or all possible + Commands if Device ommited + tags: + - Commands + parameters: + - name: deviceId + in: query + description: >- + Internal device identifier. Only works if device has already + reported some locations + schema: + type: integer + - name: protocol + in: query + description: Protocol name. Can be used instead of device id + schema: + type: string + - name: textChannel + in: query + description: >- + When `true` return SMS commands. If not specified or `false` return + data commands + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CommandType' + '400': + description: >- + Could happen when trying to fetch from a device the user does not + have permission + content: {} + /devices: + get: + summary: Fetch a list of Devices + description: Without any params, returns a list of the user's devices + tags: + - Devices + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + - name: id + in: query + description: >- + To fetch one or more devices. Multiple params can be passed like + `id=31&id=42` + schema: + type: integer + - name: uniqueId + in: query + description: >- + To fetch one or more devices. Multiple params can be passed like + `uniqueId=333331&uniqieId=44442` + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Device' + '400': + description: No permission + content: {} + post: + summary: Create a Device + tags: + - Devices + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Device' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Device' + x-codegen-request-body-name: body + /devices/{id}: + put: + summary: Update a Device + tags: + - Devices + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Device' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Device' + x-codegen-request-body-name: body + delete: + summary: Delete a Device + tags: + - Devices + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /devices/{id}/accumulators: + put: + summary: Update total distance and hours of the Device + tags: + - Devices + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeviceAccumulators' + required: true + responses: + '204': + description: No Content + content: {} + x-codegen-request-body-name: body + /groups: + get: + summary: Fetch a list of Groups + description: Without any params, returns a list of the Groups the user belongs to + tags: + - Groups + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Group' + post: + summary: Create a Group + tags: + - Groups + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + '400': + description: No permission + content: {} + x-codegen-request-body-name: body + /groups/{id}: + put: + summary: Update a Group + tags: + - Groups + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + x-codegen-request-body-name: body + delete: + summary: Delete a Group + tags: + - Groups + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /permissions: + post: + summary: Link an Object to another Object + tags: + - Permissions + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Permission' + required: true + responses: + '204': + description: No Content + content: {} + '400': + description: No permission + content: {} + x-codegen-request-body-name: body + delete: + summary: Unlink an Object from another Object + tags: + - Permissions + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Permission' + required: true + responses: + '204': + description: No Content + content: {} + x-codegen-request-body-name: body + /positions: + get: + summary: Fetches a list of Positions + description: >- + We strongly recommend using [Traccar WebSocket + API](https://www.traccar.org/traccar-api/) instead of periodically + polling positions endpoint. Without any params, it returns a list of + last known positions for all the user's Devices. _from_ and _to_ fields + are not required with _id_. + tags: + - Positions + parameters: + - name: deviceId + in: query + description: >- + _deviceId_ is optional, but requires the _from_ and _to_ parameters + when used + schema: + type: integer + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + schema: + type: string + format: date-time + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + schema: + type: string + format: date-time + - name: id + in: query + description: >- + To fetch one or more positions. Multiple params can be passed like + `id=31&id=42` + schema: + type: integer + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Position' + text/csv: + schema: + type: array + items: + $ref: '#/components/schemas/Position' + application/gpx+xml: + schema: + type: array + items: + $ref: '#/components/schemas/Position' + delete: + summary: Deletes all the Positions of a device in the time span specified + description: '' + tags: + - Positions + parameters: + - name: deviceId + in: query + description: '' + schema: + type: integer + required: true + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + schema: + type: string + format: date-time + required: true + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + schema: + type: string + format: date-time + required: true + responses: + '204': + description: No Content + content: {} + '400': + description: Bad Request + content: {} + /positions/{id}: + delete: + summary: Delete a Position + tags: + - Positions + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + '404': + description: Not Found + content: {} + /server: + get: + summary: Fetch Server information + tags: + - Server + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Server' + put: + summary: Update Server information + tags: + - Server + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Server' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Server' + x-codegen-request-body-name: body + /session: + get: + summary: Fetch Session information + tags: + - Session + parameters: + - name: token + in: query + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: Not Found + content: {} + post: + summary: Create a new Session + tags: + - Session + requestBody: + content: + application/x-www-form-urlencoded: + schema: + required: + - email + - password + properties: + email: + type: string + password: + type: string + format: password + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '401': + description: Unauthorized + content: {} + delete: + summary: Close the Session + tags: + - Session + responses: + '204': + description: No Content + content: {} + /session/openid/auth: + get: + summary: Fetch Session information + tags: + - Session + responses: + '303': + description: Redirect to OpenID Connect identity provider + content: {} + /session/openid/callback: + get: + summary: OpenID Callback + tags: + - Session + responses: + '303': + description: Successful authentication, redirect to homepage + content: {} + /users: + get: + summary: Fetch a list of Users + tags: + - Users + parameters: + - name: userId + in: query + description: Can only be used by admin or manager users + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + '400': + description: No Permission + content: {} + post: + summary: Create a User + tags: + - Users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/User' + x-codegen-request-body-name: body + /users/{id}: + put: + summary: Update a User + tags: + - Users + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/User' + x-codegen-request-body-name: body + delete: + summary: Delete a User + tags: + - Users + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /notifications: + get: + summary: Fetch a list of Notifications + description: >- + Without params, it returns a list of Notifications the user has access + to + tags: + - Notifications + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + - name: deviceId + in: query + description: >- + Standard users can use this only with _deviceId_s, they have access + to + schema: + type: integer + - name: groupId + in: query + description: >- + Standard users can use this only with _groupId_s, they have access + to + schema: + type: integer + - name: refresh + in: query + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Notification' + post: + summary: Create a Notification + tags: + - Notifications + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Notification' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Notification' + x-codegen-request-body-name: body + /notifications/{id}: + put: + summary: Update a Notification + tags: + - Notifications + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Notification' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Notification' + x-codegen-request-body-name: body + delete: + summary: Delete a Notification + tags: + - Notifications + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /notifications/types: + get: + summary: Fetch a list of available Notification types + tags: + - Notifications + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NotificationType' + /notifications/test: + post: + summary: Send test notification to current user via Email and SMS + tags: + - Notifications + responses: + '204': + description: Successful sending + content: {} + '400': + description: Could happen if sending has failed + content: {} + /geofences: + get: + summary: Fetch a list of Geofences + description: Without params, it returns a list of Geofences the user has access to + tags: + - Geofences + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + - name: deviceId + in: query + description: >- + Standard users can use this only with _deviceId_s, they have access + to + schema: + type: integer + - name: groupId + in: query + description: >- + Standard users can use this only with _groupId_s, they have access + to + schema: + type: integer + - name: refresh + in: query + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Geofence' + post: + summary: Create a Geofence + tags: + - Geofences + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Geofence' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Geofence' + x-codegen-request-body-name: body + /geofences/{id}: + put: + summary: Update a Geofence + tags: + - Geofences + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Geofence' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Geofence' + x-codegen-request-body-name: body + delete: + summary: Delete a Geofence + tags: + - Geofences + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /events/{id}: + get: + tags: + - Events + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Event' + /reports/route: + get: + summary: >- + Fetch a list of Positions within the time period for the Devices or + Groups + description: At least one _deviceId_ or one _groupId_ must be passed + tags: + - Reports + parameters: + - name: deviceId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: groupId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Position' + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: array + items: + $ref: '#/components/schemas/Position' + /reports/events: + get: + summary: Fetch a list of Events within the time period for the Devices or Groups + description: At least one _deviceId_ or one _groupId_ must be passed + tags: + - Reports + parameters: + - name: deviceId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: groupId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: type + in: query + description: '% can be used to return events of all types' + style: form + explode: false + schema: + type: array + items: + type: string + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Event' + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: array + items: + $ref: '#/components/schemas/Event' + /reports/summary: + get: + summary: >- + Fetch a list of ReportSummary within the time period for the Devices or + Groups + description: At least one _deviceId_ or one _groupId_ must be passed + tags: + - Reports + parameters: + - name: deviceId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: groupId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ReportSummary' + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: array + items: + $ref: '#/components/schemas/ReportSummary' + /reports/trips: + get: + summary: >- + Fetch a list of ReportTrips within the time period for the Devices or + Groups + description: At least one _deviceId_ or one _groupId_ must be passed + tags: + - Reports + parameters: + - name: deviceId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: groupId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ReportTrips' + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: array + items: + $ref: '#/components/schemas/ReportTrips' + /reports/stops: + get: + summary: >- + Fetch a list of ReportStops within the time period for the Devices or + Groups + description: At least one _deviceId_ or one _groupId_ must be passed + tags: + - Reports + parameters: + - name: deviceId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: groupId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ReportStops' + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: array + items: + $ref: '#/components/schemas/ReportStops' + /statistics: + get: + summary: Fetch server Statistics + tags: + - Statistics + parameters: + - name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + - name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Statistics' + /calendars: + get: + summary: Fetch a list of Calendars + description: Without params, it returns a list of Calendars the user has access to + tags: + - Calendars + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Calendar' + post: + summary: Create a Calendar + tags: + - Calendars + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Calendar' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Calendar' + x-codegen-request-body-name: body + /calendars/{id}: + put: + summary: Update a Calendar + tags: + - Calendars + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Calendar' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Calendar' + x-codegen-request-body-name: body + delete: + summary: Delete a Calendar + tags: + - Calendars + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /attributes/computed: + get: + summary: Fetch a list of Attributes + description: Without params, it returns a list of Attributes the user has access to + tags: + - Attributes + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + - name: deviceId + in: query + description: >- + Standard users can use this only with _deviceId_s, they have access + to + schema: + type: integer + - name: groupId + in: query + description: >- + Standard users can use this only with _groupId_s, they have access + to + schema: + type: integer + - name: refresh + in: query + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Attribute' + post: + summary: Create an Attribute + tags: + - Attributes + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Attribute' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Attribute' + x-codegen-request-body-name: body + /attributes/computed/{id}: + put: + summary: Update an Attribute + tags: + - Attributes + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Attribute' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Attribute' + x-codegen-request-body-name: body + delete: + summary: Delete an Attribute + tags: + - Attributes + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /drivers: + get: + summary: Fetch a list of Drivers + description: Without params, it returns a list of Drivers the user has access to + tags: + - Drivers + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + - name: deviceId + in: query + description: >- + Standard users can use this only with _deviceId_s, they have access + to + schema: + type: integer + - name: groupId + in: query + description: >- + Standard users can use this only with _groupId_s, they have access + to + schema: + type: integer + - name: refresh + in: query + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Driver' + post: + summary: Create a Driver + tags: + - Drivers + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Driver' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Driver' + x-codegen-request-body-name: body + /drivers/{id}: + put: + summary: Update a Driver + tags: + - Drivers + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Driver' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Driver' + x-codegen-request-body-name: body + delete: + summary: Delete a Driver + tags: + - Drivers + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} + /maintenance: + get: + summary: Fetch a list of Maintenance + description: Without params, it returns a list of Maintenance the user has access to + tags: + - Maintenance + parameters: + - name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + - name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + - name: deviceId + in: query + description: >- + Standard users can use this only with _deviceId_s, they have access + to + schema: + type: integer + - name: groupId + in: query + description: >- + Standard users can use this only with _groupId_s, they have access + to + schema: + type: integer + - name: refresh + in: query + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Maintenance' + post: + summary: Create a Maintenance + tags: + - Maintenance + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Maintenance' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Maintenance' + x-codegen-request-body-name: body + /maintenance/{id}: + put: + summary: Update a Maintenance + tags: + - Maintenance + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Maintenance' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Maintenance' + x-codegen-request-body-name: body + delete: + summary: Delete a Maintenance + tags: + - Maintenance + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '204': + description: No Content + content: {} +components: + schemas: + Position: + type: object + properties: + id: + type: integer + deviceId: + type: integer + protocol: + type: string + deviceTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + fixTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + serverTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + outdated: + type: boolean + valid: + type: boolean + latitude: + type: number + longitude: + type: number + altitude: + type: number + speed: + type: number + description: in knots + course: + type: number + address: + type: string + accuracy: + type: number + network: + type: object + properties: {} + geofenceIds: + type: array + items: + type: integer + attributes: + type: object + properties: {} + User: + type: object + properties: + id: + type: integer + name: + type: string + email: + type: string + phone: + type: string + nullable: true + readonly: + type: boolean + administrator: + type: boolean + map: + type: string + nullable: true + latitude: + type: number + longitude: + type: number + zoom: + type: integer + password: + type: string + coordinateFormat: + type: string + nullable: true + disabled: + type: boolean + expirationTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + nullable: true + deviceLimit: + type: integer + userLimit: + type: integer + deviceReadonly: + type: boolean + limitCommands: + type: boolean + fixedEmail: + type: boolean + poiLayer: + type: string + nullable: true + attributes: + type: object + properties: {} + Server: + type: object + properties: + id: + type: integer + registration: + type: boolean + readonly: + type: boolean + deviceReadonly: + type: boolean + limitCommands: + type: boolean + map: + type: string + bingKey: + type: string + mapUrl: + type: string + poiLayer: + type: string + latitude: + type: number + longitude: + type: number + zoom: + type: integer + version: + type: string + forceSettings: + type: boolean + coordinateFormat: + type: string + openIdEnabled: + type: boolean + openIdForce: + type: boolean + attributes: + type: object + properties: {} + Command: + type: object + properties: + id: + type: integer + deviceId: + type: integer + description: + type: string + type: + type: string + attributes: + type: object + properties: {} + Device: + type: object + properties: + id: + type: integer + name: + type: string + uniqueId: + type: string + status: + type: string + disabled: + type: boolean + lastUpdate: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + nullable: true + positionId: + type: integer + nullable: true + groupId: + type: integer + nullable: true + phone: + type: string + nullable: true + model: + type: string + nullable: true + contact: + type: string + nullable: true + category: + type: string + nullable: true + attributes: + type: object + properties: {} + Group: + type: object + properties: + id: + type: integer + name: + type: string + groupId: + type: integer + attributes: + type: object + properties: {} + Permission: + type: object + properties: + userId: + type: integer + description: User id, can be only first parameter + deviceId: + type: integer + description: >- + Device id, can be first parameter or second only in combination with + userId + groupId: + type: integer + description: >- + Group id, can be first parameter or second only in combination with + userId + geofenceId: + type: integer + description: Geofence id, can be second parameter only + notificationId: + type: integer + description: Notification id, can be second parameter only + calendarId: + type: integer + description: >- + Calendar id, can be second parameter only and only in combination + with userId + attributeId: + type: integer + description: Computed attribute id, can be second parameter only + driverId: + type: integer + description: Driver id, can be second parameter only + managedUserId: + type: integer + description: >- + User id, can be second parameter only and only in combination with + userId + commandId: + type: integer + description: Saved command id, can be second parameter only + description: >- + This is a permission map that contain two object indexes. It is used to + link/unlink objects. Order is important. Example: { deviceId:8, + geofenceId: 16 } + CommandType: + type: object + properties: + type: + type: string + Geofence: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + area: + type: string + calendarId: + type: integer + attributes: + type: object + properties: {} + Notification: + type: object + properties: + id: + type: integer + type: + type: string + always: + type: boolean + web: + type: boolean + mail: + type: boolean + sms: + type: boolean + calendarId: + type: integer + attributes: + type: object + properties: {} + NotificationType: + type: object + properties: + type: + type: string + Event: + type: object + properties: + id: + type: integer + type: + type: string + eventTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + deviceId: + type: integer + positionId: + type: integer + geofenceId: + type: integer + maintenanceId: + type: integer + attributes: + type: object + properties: {} + ReportSummary: + type: object + properties: + deviceId: + type: integer + deviceName: + type: string + maxSpeed: + type: number + description: in knots + averageSpeed: + type: number + description: in knots + distance: + type: number + description: in meters + spentFuel: + type: number + description: in liters + engineHours: + type: integer + ReportTrips: + type: object + properties: + deviceId: + type: integer + deviceName: + type: string + maxSpeed: + type: number + description: in knots + averageSpeed: + type: number + description: in knots + distance: + type: number + description: in meters + spentFuel: + type: number + description: in liters + duration: + type: integer + startTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + startAddress: + type: string + startLat: + type: number + startLon: + type: number + endTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + endAddress: + type: string + endLat: + type: number + endLon: + type: number + driverUniqueId: + type: integer + driverName: + type: string + ReportStops: + type: object + properties: + deviceId: + type: integer + deviceName: + type: string + duration: + type: integer + startTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + address: + type: string + lat: + type: number + lon: + type: number + endTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + spentFuel: + type: number + description: in liters + engineHours: + type: integer + Statistics: + type: object + properties: + captureTime: + type: string + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + format: date-time + activeUsers: + type: integer + activeDevices: + type: integer + requests: + type: integer + messagesReceived: + type: integer + messagesStored: + type: integer + DeviceAccumulators: + type: object + properties: + deviceId: + type: integer + totalDistance: + type: number + description: in meters + hours: + type: number + Calendar: + type: object + properties: + id: + type: integer + name: + type: string + data: + type: string + description: base64 encoded in iCalendar format + attributes: + type: object + properties: {} + Attribute: + type: object + properties: + id: + type: integer + description: + type: string + attribute: + type: string + expression: + type: string + type: + type: string + description: String|Number|Boolean + Driver: + type: object + properties: + id: + type: integer + name: + type: string + uniqueId: + type: string + attributes: + type: object + properties: {} + Maintenance: + type: object + properties: + id: + type: integer + name: + type: string + type: + type: string + start: + type: number + period: + type: number + attributes: + type: object + properties: {} + parameters: + entityId: + name: id + in: path + required: true + schema: + type: integer + all: + name: all + in: query + description: Can only be used by admins or managers to fetch all entities + schema: + type: boolean + refresh: + name: refresh + in: query + schema: + type: boolean + userId: + name: userId + in: query + description: Standard users can use this only with their own _userId_ + schema: + type: integer + deviceId: + name: deviceId + in: query + description: Standard users can use this only with _deviceId_s, they have access to + schema: + type: integer + groupId: + name: groupId + in: query + description: Standard users can use this only with _groupId_s, they have access to + schema: + type: integer + deviceIdArray: + name: deviceId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + groupIdArray: + name: groupId + in: query + style: form + explode: true + schema: + type: array + items: + type: integer + fromTime: + name: from + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + toTime: + name: to + in: query + description: in IS0 8601 format. eg. `1963-11-22T18:30:00Z` + required: true + schema: + type: string + format: date-time + requestBodies: + Device: + content: + application/json: + schema: + $ref: '#/components/schemas/Device' + required: true + Permission: + content: + application/json: + schema: + $ref: '#/components/schemas/Permission' + required: true + Group: + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + required: true + User: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + required: true + Geofence: + content: + application/json: + schema: + $ref: '#/components/schemas/Geofence' + required: true + Calendar: + content: + application/json: + schema: + $ref: '#/components/schemas/Calendar' + required: true + Attribute: + content: + application/json: + schema: + $ref: '#/components/schemas/Attribute' + required: true + Driver: + content: + application/json: + schema: + $ref: '#/components/schemas/Driver' + required: true + Command: + content: + application/json: + schema: + $ref: '#/components/schemas/Command' + required: true + Notification: + content: + application/json: + schema: + $ref: '#/components/schemas/Notification' + required: true + Maintenance: + content: + application/json: + schema: + $ref: '#/components/schemas/Maintenance' + required: true + securitySchemes: + BasicAuth: + type: http + scheme: basic + ApiKey: + type: http + scheme: bearer diff --git a/reqirements.txt b/reqirements.txt new file mode 100644 index 0000000..8268981 --- /dev/null +++ b/reqirements.txt @@ -0,0 +1,2 @@ +kivy +cryptography \ No newline at end of file diff --git a/resources/credentials.enc b/resources/credentials.enc new file mode 100644 index 0000000..c7d9fe0 --- /dev/null +++ b/resources/credentials.enc @@ -0,0 +1 @@ +gAAAAABoPZqrY8gNIQXxQ_TAwdtbkZWX-zhwrx7hLuBFXH0M47fThQkAAG0vi8pMJhRnFipZ7t2Nj7SoVhjQqwHo4WOs7IH7lJCKSReeQT-1MdB0fYG16foBJpxK29kz-F9YRjCELQ0m \ No newline at end of file diff --git a/resources/key.key b/resources/key.key new file mode 100644 index 0000000..ede8923 --- /dev/null +++ b/resources/key.key @@ -0,0 +1 @@ +wetp_PNG9CC5432-W9H3rUbaqIurwldZxHlOgori5kY= \ No newline at end of file diff --git a/resources/server_settings.enc b/resources/server_settings.enc new file mode 100644 index 0000000..7eabde5 --- /dev/null +++ b/resources/server_settings.enc @@ -0,0 +1 @@ +gAAAAABoPZo4DMcBt7anBtvn5wMfvWpsjR4Zi1BPdQhvnvSj0SyQmrQEF1uhsUjf0Y6Le3vBPKSplUypYURK_BUlJMbxUh2yI93KCcXdMJIy23TyNlVlt7dv1mPN3aQAAd4TqhA_dEVPAfBtT5ckcQAXqCRwd1RjRp06Qgz3XNHeLs14MxGet0rbWCLppopr6BTGulvbAdvCFoDqmRFYbfyWYlqqsN4-ZF0Q0qObOVPn7hqYHFTenK6tidi-JOx7Lx0jrCZWA2V0OgwCZOsTA-BYZeWK8RI2U645SJ_DBTBwK2avb6wDduJKOOIKJWKdUF5HaN3Ffc61KWxYO_8Eb21bYmNsDB31l9n-OgJLdzStT-WqLMmmw027YG_SZJsv-P2Ce0lCaw29s2Lbh_2VxWShJW6aDRxibZgHK3CoPajqR0MAkGJxT2w7pt7wT1zeMKHu51q3yPpV \ No newline at end of file diff --git a/resources/track.png b/resources/track.png new file mode 100644 index 0000000..eb097c1 Binary files /dev/null and b/resources/track.png differ diff --git a/test_traccar_trip.py b/test_traccar_trip.py new file mode 100644 index 0000000..e79785b --- /dev/null +++ b/test_traccar_trip.py @@ -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.") \ No newline at end of file diff --git a/traccar.kv b/traccar.kv new file mode 100644 index 0000000..cbe4143 --- /dev/null +++ b/traccar.kv @@ -0,0 +1,347 @@ +: + 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 + +: + 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) + +: + 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) + +: + 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" \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..53cf76a --- /dev/null +++ b/utils.py @@ -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)