├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── api ├── __init__.py ├── apps.py ├── migrations │ └── __init__.py ├── permissions.py ├── serializers │ ├── __init__.py │ └── plates.py ├── tests.py ├── urls.py └── views │ ├── __init__.py │ └── plates.py ├── docs ├── diagram-2.png ├── diagram.png └── results.gif ├── manage.py ├── massif ├── __init__.py ├── camera.py ├── consume.py ├── controller.py ├── engine.py ├── ip_engine.py ├── tests │ ├── gps.py │ ├── motor.py │ ├── report.py │ ├── sound.py │ ├── sound_motor.py │ └── ui.py └── utils │ └── StepperLib.py ├── plate ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_alter_platemodel_license_plate.py │ ├── 0003_platemodel_status.py │ ├── 0004_rename_created_date_platemodel_created_at_and_more.py │ ├── 0005_alter_platemodel_user.py │ ├── 0006_platecapturedmodel.py │ ├── 0007_alter_platecapturedmodel_image_license.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── static ├── css │ ├── navbar.css │ ├── plate_pictures.css │ └── styles.css └── img │ ├── home.jpeg │ └── logo.jpg ├── templates ├── admin │ ├── base.html │ └── base_site.html └── plate │ ├── navbar.html │ ├── plate_base.html │ ├── plate_home.html │ └── plate_pictures.html ├── user ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_profile_picture.py │ ├── 0003_remove_profile_created_date_and_more.py │ └── __init__.py ├── models.py ├── tests.py └── views.py └── warehouse ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | __pycache__/ 3 | output_photo/ 4 | media/ 5 | db.sqlite3 6 | license_plates.db 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.autopep8" 4 | }, 5 | "python.formatting.provider": "none" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Leunel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ANPR with Arduino 2 | 3 | Automatic license plate recognition with Arduino. 4 | 5 | ## Architecture 6 | 7 | This project is made up of four environments: 8 | 9 | ![](https://raw.githubusercontent.com/addleonel/ANPR-Arduino/main/docs/diagram.png) 10 | 11 | 1. Database and back-end 12 | 2. API 13 | 3. Desktop application (a script executor) 14 | 4. Arduino and camera 15 | 16 | ## Requirements 17 | 18 | ### Database, back-end, and API 19 | 20 | I recommend using a virtual environment. You can use `venv` or `conda`. Then you need to install the dependencies: 21 | 22 | ```bash 23 | pip install -r requirements.txt 24 | ``` 25 | 26 | I'm using Django to run the server, so you need to run the migrations, makemigrations, and createasuperuser. You can do it with the following commands: 27 | 28 | ```bash 29 | python manage.py migrate 30 | python manage.py makemigrations 31 | python manage.py createsuperuser 32 | ``` 33 | 34 | ### Desktop application 35 | 36 | Before running the desktop application you need to install tesseract. You can follow the instructions in [Tesseract installation](https://github.com/UB-Mannheim/tesseract/wiki). 37 | 38 | Open the `engine.py` file (which is in the `massif` folder) and change `pytesseract.pytesseract.tesseract_cmd` according to your tesseract installation path. For example: 39 | 40 | ```python 41 | pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' 42 | ``` 43 | 44 | ### Arduino and camera 45 | 46 | Run the Arduino IDE and open the FirmataStandard example (`File > Examples > Firmata > FirmataStandard`). Then upload it to the Arduino, Make sure to connect the Arduino first. 47 | 48 | Make sure to connect the camera to the computer. 49 | 50 | ## How all components are connected 51 | 52 | As we notice the database, server, API, and desktop application is almost ready to run, but before doing that we need to connect LEDs and sound sensor to the Arduino. The following diagram shows how all components are connected: 53 | 54 | ![](https://raw.githubusercontent.com/addleonel/ANPR-Arduino/main/docs/diagram-2.png) 55 | 56 | the LEDs are connected to pins 10, 11, 12, and 13. The sound sensor is connected to the pin A0. You can change the pins in the `controller.py` file: 57 | 58 | ```python 59 | led_1 = board.get_pin('d:12:o') 60 | led_2 = board.get_pin('d:11:o') 61 | led_3 = board.get_pin('d:10:o') 62 | sound_pin = board.get_pin('a:0:i') # Analog pin 0, input mode 63 | led_4 = board.get_pin('d:13:o') 64 | ``` 65 | 66 | There are four colors of LEDs, each color is associated with a specific action: 67 | 68 | | Variable | Color | Action | 69 | | -------- | ------ | --------------------------------------------------------------------------------------------------------------------- | 70 | | led_1 | Red | Indicates that the license plate is in the database and that it is on the wanted or banned list. | 71 | | led_2 | White | It indicates that the license plate is in the database and that there are no problems due to any improper commission. | 72 | | led_3 | Yellow | Indicates that it identified a license plate. Regardless of whether or not it is part of the database. | 73 | | led_4 | Blue | Turn on when the sound sensor detects a sound. | 74 | 75 | An extra feature is that camera rotates 180 degrees. to do that we need another Arduino, a Stepper motor, and a stepper motor driver. Finally, upload the following code to the new Arduino: 76 | 77 | ```cpp 78 | #include 79 | 80 | int stepsPerTurn=2048; 81 | Stepper motor(stepsPerTurn, 8, 10, 9, 11); 82 | int stepsPerTurnLoop = 512; 83 | void setup() { 84 | Serial.begin(9600); 85 | motor.setSpeed(10); 86 | } 87 | 88 | void loop() { 89 | motor.step(stepsPerTurnLoop); 90 | delay(2000); 91 | motor.step(-stepsPerTurnLoop); 92 | delay(2000); 93 | } 94 | ``` 95 | 96 | ## How to run it 97 | 98 | Then you can run the server: 99 | 100 | ```bash 101 | python manage.py runserver 102 | ``` 103 | 104 | when the server is running make sure to connect the Arduino and the camera. You can run the desktop application which is located in the `massif` folder in another terminal: 105 | 106 | ```bash 107 | python engine.py 108 | ``` 109 | 110 | ## Results 111 | 112 | The following video shows the results. An entertaining video about the project 113 | 114 | [![An entertaining video about the project](https://img.youtube.com/vi/7bi59xWkrVs/0.jpg)](https://www.youtube.com/watch?v=7bi59xWkrVs) gif 115 | 116 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/api/__init__.py -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/api/migrations/__init__.py -------------------------------------------------------------------------------- /api/permissions.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/api/permissions.py -------------------------------------------------------------------------------- /api/serializers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/api/serializers/__init__.py -------------------------------------------------------------------------------- /api/serializers/plates.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from plate.models import PlateModel, PlateCapturedModel 3 | 4 | 5 | class PlateSerializer(serializers.ModelSerializer): 6 | """ 7 | Serializer for PlateModel 8 | """ 9 | class Meta: 10 | model = PlateModel 11 | fields = [ 12 | 'id', 13 | 'user', 14 | 'license_plate', 15 | 'brand', 16 | 'color', 17 | 'status', 18 | 'description', 19 | 'image', 20 | 'created_at', 21 | 'updated_at', 22 | ] 23 | 24 | 25 | class PlateCapturedSerializer(serializers.ModelSerializer): 26 | """ 27 | Serializer for PlateCapturedModel 28 | """ 29 | class Meta: 30 | model = PlateCapturedModel 31 | fields = [ 32 | 'id', 33 | 'plate', 34 | 'image_license', 35 | 'image_car', 36 | 'created_at', 37 | 'updated_at', 38 | ] 39 | -------------------------------------------------------------------------------- /api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.urlpatterns import format_suffix_patterns 3 | from api.views.plates import (PlateListView, PlateDetailView, PlateByLicenseDetailAPIView, 4 | PlateCapturedListView, PlateCapturedDetailView 5 | ) 6 | urlpatterns = format_suffix_patterns([ 7 | path('plate/', PlateListView.as_view()), 8 | path('plate//', PlateDetailView.as_view()), 9 | path('plate//', PlateByLicenseDetailAPIView.as_view()), 10 | path('plate_captured/', PlateCapturedListView.as_view()), 11 | path('plate_captured//', PlateCapturedDetailView.as_view()), 12 | ]) 13 | -------------------------------------------------------------------------------- /api/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/api/views/__init__.py -------------------------------------------------------------------------------- /api/views/plates.py: -------------------------------------------------------------------------------- 1 | from django.http import Http404 2 | from rest_framework import generics 3 | from rest_framework import permissions 4 | from rest_framework.views import APIView 5 | from rest_framework.response import Response 6 | from rest_framework import status 7 | from plate.models import PlateModel, PlateCapturedModel 8 | from api.serializers.plates import PlateSerializer, PlateCapturedSerializer 9 | 10 | 11 | class PlateListView(generics.ListCreateAPIView): 12 | """ 13 | List all plates, or create a new plate. 14 | """ 15 | queryset = PlateModel.objects.all() 16 | serializer_class = PlateSerializer 17 | permission_classes = [permissions.IsAuthenticatedOrReadOnly] 18 | 19 | 20 | class PlateDetailView(generics.RetrieveUpdateDestroyAPIView): 21 | """ 22 | Retrieve, update or delete a plate instance. 23 | """ 24 | queryset = PlateModel.objects.all() 25 | serializer_class = PlateSerializer 26 | permission_classes = [permissions.IsAuthenticatedOrReadOnly] 27 | 28 | 29 | class PlateByLicenseDetailAPIView(APIView): 30 | """ 31 | Retrieve and update linkcard 32 | """ 33 | queryset = PlateModel.objects.all() 34 | serializer_class = PlateSerializer 35 | permission_classes = [permissions.IsAuthenticatedOrReadOnly] 36 | 37 | def get_plate_object(self, license_plate): 38 | try: 39 | return PlateModel.objects.get(license_plate=license_plate) 40 | except PlateModel.DoesNotExist: 41 | raise Http404 42 | 43 | def get(self, request, license_plate, format=None): 44 | plate = self.get_plate_object(license_plate=license_plate) 45 | serializer = self.serializer_class( 46 | instance=plate, context={'request': request}) 47 | return Response(serializer.data) 48 | 49 | def put(self, request, license_plate, format=None): 50 | plate = self.get_plate_object(license_plate=license_plate) 51 | serializer = self.serializer_class( 52 | plate, request.data, context={'request': request}) 53 | if serializer.is_valid(): 54 | serializer.save() 55 | return Response(serializer.data) 56 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 57 | 58 | def patch(self, request, license_plate, format=None): 59 | plate = self.get_plate_object(license_plate=license_plate) 60 | serializer = self.serializer_class( 61 | plate, request.data, partial=True, context={'request': request}) 62 | if serializer.is_valid(): 63 | serializer.save() 64 | return Response(serializer.data) 65 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 66 | 67 | 68 | class PlateCapturedListView(APIView): 69 | """ 70 | List all captured plates, or create a new plate. 71 | """ 72 | serializer_class = PlateCapturedSerializer 73 | permission_classes = [permissions.AllowAny] 74 | 75 | def get(self, request, slug=None, format=None): 76 | plates = PlateCapturedModel.objects.all() 77 | serializer = self.serializer_class( 78 | plates, many=True, context={'request': request}) 79 | return Response(serializer.data) 80 | 81 | def post(self, request, slug=None, format=None): 82 | serializer = self.serializer_class( 83 | data=request.data, context={'request': request}) 84 | if serializer.is_valid(): 85 | serializer.save() 86 | return Response(serializer.data, status=status.HTTP_201_CREATED) 87 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 88 | 89 | 90 | class PlateCapturedDetailView(generics.RetrieveUpdateDestroyAPIView): 91 | """ 92 | Retrieve, update or delete a captured plate instance. 93 | """ 94 | queryset = PlateCapturedModel.objects.all() 95 | serializer_class = PlateCapturedSerializer 96 | permission_classes = [permissions.AllowAny] 97 | 98 | def get_plate_object(self, pk): 99 | try: 100 | return PlateCapturedModel.objects.get(pk=pk) 101 | except PlateCapturedModel.DoesNotExist: 102 | raise Http404 103 | 104 | def get(self, request, pk, format=None): 105 | plate = self.get_plate_object(pk=pk) 106 | serializer = self.serializer_class( 107 | instance=plate, context={'request': request}) 108 | return Response(serializer.data) 109 | 110 | def put(self, request, pk, format=None): 111 | plate = self.get_plate_object(pk=pk) 112 | serializer = self.serializer_class( 113 | plate, request.data, context={'request': request}) 114 | if serializer.is_valid(): 115 | serializer.save() 116 | return Response(serializer.data) 117 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 118 | 119 | def patch(self, request, pk, format=None): 120 | plate = self.get_plate_object(pk=pk) 121 | serializer = self.serializer_class( 122 | plate, request.data, partial=True, context={'request': request}) 123 | if serializer.is_valid(): 124 | serializer.save() 125 | return Response(serializer.data) 126 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 127 | -------------------------------------------------------------------------------- /docs/diagram-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/docs/diagram-2.png -------------------------------------------------------------------------------- /docs/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/docs/diagram.png -------------------------------------------------------------------------------- /docs/results.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/docs/results.gif -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'warehouse.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /massif/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/massif/__init__.py -------------------------------------------------------------------------------- /massif/camera.py: -------------------------------------------------------------------------------- 1 | cameras = [ 2 | { 3 | "ip": 0, 4 | "name": "camera1", 5 | }, 6 | { 7 | "ip": 1, 8 | "name": "camera2", 9 | }, 10 | { 11 | "ip": 3, 12 | "name": "camera3", 13 | }, 14 | { 15 | "ip": 4, 16 | "name": "camera4", 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /massif/consume.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | base_url = 'http://127.0.0.1:8000/api/' 4 | 5 | 6 | def get_licenses_plates(): 7 | """ 8 | get all licenses plates 9 | """ 10 | response = requests.get(base_url + 'plate/') 11 | if response.status_code == 200: 12 | data = response.json() 13 | return data 14 | else: 15 | return 'Error:', response.status_code 16 | 17 | 18 | def get_license_plate(license_plate): 19 | """ 20 | get a license plate 21 | """ 22 | response = requests.get(base_url + 'plate/' + license_plate) 23 | if response.status_code == 200: 24 | data = response.json() 25 | return data 26 | else: 27 | return 0 28 | 29 | 30 | def make_match(license_plate): 31 | """ 32 | make match, if the license plate is in the database 33 | """ 34 | if get_license_plate(license_plate): 35 | return True 36 | return False 37 | 38 | 39 | def is_banned(license_plate): 40 | """ 41 | check if the license plate is banned 42 | """ 43 | if make_match(license_plate): 44 | data = get_license_plate(license_plate) 45 | if data['status'] == 'BANNED': 46 | return True 47 | return False 48 | return 'License plate not found' 49 | 50 | 51 | def post_captured_plates(files, data): 52 | """ 53 | post captured plates. Send a POST request to the API endpoint with the file data and form data 54 | """ 55 | 56 | upload_url = base_url + 'plate_captured/' 57 | response = requests.post(upload_url, files=files, data=data) 58 | 59 | if response.status_code == 201: 60 | print('Image uploaded successfully!') 61 | print('Image URL:', response.json()['image_license']) 62 | else: 63 | print('Image upload failed:', response.text) 64 | 65 | 66 | if __name__ == '__main__': 67 | input_license_plate = input('Enter a license plate: ') 68 | print(is_banned(input_license_plate)) 69 | -------------------------------------------------------------------------------- /massif/controller.py: -------------------------------------------------------------------------------- 1 | from pyfirmata import Arduino 2 | import pyfirmata 3 | 4 | comport = 'COM5' 5 | 6 | board = Arduino(comport) 7 | 8 | led_1 = board.get_pin('d:12:o') 9 | led_2 = board.get_pin('d:11:o') 10 | led_3 = board.get_pin('d:10:o') 11 | 12 | sound_pin = board.get_pin('a:0:i') # Analog pin 0, input mode 13 | led_4 = board.get_pin('d:13:o') 14 | 15 | it = pyfirmata.util.Iterator(board) 16 | it.start() 17 | 18 | 19 | def turn_on_led(status): 20 | if status == 'BANNED': 21 | # into db and banned, red 22 | led_1.write(1) 23 | led_2.write(0) 24 | led_3.write(0) 25 | 26 | if status == 'ADMITED': 27 | # into db and admited, white 28 | led_1.write(0) 29 | led_2.write(1) 30 | led_3.write(0) 31 | 32 | if status == 'DETECTED': 33 | # not in db but is a license plate, yellow 34 | led_1.write(0) 35 | led_2.write(0) 36 | led_3.write(1) 37 | 38 | if status == 'NOT DETECTED': 39 | # not in db and is not a license plate 40 | led_1.write(0) 41 | led_2.write(0) 42 | led_3.write(0) 43 | -------------------------------------------------------------------------------- /massif/engine.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import sqlite3 4 | import cv2 5 | import pytesseract 6 | import numpy as np 7 | from PIL import Image 8 | from datetime import datetime 9 | from consume import is_banned, make_match, post_captured_plates 10 | from controller import turn_on_led, sound_pin, led_4 11 | from camera import cameras 12 | 13 | 14 | def run_engine(): 15 | # Directory to save the captured photos 16 | output_dir = "../output_photo/plate" 17 | output_dir_car = "../output_photo/car" 18 | 19 | # Create the output directory if it doesn't exist 20 | if not os.path.exists(output_dir): 21 | os.makedirs(output_dir) 22 | 23 | if not os.path.exists(output_dir_car): 24 | os.makedirs(output_dir_car) 25 | 26 | # Connect to the SQLite database 27 | conn = sqlite3.connect('../license_plates.db') 28 | connexec = conn.cursor() 29 | 30 | # Create the table if it doesn't exist 31 | connexec.execute('''CREATE TABLE IF NOT EXISTS plates 32 | (id INTEGER PRIMARY KEY AUTOINCREMENT, plate_number TEXT)''') 33 | 34 | cap = cv2.VideoCapture(cameras[0]["ip"]) 35 | width = 1380 36 | height = 750 37 | 38 | cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) 39 | cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) 40 | 41 | cText = '' 42 | pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' 43 | while True: 44 | 45 | sound_value = sound_pin.read() 46 | if sound_value is not None: 47 | print("Sound sensor value:", sound_value) 48 | if sound_value > 0.64: 49 | led_4.write(1) 50 | time.sleep(3) 51 | else: 52 | led_4.write(0) 53 | ret, frame = cap.read() 54 | 55 | if ret == False: 56 | break 57 | cv2.rectangle(frame, (870, 750), (1070, 850), 58 | (58, 252, 61), cv2.FILLED) 59 | cv2.putText(frame, "HELLO", (900, 810), 60 | cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 255), 5) 61 | 62 | al, an, c = frame.shape 63 | 64 | x1 = int(an/6) 65 | x2 = int(x1 * 5) 66 | 67 | y1 = int(al/6) 68 | y2 = int(y1 * 5) 69 | 70 | cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 255, 255), 2) 71 | 72 | cut = frame[y1:y2, x1:x2] 73 | 74 | mB = np.matrix(cut[:, :, 0]) 75 | mG = np.matrix(cut[:, :, 1]) 76 | mR = np.matrix(cut[:, :, 2]) 77 | 78 | color = cv2.absdiff(mG, mB) # yellow 79 | 80 | _, umbral = cv2.threshold(color, 40, 255, cv2.THRESH_BINARY) 81 | 82 | contours, _ = cv2.findContours( 83 | umbral, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 84 | contours = sorted( 85 | contours, key=lambda x: cv2.contourArea(x), reverse=True) 86 | 87 | for contour in contours: 88 | area = cv2.contourArea(contour) 89 | if area > 100 and area < 6000: 90 | x, y, w, h = cv2.boundingRect(contour) 91 | 92 | xpi = x + x1 93 | ypi = y + y1 94 | 95 | xpf = x + w + x1 96 | ypf = y + h + y1 97 | 98 | cv2.rectangle(frame, (xpi, ypi), (xpf, ypf), (236, 29, 50), 2) 99 | 100 | placa = frame[ypi:ypf, xpi:xpf] 101 | 102 | hp, wp, cp = placa.shape 103 | 104 | Mva = np.zeros((hp, wp)) 105 | 106 | mBp = np.matrix(placa[:, :, 0]) 107 | mGp = np.matrix(placa[:, :, 1]) 108 | mRp = np.matrix(placa[:, :, 2]) 109 | 110 | for col in range(0, hp): 111 | for fil in range(0, wp): 112 | Max = max(mRp[col, fil], mGp[col, fil], mBp[col, fil]) 113 | Mva[col, fil] = 255 - Max 114 | 115 | _, bin = cv2.threshold(Mva, 150, 255, cv2.THRESH_BINARY) 116 | 117 | bin = bin.reshape(hp, wp) 118 | bin = Image.fromarray(bin) 119 | bin = bin.convert("L") 120 | 121 | # if hp >= 100 and wp >= 200: 122 | # if hp >= 40 and wp >= 90: 123 | config = '--psm 1' 124 | text = pytesseract.image_to_string(bin, config=config) 125 | # count_texts = [] 126 | turn_on_led('NOT DETECTED') 127 | if len(text) >= 7: 128 | # Save the frame as an image 129 | cText = text 130 | turn_on_led('DETECTED') 131 | print("License plate detected:", cText) 132 | 133 | # license 134 | filename = datetime.now().strftime("%Y%m%d_%H%M%S_%f") 135 | output_path = os.path.join(output_dir, f"{filename}.jpg") 136 | cv2.imwrite(output_path, placa) 137 | 138 | # car 139 | filename_car = datetime.now().strftime("%Y%m%d_%H%M%S_%f") 140 | output_path_car = os.path.join( 141 | output_dir_car, f"{filename_car}.jpg") 142 | cv2.imwrite(output_path_car, frame) 143 | 144 | files = { 145 | 'image_license': open(output_path, 'rb'), 146 | 'image_car': open(output_path_car, 'rb') 147 | } 148 | 149 | data = { 150 | 'plate': cText, 151 | } 152 | 153 | post_captured_plates(files, data) 154 | 155 | # os.remove(output_path) 156 | # os.remove(output_path_car) 157 | 158 | if make_match(cText.strip()): # make match with the database 159 | cv2.putText(frame, "ENCONTRADO", (45, 90), 160 | cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5) 161 | if is_banned(cText.strip()): # check if the license plate is banned 162 | turn_on_led('BANNED') 163 | print("Placa Baneada") 164 | cv2.putText(frame, "BANEADO", (45, 150), 165 | cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5) 166 | else: 167 | turn_on_led('ADMITED') 168 | print("Placa Admitida") 169 | cv2.putText(frame, "ADMITIDO", (45, 150), 170 | cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5) 171 | 172 | connexec.execute( 173 | "INSERT INTO plates (plate_number) VALUES (?)", (cText,)) 174 | conn.commit() 175 | 176 | break 177 | cv2.imshow('FALCONI', frame) 178 | 179 | k = cv2.waitKey(1) 180 | 181 | if k == ord('q'): 182 | break 183 | 184 | cap.release() 185 | cv2.destroyAllWindows() 186 | 187 | 188 | if __name__ == "__main__": 189 | run_engine() 190 | -------------------------------------------------------------------------------- /massif/ip_engine.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import sqlite3 4 | import cv2 5 | import pytesseract 6 | import numpy as np 7 | from PIL import Image 8 | from datetime import datetime 9 | from consume import is_banned, make_match, post_captured_plates 10 | from controller import turn_on_led 11 | from camera import cameras 12 | 13 | 14 | def run_engine(): 15 | # Directory to save the captured photos 16 | output_dir = "../output_photo/plate" 17 | output_dir_car = "../output_photo/car" 18 | 19 | # Create the output directory if it doesn't exist 20 | if not os.path.exists(output_dir): 21 | os.makedirs(output_dir) 22 | 23 | if not os.path.exists(output_dir_car): 24 | os.makedirs(output_dir_car) 25 | 26 | # Connect to the SQLite database 27 | conn = sqlite3.connect('../license_plates.db') 28 | connexec = conn.cursor() 29 | 30 | # Create the table if it doesn't exist 31 | connexec.execute('''CREATE TABLE IF NOT EXISTS plates 32 | (id INTEGER PRIMARY KEY AUTOINCREMENT, plate_number TEXT)''') 33 | 34 | cap = cv2.VideoCapture(cameras[2]["ip"]) 35 | width = 1380 36 | height = 750 37 | 38 | cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) 39 | cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) 40 | 41 | cText = '' 42 | pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' 43 | while True: 44 | ret, frame = cap.read() 45 | 46 | if ret == False: 47 | break 48 | cv2.rectangle(frame, (870, 750), (1070, 850), 49 | (58, 252, 61), cv2.FILLED) 50 | cv2.putText(frame, "HELLO", (900, 810), 51 | cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 255), 5) 52 | # cv2.putText(frame, cText[0:7], (45, 90), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5) 53 | 54 | al, an, c = frame.shape 55 | 56 | x1 = int(an/6) 57 | x2 = int(x1 * 5) 58 | 59 | y1 = int(al/6) 60 | y2 = int(y1 * 5) 61 | 62 | cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 255, 255), 2) 63 | 64 | cut = frame[y1:y2, x1:x2] 65 | 66 | mB = np.matrix(cut[:, :, 0]) 67 | mG = np.matrix(cut[:, :, 1]) 68 | mR = np.matrix(cut[:, :, 2]) 69 | 70 | color = cv2.absdiff(mG, mB) # yellow 71 | # color = cv2.absdiff(mB, mR) 72 | 73 | _, umbral = cv2.threshold(color, 40, 255, cv2.THRESH_BINARY) 74 | 75 | contours, _ = cv2.findContours( 76 | umbral, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 77 | contours = sorted( 78 | contours, key=lambda x: cv2.contourArea(x), reverse=True) 79 | 80 | for contour in contours: 81 | area = cv2.contourArea(contour) 82 | if area > 100 and area < 6000: 83 | x, y, w, h = cv2.boundingRect(contour) 84 | 85 | xpi = x + x1 86 | ypi = y + y1 87 | 88 | xpf = x + w + x1 89 | ypf = y + h + y1 90 | 91 | cv2.rectangle(frame, (xpi, ypi), (xpf, ypf), (236, 29, 50), 2) 92 | 93 | placa = frame[ypi:ypf, xpi:xpf] 94 | 95 | hp, wp, cp = placa.shape 96 | 97 | Mva = np.zeros((hp, wp)) 98 | 99 | mBp = np.matrix(placa[:, :, 0]) 100 | mGp = np.matrix(placa[:, :, 1]) 101 | mRp = np.matrix(placa[:, :, 2]) 102 | 103 | for col in range(0, hp): 104 | for fil in range(0, wp): 105 | Max = max(mRp[col, fil], mGp[col, fil], mBp[col, fil]) 106 | Mva[col, fil] = 255 - Max 107 | 108 | _, bin = cv2.threshold(Mva, 150, 255, cv2.THRESH_BINARY) 109 | 110 | bin = bin.reshape(hp, wp) 111 | bin = Image.fromarray(bin) 112 | bin = bin.convert("L") 113 | 114 | # if hp >= 100 and wp >= 200: 115 | # if hp >= 40 and wp >= 90: 116 | config = '--psm 1' 117 | text = pytesseract.image_to_string(bin, config=config) 118 | # count_texts = [] 119 | turn_on_led('NOT DETECTED') 120 | if len(text) >= 7: 121 | # Save the frame as an image 122 | cText = text 123 | turn_on_led('DETECTED') 124 | print("License plate detected:", cText) 125 | 126 | # license 127 | filename = datetime.now().strftime("%Y%m%d_%H%M%S_%f") 128 | output_path = os.path.join(output_dir, f"{filename}.jpg") 129 | cv2.imwrite(output_path, placa) 130 | 131 | # car 132 | filename_car = datetime.now().strftime("%Y%m%d_%H%M%S_%f") 133 | output_path_car = os.path.join( 134 | output_dir_car, f"{filename_car}.jpg") 135 | cv2.imwrite(output_path_car, frame) 136 | 137 | files = { 138 | 'image_license': open(output_path, 'rb'), 139 | 'image_car': open(output_path_car, 'rb') 140 | } 141 | 142 | data = { 143 | 'plate': cText, 144 | } 145 | 146 | post_captured_plates(files, data) 147 | 148 | # os.remove(output_path) 149 | # os.remove(output_path_car) 150 | 151 | if make_match(cText.strip()): # make match with the database 152 | cv2.putText(frame, "ENCONTRADO", (45, 90), 153 | cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5) 154 | if is_banned(cText.strip()): # check if the license plate is banned 155 | turn_on_led('BANNED') 156 | print("Placa Baneada") 157 | cv2.putText(frame, "BANEADO", (45, 150), 158 | cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5) 159 | else: 160 | turn_on_led('ADMITED') 161 | print("Placa Admitida") 162 | cv2.putText(frame, "ADMITIDO", (45, 150), 163 | cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5) 164 | 165 | connexec.execute( 166 | "INSERT INTO plates (plate_number) VALUES (?)", (cText,)) 167 | conn.commit() 168 | 169 | break 170 | cv2.imshow('FALCONI', frame) 171 | 172 | k = cv2.waitKey(1) 173 | 174 | if k == ord('q'): 175 | break 176 | 177 | cap.release() 178 | cv2.destroyAllWindows() 179 | 180 | 181 | if __name__ == "__main__": 182 | run_engine() 183 | -------------------------------------------------------------------------------- /massif/tests/gps.py: -------------------------------------------------------------------------------- 1 | import os 2 | from GPSPhoto import gpsphoto 3 | 4 | filename = 'test2.jpg' 5 | path = os.getcwd() + f'\\{filename}' 6 | data = gpsphoto.getGPSData(path) 7 | print(path) 8 | print(data.get('Latitude'), data.get('Longitude')) 9 | -------------------------------------------------------------------------------- /massif/tests/motor.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | import pyfirmata 3 | from massif.utils import StepperLib 4 | 5 | # Set the port to match your Arduino board 6 | port = 'COM6' 7 | 8 | # Create a new board instance 9 | board = pyfirmata.Arduino(port) 10 | 11 | reader = pyfirmata.util.Iterator(board) # reads inputs of the circuit 12 | reader.start() 13 | 14 | # 9, 10, 11, 12 are digital pin numbers and 2038 is the number of steps in the stepper motor I used 15 | step_ = 2048 16 | motor = StepperLib.Stepper(step_, board, reader, 9, 10, 11, 12) 17 | motor.set_speed(100000) 18 | 19 | step_2 = 1024 20 | while True: 21 | print(step_2) 22 | motor.step(step_2) 23 | sleep(1) 24 | motor.step(-step_2) 25 | sleep(1) 26 | -------------------------------------------------------------------------------- /massif/tests/report.py: -------------------------------------------------------------------------------- 1 | import os 2 | from reportlab.lib.pagesizes import letter 3 | from reportlab.lib import colors 4 | from reportlab.platypus import SimpleDocTemplate, Paragraph, Table, TableStyle 5 | from reportlab.lib.styles import getSampleStyleSheet 6 | 7 | 8 | def create_pdf_report(pdf_file_path, report_data): 9 | # Create a PDF document 10 | doc = SimpleDocTemplate(pdf_file_path, pagesize=letter) 11 | 12 | # Create a list to store the report content 13 | elements = [] 14 | 15 | # Add a title to the report 16 | title_style = getSampleStyleSheet()["Title"] 17 | text_style = getSampleStyleSheet()["BodyText"] 18 | title_text = "Report Title" 19 | elements.append(Paragraph(title_text, title_style)) 20 | elements.append( 21 | Paragraph("clksndklnclknsldkc slkndcklnsd, sldknclksdnc\n", text_style)) 22 | elements.append(Paragraph("\n", text_style)) 23 | # Add a table with the report data 24 | data = [ 25 | ["Name", "Age", "Email"], 26 | ["John Doe", "30", "john@example.com"], 27 | ["Jane Smith", "25", "jane@example.com"], 28 | # Add more rows with data from the report_data dictionary 29 | ] 30 | 31 | table_style = TableStyle([ 32 | ('BACKGROUND', (0, 0), (-1, 0), colors.grey), 33 | ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), 34 | ('ALIGN', (0, 0), (-1, -1), 'CENTER'), 35 | ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), 36 | ('BOTTOMPADDING', (0, 0), (-1, 0), 12), 37 | ('BACKGROUND', (0, 1), (-1, -1), colors.beige), 38 | ('GRID', (0, 0), (-1, -1), 1, colors.black), 39 | ]) 40 | 41 | table = Table(data) 42 | table.setStyle(table_style) 43 | times = 3 44 | for i in range(times): 45 | elements.append(table) 46 | 47 | # Add other content to the report as needed 48 | 49 | # Build the PDF document 50 | doc.build(elements) 51 | 52 | 53 | if __name__ == "__main__": 54 | # Example usage 55 | report_data = {...} # Your report data goes here 56 | filename = 'report.pdf' 57 | pdf_file_path = os.getcwd() + f'\\{filename}' 58 | create_pdf_report(pdf_file_path, report_data) 59 | -------------------------------------------------------------------------------- /massif/tests/sound.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pyfirmata 3 | 4 | # Set the port to match your Arduino board 5 | port = 'COM5' 6 | 7 | # Create a new board instance 8 | board = pyfirmata.Arduino(port) 9 | 10 | # Define the pin connected to the sound sensor 11 | sound_pin = board.get_pin('a:0:i') # Analog pin 0, input mode 12 | led_1 = board.get_pin('d:13:o') 13 | 14 | # Start an iterator thread to read analog values from the board 15 | it = pyfirmata.util.Iterator(board) 16 | it.start() 17 | 18 | print('Reading sound sensor') 19 | # Read sound sensor data in a loop 20 | try: 21 | while True: 22 | # Read the sensor value 23 | sound_value = sound_pin.read() 24 | if sound_value is not None: 25 | print("Sound sensor value:", sound_value) 26 | if sound_value > 0.64: 27 | led_1.write(1) 28 | time.sleep(3) 29 | else: 30 | led_1.write(0) 31 | # Delay between readings 32 | # time.sleep(0.1) 33 | 34 | except KeyboardInterrupt: 35 | # Clean up and close the connection 36 | board.exit() 37 | -------------------------------------------------------------------------------- /massif/tests/sound_motor.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pyfirmata 3 | import threading 4 | from massif.utils import StepperLib 5 | 6 | # Set the port to match your Arduino board 7 | port = 'COM6' 8 | # Create a new board instance 9 | board = pyfirmata.Arduino(port) 10 | # Start an iterator thread to read analog values from the board 11 | reader = pyfirmata.util.Iterator(board) 12 | reader.start() 13 | 14 | # Sound Sensor 15 | 16 | 17 | def run_sound_sensor(): 18 | # Define the pin connected to the sound sensor 19 | sound_pin = board.get_pin('a:0:i') # Analog pin 0, input mode 20 | led_1 = board.get_pin('d:13:o') 21 | 22 | # Start an iterator thread to read analog values from the board 23 | it = pyfirmata.util.Iterator(board) 24 | it.start() 25 | 26 | # Read sound sensor data in a loop 27 | try: 28 | while True: 29 | # Read the sensor value 30 | sound_value = sound_pin.read() 31 | if sound_value is not None: 32 | print("Sound sensor value:", sound_value) 33 | if sound_value > 0.6: 34 | led_1.write(1) 35 | time.sleep(3) 36 | else: 37 | led_1.write(0) 38 | # Delay between readings 39 | # time.sleep(0.1) 40 | 41 | except KeyboardInterrupt: 42 | # Clean up and close the connection 43 | board.exit() 44 | 45 | 46 | def run_motor(): 47 | # 9, 10, 11, 12 are digital pin numbers and 2038 is the number of steps in the stepper motor I used 48 | step_ = 2048 49 | motor = StepperLib.Stepper(step_, board, reader, 9, 10, 11, 12) 50 | motor.set_speed(100000) 51 | 52 | step_2 = 1024 53 | while True: 54 | print(step_2) 55 | motor.step(step_2) 56 | time.sleep(1) 57 | motor.step(-step_2) 58 | time.sleep(1) 59 | 60 | 61 | def all_functions(): 62 | run_sound_sensor() 63 | run_motor() 64 | 65 | 66 | threading = threading.Thread(target=all_functions) 67 | threading.start() 68 | -------------------------------------------------------------------------------- /massif/tests/ui.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from massif.engine import run_engine 3 | def print_me(sender): 4 | print(f"Menu Item: {sender}") 5 | 6 | dpg.create_context() 7 | dpg.create_viewport(title='Custom Title', width=600, height=200) 8 | 9 | with dpg.viewport_menu_bar(): 10 | with dpg.menu(label="File"): 11 | dpg.add_menu_item(label="Save", callback=print_me) 12 | dpg.add_menu_item(label="Save As", callback=print_me) 13 | 14 | with dpg.menu(label="Settings"): 15 | dpg.add_menu_item(label="Setting 1", callback=print_me, check=True) 16 | dpg.add_menu_item(label="Setting 2", callback=print_me) 17 | 18 | dpg.add_menu_item(label="Cámara", callback=run_engine) 19 | 20 | with dpg.menu(label="Widget Items"): 21 | dpg.add_checkbox(label="Pick Me", callback=print_me) 22 | dpg.add_button(label="Press Me", callback=print_me) 23 | dpg.add_color_picker(label="Color Me", callback=print_me) 24 | 25 | dpg.setup_dearpygui() 26 | dpg.show_viewport() 27 | dpg.start_dearpygui() 28 | dpg.destroy_context() -------------------------------------------------------------------------------- /massif/utils/StepperLib.py: -------------------------------------------------------------------------------- 1 | 2 | from time import sleep 3 | 4 | 5 | class Stepper(): 6 | # for a 4 control wire stepper 7 | # total steps is the total number of steps per revolution, which is 2038 8 | def __init__(self, total_steps, board, reader, pin_1, pin_2, pin_3, pin_4): 9 | 10 | # sets up the thing so it will mesh with the arduino 11 | # initializes the pins as outputs 12 | self.pin_1 = board.get_pin('d:%s:o' % (pin_1)) 13 | self.pin_2 = board.get_pin('d:%s:o' % (pin_2)) 14 | self.pin_3 = board.get_pin('d:%s:o' % (pin_3)) 15 | self.pin_4 = board.get_pin('d:%s:o' % (pin_4)) 16 | # led for testing 17 | 18 | # led = board.get_pin('d:12:o') 19 | # led.write(1) 20 | 21 | self.step_number = 0 # what number of steps are going to be turned 22 | self.direction = 0 23 | self.total_steps = total_steps # total number of steps per revolution 24 | self.step_delay = 0 # time delay between steps 25 | 26 | def set_speed(self, what_speed): 27 | # sets the speed. number is arbitrary but the smaller the delay the faster it runs 28 | self.step_delay = (self.total_steps / (1000000 * what_speed)) 29 | # print(self.step_delay) 30 | 31 | def step(self, steps_to_move): 32 | # sets forward and backward 33 | if steps_to_move > 0: 34 | self.direction = 1 35 | if steps_to_move < 0: 36 | self.direction = 0 37 | 38 | # sets the number of steps that still need to be turned 39 | steps_left = abs(steps_to_move) 40 | 41 | # while this still needs to be turned, it starts with a delay and then depending on 42 | # the direction, steps in one direction and then also has a reset for when the 43 | # step number hits the max or min limits 44 | while steps_left > 0: 45 | sleep(self.step_delay) 46 | if self.direction == 1: 47 | self.step_number += 1 48 | if self.step_number == self.total_steps: 49 | self.step_number = 0 50 | else: 51 | if self.direction == 0: 52 | self.step_number -= 1 53 | if self.step_number == 0: 54 | self.step_number = self.total_steps 55 | 56 | # print(str(self.step_number)) 57 | 58 | steps_left -= 1 59 | # calls method to actually turn it 60 | self.step_motor(self.step_number % 8) 61 | 62 | def step_motor(self, this_step): 63 | # the binary numbers turn it. not sure why, pulled numbers from the github 64 | # use half steps so that it can turn backwards 65 | if this_step == 0: 66 | self.pin_1.write(1) 67 | self.pin_2.write(0) 68 | self.pin_3.write(0) 69 | self.pin_4.write(0) 70 | elif this_step == 1: 71 | self.pin_1.write(1) 72 | self.pin_2.write(1) 73 | self.pin_3.write(0) 74 | self.pin_4.write(0) 75 | elif this_step == 2: 76 | self.pin_1.write(0) 77 | self.pin_2.write(1) 78 | self.pin_3.write(0) 79 | self.pin_4.write(0) 80 | elif this_step == 3: 81 | self.pin_1.write(0) 82 | self.pin_2.write(1) 83 | self.pin_3.write(1) 84 | self.pin_4.write(0) 85 | elif this_step == 4: 86 | self.pin_1.write(0) 87 | self.pin_2.write(0) 88 | self.pin_3.write(1) 89 | self.pin_4.write(0) 90 | elif this_step == 5: 91 | self.pin_1.write(0) 92 | self.pin_2.write(0) 93 | self.pin_3.write(1) 94 | self.pin_4.write(1) 95 | elif this_step == 6: 96 | self.pin_1.write(0) 97 | self.pin_2.write(0) 98 | self.pin_3.write(0) 99 | self.pin_4.write(1) 100 | elif this_step == 7: 101 | self.pin_1.write(1) 102 | self.pin_2.write(0) 103 | self.pin_3.write(0) 104 | self.pin_4.write(1) 105 | -------------------------------------------------------------------------------- /plate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/plate/__init__.py -------------------------------------------------------------------------------- /plate/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from plate.models import PlateModel, PlateCapturedModel 3 | 4 | 5 | @admin.register(PlateModel) 6 | class PlateAdmin(admin.ModelAdmin): 7 | list_display = ( 8 | 'user', 9 | 'license_plate', 10 | 'brand', 11 | 'color', 12 | 'status', 13 | 'description', 14 | 'image', 15 | ) 16 | search_fields = ('user', 'license', 'brand', 'color', 'status',) 17 | list_filter = ('user',) 18 | 19 | 20 | @admin.register(PlateCapturedModel) 21 | class PlateCapturedAdmin(admin.ModelAdmin): 22 | list_display = ( 23 | 'plate', 24 | 'image_license', 25 | 'image_car', 26 | ) 27 | search_fields = ('plate',) 28 | list_filter = ('plate',) 29 | -------------------------------------------------------------------------------- /plate/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PlateConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'plate' 7 | -------------------------------------------------------------------------------- /plate/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-23 01:14 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='PlateModel', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('license_plate', models.CharField(blank=True, max_length=255)), 22 | ('brand', models.CharField(blank=True, max_length=255)), 23 | ('color', models.CharField(blank=True, max_length=255)), 24 | ('description', models.TextField(blank=True)), 25 | ('slug', models.SlugField(max_length=300, null=True, unique=True)), 26 | ('created_date', models.DateTimeField(auto_now_add=True)), 27 | ('published_date', models.DateTimeField(blank=True, null=True)), 28 | ('image', models.ImageField(blank=True, null=True, upload_to='plate/')), 29 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 30 | ], 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /plate/migrations/0002_alter_platemodel_license_plate.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-27 15:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('plate', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='platemodel', 15 | name='license_plate', 16 | field=models.CharField(max_length=255, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /plate/migrations/0003_platemodel_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-28 14:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('plate', '0002_alter_platemodel_license_plate'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='platemodel', 15 | name='status', 16 | field=models.CharField(blank=True, choices=[('ADMITED', 'admited'), ('BANNED', 'banned')], default='ADMITED', max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /plate/migrations/0004_rename_created_date_platemodel_created_at_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-07-11 03:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('plate', '0003_platemodel_status'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='platemodel', 15 | old_name='created_date', 16 | new_name='created_at', 17 | ), 18 | migrations.RemoveField( 19 | model_name='platemodel', 20 | name='published_date', 21 | ), 22 | migrations.AddField( 23 | model_name='platemodel', 24 | name='updated_at', 25 | field=models.DateTimeField(auto_now=True), 26 | ), 27 | migrations.AlterField( 28 | model_name='platemodel', 29 | name='image', 30 | field=models.ImageField(blank=True, max_length=300, null=True, upload_to='img/license_plates/'), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /plate/migrations/0005_alter_platemodel_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-07-11 04:21 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('plate', '0004_rename_created_date_platemodel_created_at_and_more'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='platemodel', 18 | name='user', 19 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='userplate', to=settings.AUTH_USER_MODEL), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /plate/migrations/0006_platecapturedmodel.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-07-11 04:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('plate', '0005_alter_platemodel_user'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='PlateCapturedModel', 15 | fields=[ 16 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('plate', models.CharField(blank=True, max_length=255, null=True)), 18 | ('image_license', models.ImageField(blank=True, max_length=300, null=True, upload_to='img/output_photos/')), 19 | ('image_car', models.ImageField(blank=True, max_length=300, null=True, upload_to='img/car_captured/')), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ('updated_at', models.DateTimeField(auto_now=True)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /plate/migrations/0007_alter_platecapturedmodel_image_license.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-07-11 04:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('plate', '0006_platecapturedmodel'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='platecapturedmodel', 15 | name='image_license', 16 | field=models.ImageField(blank=True, max_length=300, null=True, upload_to='img/plate_captured/'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /plate/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/plate/migrations/__init__.py -------------------------------------------------------------------------------- /plate/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.urls import reverse 4 | from django.template.defaultfilters import slugify 5 | 6 | 7 | class PlateModel(models.Model): 8 | """ 9 | Model for plates 10 | """ 11 | OPTIONS = [ 12 | ('ADMITED', 'admited'), 13 | ('BANNED', 'banned'), 14 | ] 15 | user = models.ForeignKey( 16 | User, on_delete=models.CASCADE, related_name='userplate') 17 | license_plate = models.CharField( 18 | max_length=255, blank=False, null=False, unique=True) 19 | brand = models.CharField(max_length=255, blank=True) 20 | color = models.CharField(max_length=255, blank=True) 21 | status = models.CharField( 22 | max_length=255, blank=True, default='ADMITED', choices=OPTIONS) 23 | description = models.TextField(blank=True) 24 | slug = models.SlugField(null=True, unique=True, max_length=300) 25 | image = models.ImageField( 26 | upload_to='img/license_plates/', blank=True, null=True, max_length=300) 27 | created_at = models.DateTimeField(auto_now_add=True) 28 | updated_at = models.DateTimeField(auto_now=True) 29 | 30 | def __str__(self): 31 | return self.license_plate 32 | 33 | def get_absolute_url(self): 34 | return reverse('plate:plate', kwargs={'slug': self.slug}) 35 | 36 | def save(self, *args, **kwargs): 37 | if not self.slug: 38 | self.slug = slugify(self.license_plate) 39 | return super().save(*args, **kwargs) 40 | 41 | 42 | class PlateCapturedModel(models.Model): 43 | """ 44 | Model for captured plates 45 | """ 46 | plate = models.CharField(max_length=255, blank=True, null=True) 47 | image_license = models.ImageField( 48 | upload_to='img/plate_captured/', blank=True, null=True, max_length=300) 49 | image_car = models.ImageField( 50 | upload_to='img/car_captured/', blank=True, null=True, max_length=300) 51 | created_at = models.DateTimeField(auto_now_add=True) 52 | updated_at = models.DateTimeField(auto_now=True) 53 | 54 | def __str__(self): 55 | return self.plate 56 | -------------------------------------------------------------------------------- /plate/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /plate/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from plate.views import home, plate_list 3 | 4 | app_name = 'plate' 5 | urlpatterns = [ 6 | path('', home, name='plate_home'), 7 | path('list/', plate_list, name='plate_list'), 8 | ] 9 | -------------------------------------------------------------------------------- /plate/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from plate.models import PlateCapturedModel 3 | 4 | 5 | def home(request): 6 | """ 7 | Home page 8 | """ 9 | return render(request, 'plate/plate_home.html') 10 | 11 | 12 | def plate_list(request): 13 | """ 14 | List of plates 15 | """ 16 | plates_captured = PlateCapturedModel.objects.all().order_by('-created_at') 17 | 18 | context = { 19 | 'plates_captured': plates_captured, 20 | } 21 | 22 | return render(request, 'plate/plate_pictures.html', context) 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | backports.zoneinfo==0.2.1 3 | certifi==2023.5.7 4 | charset-normalizer==3.1.0 5 | dearpygui==1.9.1 6 | Django==4.2.2 7 | django-phonenumber-field==7.1.0 8 | djangorestframework==3.14.0 9 | ExifRead==3.0.0 10 | gpsphoto==2.2.3 11 | idna==3.4 12 | numpy==1.23.2 13 | opencv-contrib-python==4.6.0.66 14 | packaging==21.3 15 | phonenumbers==8.13.14 16 | piexif==1.1.3 17 | Pillow==9.2.0 18 | pyFirmata==1.1.0 19 | pyparsing==3.0.9 20 | pyserial==3.5 21 | pytesseract==0.3.10 22 | pytz==2023.3 23 | reportlab==4.0.4 24 | requests==2.31.0 25 | sqlparse==0.4.4 26 | typing_extensions==4.6.3 27 | tzdata==2023.3 28 | urllib3==2.0.3 29 | -------------------------------------------------------------------------------- /static/css/navbar.css: -------------------------------------------------------------------------------- 1 | .navbar-container { 2 | background: linear-gradient( 3 | 90deg, 4 | rgb(4, 115, 170) 0%, 5 | rgba(65, 118, 144, 1) 35%, 6 | rgba(65, 118, 144, 1) 100% 7 | ); 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | z-index: 2; 12 | width: 100%; 13 | height: auto; 14 | } 15 | 16 | .navbar-logo { 17 | color: #f7f7f7; 18 | font-size: 20px; 19 | font-weight: bold; 20 | text-decoration: none !important; 21 | padding: 0 30px 0 0; 22 | } 23 | 24 | .navbar-logo:hover { 25 | color: #c7c7c7; 26 | font-weight: bold; 27 | } 28 | 29 | .navbar-links { 30 | color: #f7f7f7; 31 | font-weight: bold; 32 | text-decoration: none; 33 | } 34 | 35 | .navbar-links:hover { 36 | color: #c7c7c7; 37 | font-weight: bold; 38 | } 39 | -------------------------------------------------------------------------------- /static/css/plate_pictures.css: -------------------------------------------------------------------------------- 1 | .body-container { 2 | padding-top: 100px; 3 | } 4 | 5 | .info-tags { 6 | font-size: 12px; 7 | font-weight: bold; 8 | padding: 8px 10px; 9 | border-radius: 8px; 10 | background-color: #eeeeee; 11 | text-align: center; 12 | } 13 | -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | .home-container { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | } 12 | 13 | .look-license { 14 | background-color: #000; 15 | color: #fff; 16 | padding: 20px 30px; 17 | font-weight: bold; 18 | font-size: 20px; 19 | border-radius: 10px; 20 | } 21 | 22 | .image-home { 23 | width: 100%; 24 | } 25 | 26 | .home-content { 27 | position: absolute; 28 | top: 0; 29 | left: 0; 30 | padding: 300px; 31 | } 32 | -------------------------------------------------------------------------------- /static/img/home.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/static/img/home.jpeg -------------------------------------------------------------------------------- /static/img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/static/img/logo.jpg -------------------------------------------------------------------------------- /templates/admin/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n static %} 2 | {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} 3 | 4 | 5 | {% block title %}{% endblock %} 6 | 7 | {% block dark-mode-vars %} 8 | 9 | 10 | {% endblock %} 11 | {% if not is_popup and is_nav_sidebar_enabled %} 12 | 13 | 14 | {% endif %} 15 | {% block extrastyle %}{% endblock %} 16 | {% if LANGUAGE_BIDI %}{% endif %} 17 | {% block extrahead %}{% endblock %} 18 | {% block responsive %} 19 | 20 | 21 | {% if LANGUAGE_BIDI %}{% endif %} 22 | {% endblock %} 23 | {% block blockbots %}{% endblock %} 24 | 25 | 26 | 28 | {% translate 'Skip to main content' %} 29 | 30 |
31 | 32 | {% if not is_popup %} 33 | 34 | {% block header %} 35 | 70 | {% endblock %} 71 | 72 | {% block nav-breadcrumbs %} 73 | 81 | {% endblock %} 82 | {% endif %} 83 | 84 |
85 | {% if not is_popup and is_nav_sidebar_enabled %} 86 | {% block nav-sidebar %} 87 | {% include "admin/nav_sidebar.html" %} 88 | {% endblock %} 89 | {% endif %} 90 |
91 | {% block messages %} 92 | {% if messages %} 93 |
    {% for message in messages %} 94 | {{ message|capfirst }} 95 | {% endfor %}
96 | {% endif %} 97 | {% endblock messages %} 98 | 99 |
100 | {% block pretitle %}{% endblock %} 101 | {% block content_title %}{% if title %}

{{ title }}

{% endif %}{% endblock %} 102 | {% block content_subtitle %}{% if subtitle %}

{{ subtitle }}

{% endif %}{% endblock %} 103 | {% block content %} 104 | {% block object-tools %}{% endblock %} 105 | {{ content }} 106 | {% endblock %} 107 | {% block sidebar %}{% endblock %} 108 |
109 |
110 | 111 | {% block footer %}{% endblock %} 112 |
113 |
114 |
115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} {% block title %}FALCONI{% endblock %} {% block branding %} 2 | FALCONI 3 | {% if user.is_anonymous %} {% include "admin/color_theme_toggle.html" %} {% endif %} {% endblock %} {% block nav-global %}{% endblock %} 4 | -------------------------------------------------------------------------------- /templates/plate/navbar.html: -------------------------------------------------------------------------------- 1 | {% load static %} {% block links %} 2 | 3 | {% endblock links%} 4 | 42 | -------------------------------------------------------------------------------- /templates/plate/plate_base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | {% block title%}{%endblock title%} 7 | 13 | 14 | 19 | 20 | 25 | {% block links %} {% endblock links%} 26 | 27 | 28 | {% include 'plate/navbar.html'%} {% block content%}{% endblock content%} 29 | 34 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /templates/plate/plate_home.html: -------------------------------------------------------------------------------- 1 | {% extends "plate/plate_base.html" %} {% load static %} {% block title%}Home{%endblock title%} {% block content%} 2 |
3 | 4 |
5 | 6 |
7 |
8 | 9 | {% endblock content%} 10 | -------------------------------------------------------------------------------- /templates/plate/plate_pictures.html: -------------------------------------------------------------------------------- 1 | {% extends "plate/plate_base.html" %} {% load static %} {% block title%}Matrículas{%endblock title%} {% block links%} 2 | 3 | {% endblock links %} {% block content%} 4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for plate in plates_captured%} 19 | 20 | 21 | 22 | 34 | 44 | 47 | 48 | 49 | 86 | 123 | {% endfor %} 124 | 125 |
IDCaracteresMatrículaVista ampliaFecha
{{plate.id}}
{{plate.plate}}
23 | 33 | 35 | 43 | 45 |

{{plate.created_at}}

46 |
126 |
127 |
128 | {% endblock content%} 129 | -------------------------------------------------------------------------------- /user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/user/__init__.py -------------------------------------------------------------------------------- /user/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from user.models import Profile 3 | 4 | 5 | @admin.register(Profile) 6 | class ProfileAdmin(admin.ModelAdmin): 7 | list_display = ( 8 | 'user', 9 | 'name', 10 | 'surname', 11 | 'dni', 12 | 'address', 13 | 'email', 14 | 'phone', 15 | 'description_profile', 16 | 'city', 17 | 'country', 18 | 'picture', 19 | ) 20 | search_fields = ('user', 'name', 'surname', 'dni', 21 | 'address', 'email', 'phone', 'city', 'country') 22 | list_filter = ('user',) 23 | -------------------------------------------------------------------------------- /user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'user' 7 | -------------------------------------------------------------------------------- /user/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-06-23 01:14 2 | 3 | from django.conf import settings 4 | import django.core.validators 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import django.utils.timezone 8 | import phonenumber_field.modelfields 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Profile', 22 | fields=[ 23 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('name', models.CharField(blank=True, max_length=255)), 25 | ('surname', models.CharField(blank=True, max_length=255)), 26 | ('dni', models.CharField(blank=True, max_length=8, validators=[django.core.validators.RegexValidator(message='El DNI debe contener 8 dígitos', regex='^\\d{8}$')])), 27 | ('address', models.CharField(blank=True, max_length=255)), 28 | ('email', models.EmailField(blank=True, max_length=255)), 29 | ('phone', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None)), 30 | ('description_profile', models.TextField(blank=True)), 31 | ('city', models.CharField(blank=True, max_length=255)), 32 | ('country', models.CharField(blank=True, max_length=255)), 33 | ('slug', models.SlugField(max_length=300, null=True, unique=True)), 34 | ('created_date', models.DateTimeField(default=django.utils.timezone.now)), 35 | ('published_date', models.DateTimeField(blank=True, null=True)), 36 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 37 | ], 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /user/migrations/0002_profile_picture.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-07-11 02:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('user', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='profile', 15 | name='picture', 16 | field=models.ImageField(blank=True, max_length=300, upload_to='img/profile_pics'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /user/migrations/0003_remove_profile_created_date_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.2 on 2023-07-11 03:22 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('user', '0002_profile_picture'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='profile', 16 | name='created_date', 17 | ), 18 | migrations.RemoveField( 19 | model_name='profile', 20 | name='published_date', 21 | ), 22 | migrations.AddField( 23 | model_name='profile', 24 | name='created_at', 25 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 26 | preserve_default=False, 27 | ), 28 | migrations.AddField( 29 | model_name='profile', 30 | name='updated_at', 31 | field=models.DateTimeField(auto_now=True), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/user/migrations/__init__.py -------------------------------------------------------------------------------- /user/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.urls import reverse 4 | from django.template.defaultfilters import slugify 5 | from django.dispatch import receiver 6 | from django.db.models.signals import post_save 7 | from django.core.validators import RegexValidator 8 | from phonenumber_field.modelfields import PhoneNumberField 9 | 10 | 11 | class Profile(models.Model): 12 | """ 13 | Model for user profile 14 | """ 15 | user = models.OneToOneField(User, on_delete=models.CASCADE) 16 | name = models.CharField(max_length=255, blank=True) 17 | surname = models.CharField(max_length=255, blank=True) 18 | dni = models.CharField(max_length=8, blank=True, validators=[ 19 | RegexValidator( 20 | regex=r'^\d{8}$', 21 | message='El DNI debe contener 8 dígitos', 22 | ) 23 | ]) 24 | address = models.CharField(max_length=255, blank=True) 25 | email = models.EmailField(max_length=255, blank=True) 26 | phone = PhoneNumberField(blank=True) 27 | description_profile = models.TextField(blank=True) 28 | city = models.CharField(max_length=255, blank=True) 29 | country = models.CharField(max_length=255, blank=True) 30 | picture = models.ImageField( 31 | upload_to='img/profile_pics', blank=True, max_length=300) 32 | slug = models.SlugField(null=True, unique=True, max_length=300) 33 | created_at = models.DateTimeField(auto_now_add=True) 34 | updated_at = models.DateTimeField(auto_now=True) 35 | 36 | def __str__(self): 37 | return self.user.username 38 | 39 | def get_absolute_url(self): 40 | return reverse('user:profile', kwargs={'slug': self.slug}) 41 | 42 | def save(self, *args, **kwargs): 43 | if not self.slug: 44 | self.slug = slugify(self.user) 45 | return super().save(*args, **kwargs) 46 | 47 | 48 | @receiver(post_save, sender=User) 49 | def create_user_profile(sender, instance, created, **kwargs): 50 | if created: 51 | Profile.objects.create(user=instance) 52 | 53 | 54 | @receiver(post_save, sender=User) 55 | def save_user_profile(sender, instance, **kwargs): 56 | instance.profile.save() 57 | -------------------------------------------------------------------------------- /user/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /user/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /warehouse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addleonel/ANPR-Arduino/e0acdf5631081de12671a68ec9d32c4c7b063db8/warehouse/__init__.py -------------------------------------------------------------------------------- /warehouse/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for warehouse project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'warehouse.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /warehouse/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 5 | BASE_DIR = Path(__file__).resolve().parent.parent 6 | 7 | # SECURITY WARNING: keep the secret key used in production secret! 8 | SECRET_KEY = 'django-insecure-y33%ohd9etm3(c5fwl7feetda3bt&&9!rdj=kls=@)f4*4$8*9' 9 | 10 | # SECURITY WARNING: don't run with debug turned on in production! 11 | DEBUG = True 12 | 13 | ALLOWED_HOSTS = [] 14 | 15 | 16 | # Application definition 17 | 18 | DJANGO_APPS = [ 19 | 'django.contrib.admin', 20 | 'django.contrib.auth', 21 | 'django.contrib.contenttypes', 22 | 'django.contrib.sessions', 23 | 'django.contrib.messages', 24 | 'django.contrib.staticfiles', 25 | ] 26 | 27 | LOCAL_APPS = [ 28 | 'plate', 29 | 'user', 30 | 'api', 31 | ] 32 | 33 | THIRD_PARTY_APPS = [ 34 | 'phonenumber_field', 35 | 'rest_framework', 36 | ] 37 | 38 | INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS + THIRD_PARTY_APPS 39 | 40 | MIDDLEWARE = [ 41 | 'django.middleware.security.SecurityMiddleware', 42 | 'django.contrib.sessions.middleware.SessionMiddleware', 43 | 'django.middleware.common.CommonMiddleware', 44 | 'django.middleware.csrf.CsrfViewMiddleware', 45 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 46 | 'django.contrib.messages.middleware.MessageMiddleware', 47 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 48 | ] 49 | 50 | ROOT_URLCONF = 'warehouse.urls' 51 | 52 | TEMPLATES = [ 53 | { 54 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 55 | 'DIRS': [ 56 | os.path.join(BASE_DIR, 'templates'), 57 | ], 58 | 'APP_DIRS': True, 59 | 'OPTIONS': { 60 | 'context_processors': [ 61 | 'django.template.context_processors.debug', 62 | 'django.template.context_processors.request', 63 | 'django.contrib.auth.context_processors.auth', 64 | 'django.contrib.messages.context_processors.messages', 65 | ], 66 | }, 67 | }, 68 | ] 69 | 70 | WSGI_APPLICATION = 'warehouse.wsgi.application' 71 | 72 | 73 | # Database 74 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases 75 | 76 | DATABASES = { 77 | 'default': { 78 | 'ENGINE': 'django.db.backends.sqlite3', 79 | 'NAME': BASE_DIR / 'db.sqlite3', 80 | } 81 | } 82 | 83 | 84 | # Password validation 85 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 86 | 87 | AUTH_PASSWORD_VALIDATORS = [ 88 | { 89 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 90 | }, 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 99 | }, 100 | ] 101 | 102 | 103 | # Internationalization 104 | # https://docs.djangoproject.com/en/4.2/topics/i18n/ 105 | 106 | LANGUAGE_CODE = 'es-pe' 107 | TIME_ZONE = 'America/Lima' 108 | USE_I18N = True 109 | USE_TZ = True 110 | 111 | 112 | # Static files (CSS, JavaScript, Images) 113 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 114 | STATIC_URL = '/static/' 115 | STATICFILES_DIRS = [ 116 | os.path.join(BASE_DIR, 'static'), 117 | ] 118 | 119 | # media 120 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 121 | MEDIA_URL = '/media/' 122 | 123 | 124 | # Default primary key field type 125 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 126 | 127 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 128 | -------------------------------------------------------------------------------- /warehouse/urls.py: -------------------------------------------------------------------------------- 1 | 2 | from django.contrib import admin 3 | from django.urls import path, include 4 | from django.conf import settings 5 | from django.conf.urls.static import static 6 | 7 | urlpatterns = [ 8 | path('', include('plate.urls')), 9 | path('admin/', admin.site.urls), 10 | path('api/', include('api.urls')), 11 | ] 12 | 13 | if settings.DEBUG: 14 | urlpatterns += static(settings.MEDIA_URL, 15 | document_root=settings.MEDIA_ROOT) 16 | -------------------------------------------------------------------------------- /warehouse/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'warehouse.settings') 6 | 7 | application = get_wsgi_application() 8 | --------------------------------------------------------------------------------