Compare commits
48 Commits
modulo-led
...
master
Author | SHA1 | Date |
---|---|---|
![]() |
82203db513 | |
|
50a47d0f95 | |
|
a18c5c1784 | |
![]() |
54daec5249 | |
![]() |
371da77fb0 | |
![]() |
d046c06db9 | |
![]() |
53dcd69f16 | |
![]() |
713370b162 | |
![]() |
b164e07476 | |
![]() |
5309589a5c | |
![]() |
8d77225bee | |
![]() |
0d9f958762 | |
![]() |
c78b5e1251 | |
![]() |
b66bd8fe0c | |
![]() |
659738a0c2 | |
![]() |
bd3c37837a | |
![]() |
bf6d58d554 | |
![]() |
3721fe4e8f | |
![]() |
9f728916b4 | |
![]() |
de0c1ffb90 | |
|
b136bd23f4 | |
|
2ae2b33d08 | |
![]() |
af28113bf0 | |
![]() |
b04bbe8bf5 | |
![]() |
0493e8dc7e | |
![]() |
8384e4f96a | |
![]() |
b84f990abc | |
![]() |
f0a90c5bbf | |
![]() |
1f8e5ceee9 | |
![]() |
f7ba70c96f | |
![]() |
d904392df9 | |
![]() |
6ebcd057c6 | |
![]() |
f845b5bba9 | |
![]() |
350464e9b2 | |
![]() |
bc21d403af | |
![]() |
e044cf37e3 | |
![]() |
c26d426dd0 | |
![]() |
3ab15451ec | |
![]() |
81ed1aeb72 | |
![]() |
03f3189c53 | |
![]() |
af52a2c778 | |
![]() |
53adb29754 | |
![]() |
1a805479f5 | |
![]() |
c092a8647c | |
![]() |
6bbcb99272 | |
![]() |
13f3f98f18 | |
![]() |
989e838fd8 | |
![]() |
450f263793 |
|
@ -0,0 +1,43 @@
|
||||||
|
# Generación de Póster de Bus
|
||||||
|
En el directorio raiz del respositorio pasamos al carpeta:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd GenPoster
|
||||||
|
```
|
||||||
|
|
||||||
|
## Construir la Imagen Docker
|
||||||
|
Primero, construye la imagen Docker que contiene todas las dependencias necesarias:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t bus_poster .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ejecutar el Contenedor Docker
|
||||||
|
Utiliza el script ```run_container.sh``` para ejecutar el contenedor. Este script monta las carpetas locales necesarias y inicia el contenedor. El contenedor se eliminará automáticamente después de su ejecución debido al parámetro ```--rm```.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./run_container.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Nota: Asegúrate de que el script ```run_container.sh``` tenga permisos de ejecución. Si no es así, ejecuta:
|
||||||
|
```bash
|
||||||
|
chmod +x run_container.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generación y Almacenamiento del Póster
|
||||||
|
Al ejecutar el contenedor, el script ```app.py``` se iniciará automáticamente y realizará lo siguiente:
|
||||||
|
|
||||||
|
- Calcula el tiempo restante hasta la llegada del autobús.
|
||||||
|
- Genera visualizaciones con los detalles del autobús y el tiempo restante.
|
||||||
|
- Guarda la imagen generada en una carpeta local mapeada al contenedor.
|
||||||
|
|
||||||
|
Por el momento esta funcionando con datos de prueba, eventualmente se usará como datos de entrada las respuestas del endpoint.
|
||||||
|
|
||||||
|
## Acceso a la Imagen Generada
|
||||||
|
La imagen del póster se guardará en la carpeta local especificada en el script ```run_container.sh```. Puedes acceder a ella directamente desde esta carpeta en tu máquina local.
|
||||||
|
|
||||||
|
## Ejemplo de Imagen Generada
|
||||||
|
|
||||||
|
Actualmente se están generando imagenes para alimentar un arreglo de pantallas P4 de 2x1, por lo tanto se ajustaron las dimensiones para esa configuración.
|
||||||
|
|
||||||
|

|
|
@ -3,26 +3,35 @@ from scripts.Poster.BusPoster import BusPoster
|
||||||
from scripts.Poster.TimeAnnouncement import TimeAnnouncement
|
from scripts.Poster.TimeAnnouncement import TimeAnnouncement
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from import_data import export_data
|
||||||
|
from PIL import Image
|
||||||
|
import subprocess
|
||||||
|
from getData import Paradero
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
os.environ['TZ'] = 'America/Santiago'
|
||||||
|
time.tzset()
|
||||||
|
|
||||||
|
def reescalar_imagen(input_path, output_path, nuevo_ancho, nuevo_alto):
|
||||||
|
try:
|
||||||
|
# Abrir la imagen original
|
||||||
|
imagen = Image.open(input_path)
|
||||||
|
|
||||||
|
# Reescalar la imagen
|
||||||
|
imagen_redimensionada = imagen.resize((nuevo_ancho, nuevo_alto))
|
||||||
|
|
||||||
|
# Guardar la imagen redimensionada en el nuevo archivo
|
||||||
|
imagen_redimensionada.save(output_path)
|
||||||
|
# print("Imagen redimensionada y guardada con éxito en", output_path)
|
||||||
|
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
def aprox(n):
|
def aprox(n):
|
||||||
return int(np.round(n))
|
return int(np.round(n))
|
||||||
|
|
||||||
def load_data():
|
|
||||||
data = {
|
|
||||||
"direction": "R",
|
|
||||||
"distance": 1948.575483806973,
|
|
||||||
"epochTime": 1674650956,
|
|
||||||
"latitude": -33.43729782104492,
|
|
||||||
"licensePlate": "LJHA57",
|
|
||||||
"longitude": -70.52730560302734,
|
|
||||||
"realtime": True,
|
|
||||||
"route": "401",
|
|
||||||
"routeId": "401",
|
|
||||||
"timeLabel": "09:49",
|
|
||||||
"tripId": "401-I-L-005"
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
|
|
||||||
def approx_km(data):
|
def approx_km(data):
|
||||||
distance_meters = data["distance"]
|
distance_meters = data["distance"]
|
||||||
distance_km = distance_meters / 100 # Convert meters to kilometers
|
distance_km = distance_meters / 100 # Convert meters to kilometers
|
||||||
|
@ -31,7 +40,7 @@ def approx_km(data):
|
||||||
return approx_km
|
return approx_km
|
||||||
|
|
||||||
def calc_remaining_time(data):
|
def calc_remaining_time(data):
|
||||||
arrival_time = data["timeLabel"]
|
arrival_time = data["timeLabel"][:-3]
|
||||||
target_time = datetime.strptime(arrival_time, "%H:%M").time()
|
target_time = datetime.strptime(arrival_time, "%H:%M").time()
|
||||||
current_time = datetime.now().time()
|
current_time = datetime.now().time()
|
||||||
|
|
||||||
|
@ -44,7 +53,9 @@ def calc_remaining_time(data):
|
||||||
return remaining_minutes
|
return remaining_minutes
|
||||||
|
|
||||||
def obtain_min_max_time(remaining_time):
|
def obtain_min_max_time(remaining_time):
|
||||||
if remaining_time == 1:
|
if type(remaining_time) != int:
|
||||||
|
return "N/A", "N/A"
|
||||||
|
elif remaining_time == 1:
|
||||||
return 0, 1
|
return 0, 1
|
||||||
elif remaining_time == 2:
|
elif remaining_time == 2:
|
||||||
return 1, 2
|
return 1, 2
|
||||||
|
@ -70,19 +81,44 @@ def obtain_min_max_time(remaining_time):
|
||||||
# 'letter_background_color': 'green', Color del fondo para la letra
|
# 'letter_background_color': 'green', Color del fondo para la letra
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Carga los datos
|
bus_stop = Paradero()
|
||||||
data = load_data()
|
|
||||||
|
data = bus_stop.get_data()
|
||||||
|
if data is not None:
|
||||||
|
data1 = data[0]
|
||||||
|
data2 = data[1]
|
||||||
|
|
||||||
|
# print(data)
|
||||||
|
|
||||||
# Calcula el tiempo restante a la llegada
|
# Calcula el tiempo restante a la llegada
|
||||||
remaining_time = calc_remaining_time(data)
|
remaining_time1 = data1['timeRemaining']
|
||||||
|
remaining_time2 = data2['timeRemaining']
|
||||||
# Obtiene valores máximos y mínimo de rangos para desplegar en pantalla
|
# Obtiene valores máximos y mínimo de rangos para desplegar en pantalla
|
||||||
min_time, max_time = obtain_min_max_time(remaining_time)
|
min_time1, max_time1 = obtain_min_max_time(remaining_time1)
|
||||||
|
min_time2, max_time2 = obtain_min_max_time(remaining_time2)
|
||||||
|
ruta1 = data1['route']
|
||||||
|
direccion1 = data1['direction']
|
||||||
|
ruta2 = data2['route']
|
||||||
|
direccion2 = data2['direction']
|
||||||
|
|
||||||
|
else:
|
||||||
|
remaining_time1 = 'N/A'
|
||||||
|
remaining_time2 = 'N/A'
|
||||||
|
min_time1 = 'N/A'
|
||||||
|
min_time2 = 'N/A'
|
||||||
|
max_time1 = 'N/A'
|
||||||
|
max_time2 = 'N/A'
|
||||||
|
ruta1 = "00"
|
||||||
|
direccion1 = "X"
|
||||||
|
ruta2 = "99"
|
||||||
|
direccion2 = "Y"
|
||||||
|
|
||||||
# Selecciona el tema
|
# Selecciona el tema
|
||||||
theme = 'day'
|
theme = 'night'
|
||||||
|
|
||||||
# Alto y ancho de la imagen en pixeles
|
# Alto y ancho de la imagen en pixeles
|
||||||
height, width = 40, 160
|
#height, width = 40, 160
|
||||||
|
height, width = 200, 800
|
||||||
|
|
||||||
# Inicia el dibujo y setea el tema
|
# Inicia el dibujo y setea el tema
|
||||||
full_panel = MyDraw(height=height, width=width)
|
full_panel = MyDraw(height=height, width=width)
|
||||||
|
@ -90,20 +126,21 @@ def main():
|
||||||
full_panel.start_draw()
|
full_panel.start_draw()
|
||||||
|
|
||||||
# Agrega el anuncio de los minutos restante al arribo
|
# Agrega el anuncio de los minutos restante al arribo
|
||||||
time_anmc1 = TimeAnnouncement(aprox((2/5)*height), aprox((1/3)*width))
|
time_anmc1 = TimeAnnouncement(aprox((2/5)*height), aprox((2/5)*width))
|
||||||
time_anmc1.set_theme(theme)
|
time_anmc1.set_theme(theme)
|
||||||
time_anmc1.start_draw()
|
time_anmc1.start_draw()
|
||||||
# time_anmc1.set_background()
|
# time_anmc1.set_background()
|
||||||
# time_anmc1.set_base_text()
|
# time_anmc1.set_base_text()
|
||||||
time_anmc1.set_min_max_text(min_time=min_time, max_time=max_time)
|
time_anmc1.set_remaining_text(data[0]['timeRemaining'])
|
||||||
|
|
||||||
# Agrega el anuncio de los minutos restante al arribo
|
# Agrega el anuncio de los minutos restante al arribo
|
||||||
time_anmc2 = TimeAnnouncement(aprox((2/5)*height), aprox((1/3)*width))
|
time_anmc2 = TimeAnnouncement(aprox((2/5)*height), aprox((2/5)*width))
|
||||||
time_anmc2.set_theme(theme)
|
time_anmc2.set_theme(theme)
|
||||||
time_anmc2.start_draw()
|
time_anmc2.start_draw()
|
||||||
# time_anmc2.set_background()
|
# time_anmc2.set_background()
|
||||||
# time_anmc2.set_base_text()
|
# time_anmc2.set_base_text()
|
||||||
time_anmc2.set_min_max_text(min_time=3, max_time=5)
|
# time_anmc2.set_min_max_text(min_time=min_time2, max_time=max_time2)
|
||||||
|
time_anmc2.set_remaining_text(data[1]['timeRemaining'])
|
||||||
|
|
||||||
# Genera la imagen de la linea del bus
|
# Genera la imagen de la linea del bus
|
||||||
poster1 = BusPoster(aprox(1.1*(1/3)*height), aprox(1.1*(1/3)*width))
|
poster1 = BusPoster(aprox(1.1*(1/3)*height), aprox(1.1*(1/3)*width))
|
||||||
|
@ -112,19 +149,21 @@ def main():
|
||||||
|
|
||||||
bus_announcement_1 = {
|
bus_announcement_1 = {
|
||||||
'proportion': 0.6,
|
'proportion': 0.6,
|
||||||
'width_border': 2,
|
'width_border': 3,
|
||||||
'font_size': 11,
|
# 'font_size': 11,
|
||||||
|
'font_size': 80,
|
||||||
'number_background_color': 'yellow',
|
'number_background_color': 'yellow',
|
||||||
'letter_background_color': 'green',
|
'letter_background_color': data[0]['number_background_color'],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Se setean los parametros
|
# Se setean los parametros
|
||||||
poster1.set_params(bus_announcement_1)
|
poster1.set_params(bus_announcement_1)
|
||||||
poster1.load_barlow()
|
poster1.load_barlow()
|
||||||
poster1.set_colors()
|
poster1.set_colors()
|
||||||
|
|
||||||
# Se setea la ruta y la direccion en la que va
|
# Se setea la ruta y la direccion en la que va
|
||||||
poster1.set_bus_number(bus_number=data["route"])
|
poster1.set_bus_number(bus_number=ruta1)
|
||||||
poster1.set_bus_letter(bus_letter=data["direction"])
|
poster1.set_bus_letter(bus_letter=direccion1)
|
||||||
|
|
||||||
# Genera la imagen de la linea del bus
|
# Genera la imagen de la linea del bus
|
||||||
poster2 = BusPoster(aprox(1.1*(1/3)*height), aprox(1.1*(1/3)*width))
|
poster2 = BusPoster(aprox(1.1*(1/3)*height), aprox(1.1*(1/3)*width))
|
||||||
|
@ -133,10 +172,11 @@ def main():
|
||||||
|
|
||||||
bus_announcement_2 = {
|
bus_announcement_2 = {
|
||||||
'proportion': 0.6,
|
'proportion': 0.6,
|
||||||
'width_border': 2,
|
'width_border': 3,
|
||||||
'font_size': 11,
|
# 'font_size': 11,
|
||||||
|
'font_size': 80,
|
||||||
'number_background_color': 'yellow',
|
'number_background_color': 'yellow',
|
||||||
'letter_background_color': 'blue',
|
'letter_background_color': data[1]['number_background_color'],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Se setean los parametros
|
# Se setean los parametros
|
||||||
|
@ -144,17 +184,30 @@ def main():
|
||||||
poster2.load_barlow()
|
poster2.load_barlow()
|
||||||
poster2.set_colors()
|
poster2.set_colors()
|
||||||
# Se setea la ruta y la direccion en la que va
|
# Se setea la ruta y la direccion en la que va
|
||||||
poster2.set_bus_number(bus_number="16")
|
poster2.set_bus_number(bus_number=ruta2)
|
||||||
poster2.set_bus_letter(bus_letter="I")
|
poster2.set_bus_letter(bus_letter=direccion2)
|
||||||
|
|
||||||
# Se agregan todas las imagenes al canvas
|
# Se agregan todas las imagenes al canvas
|
||||||
full_panel.add_image(time_anmc1, (aprox((0.6)*width), aprox(0.05*height)))
|
full_panel.add_image(time_anmc1, (aprox((0.55)*width), aprox(0.05*height)))
|
||||||
full_panel.add_image(time_anmc2, (aprox((0.6)*width), aprox(0.45*height)))
|
full_panel.add_image(time_anmc2, (aprox((0.55)*width), aprox(0.45*height)))
|
||||||
full_panel.add_image(poster1, (aprox((0.05)*width), aprox((0.1)*height)))
|
full_panel.add_image(poster1, (aprox((0.05)*width), aprox((0.1)*height)))
|
||||||
full_panel.add_image(poster2, (aprox((0.05)*width), aprox((0.5)*height)))
|
full_panel.add_image(poster2, (aprox((0.05)*width), aprox((0.5)*height)))
|
||||||
#full_panel.add_image(bm, (aprox(0.02*width),aprox((1/6)*height)))
|
#full_panel.add_image(bm, (aprox(0.02*width),aprox((1/6)*height)))
|
||||||
full_panel.get_image()
|
full_panel.get_image()
|
||||||
full_panel.save_image('/app/data/output/images/poster.png')
|
full_panel.save_image('/srv/ledram/poster.png')
|
||||||
|
|
||||||
|
nuevo_alto = 40 # Reemplaza con el alto deseado en píxeles
|
||||||
|
nuevo_ancho = 160 # Reemplaza con el ancho deseado en píxeles
|
||||||
|
input_path = f'/srv/ledram/poster.png'
|
||||||
|
output_path = '/srv/ledram/next.png'
|
||||||
|
|
||||||
|
reescalar_imagen(input_path, output_path, nuevo_ancho, nuevo_alto)
|
||||||
|
subprocess.run(['cp', output_path, '/srv/ledram/current.png'])
|
||||||
|
|
||||||
|
|
||||||
|
from apscheduler.schedulers.blocking import BlockingScheduler
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
sched = BlockingScheduler()
|
||||||
|
sched.add_job(main, 'interval', seconds=15)
|
||||||
|
sched.start()
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import pytz
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
|
||||||
|
class Paradero:
|
||||||
|
def __init__(self):
|
||||||
|
# Dispositivo
|
||||||
|
self.id = "panel-pruebas-2"
|
||||||
|
|
||||||
|
# Autentificación data
|
||||||
|
self.url_auth = 'https://gestion.tdt-dev.ilab.cl/api/auth/'
|
||||||
|
self.rut = "11111111-1"
|
||||||
|
self.password = "usuario1"
|
||||||
|
|
||||||
|
# Token obtenido luego del 'login'
|
||||||
|
self.token = self.__get_token()
|
||||||
|
# print(self.token)
|
||||||
|
|
||||||
|
# URL de la API para obtener los datos de los recorridos
|
||||||
|
self.url_getinfodevice = 'https://gestion.tdt-dev.ilab.cl/api/dispositivos/getInfoDevice/'
|
||||||
|
self.data = None
|
||||||
|
self.bus_list = []
|
||||||
|
self.servicios = ["12Q", "56O"]
|
||||||
|
|
||||||
|
|
||||||
|
def __get_token(self):
|
||||||
|
auth = '''{
|
||||||
|
"rut": "11111111-1",
|
||||||
|
"password": "usuario1"
|
||||||
|
}'''
|
||||||
|
|
||||||
|
response = requests.post(self.url_auth, data=auth)
|
||||||
|
|
||||||
|
# Estado de la respuesta
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()['token']
|
||||||
|
else:
|
||||||
|
# print(response)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
# Datos para la solicitud
|
||||||
|
data_getinfodevice = {
|
||||||
|
"GetInfoDevice": {
|
||||||
|
"idDispositivo": self.id,
|
||||||
|
"KeyAuthorizacion": "tokenSinUso" #Autentificacion de mentira sisisi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.token is not None:
|
||||||
|
#Aquí se ingresa el token obtenido anteriormente
|
||||||
|
headers_getinfodevice = {
|
||||||
|
'Authorization': f'Bearer {self.token}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Request
|
||||||
|
response = requests.post(self.url_getinfodevice, json=data_getinfodevice, headers=headers_getinfodevice)
|
||||||
|
|
||||||
|
self.data = self.__serialize_data(response)
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
def __generate_bus_list(self, info):
|
||||||
|
data_main = {}
|
||||||
|
zona_horaria_santiago = pytz.timezone('America/Santiago')
|
||||||
|
hora_actual_santiago = datetime.now(zona_horaria_santiago).time()
|
||||||
|
|
||||||
|
for i in range(len(info["GetInfoDeviceResponse"]["DetalleLineas"])):
|
||||||
|
|
||||||
|
data = info["GetInfoDeviceResponse"]["DetalleLineas"][i]
|
||||||
|
if data["Descripcion"] not in self.servicios:
|
||||||
|
continue
|
||||||
|
|
||||||
|
bus_info = {}
|
||||||
|
if len(data['Llegadas']) > 0 and data["Llegadas"][0]["DistanciaGPS"] is not None:
|
||||||
|
bus_info["distance"] = data["Llegadas"][0]["DistanciaGPS"]
|
||||||
|
bus_info["timeLabel"] = data["Llegadas"][0]["EstimadaGPS"]
|
||||||
|
bus_info["patente"] = data["Llegadas"][0]["patente"]
|
||||||
|
bus_hour = datetime.strptime(bus_info["timeLabel"], "%H:%M:%S").time().hour if datetime.strptime(bus_info["timeLabel"], "%H:%M:%S").time().hour != 0 else 24
|
||||||
|
diff = timedelta(
|
||||||
|
hours = bus_hour - hora_actual_santiago.hour,
|
||||||
|
minutes = datetime.strptime(bus_info["timeLabel"], "%H:%M:%S").time().minute - hora_actual_santiago.minute,
|
||||||
|
seconds=datetime.strptime(bus_info["timeLabel"], "%H:%M:%S").time().second - hora_actual_santiago.second
|
||||||
|
)
|
||||||
|
# print(diff.total_seconds())
|
||||||
|
bus_info["timeLabel"] = "m"
|
||||||
|
|
||||||
|
if data['Llegadas'][0]['textoLlegada'].startswith("Entre"):
|
||||||
|
data['Llegadas'][0]['textoLlegada'] = data['Llegadas'][0]['textoLlegada'][5:]
|
||||||
|
cut = data['Llegadas'][0]['textoLlegada'].index("min")+3
|
||||||
|
bus_info["timeRemaining"] = data['Llegadas'][0]['textoLlegada'][:cut]
|
||||||
|
else:
|
||||||
|
bus_info["distance"] = "-"
|
||||||
|
bus_info["timeLabel"] = "Sin Info"
|
||||||
|
bus_info["timeRemaining"] = "Sin Dato"
|
||||||
|
|
||||||
|
bus_info["route"] = data["Descripcion"][:-1] if data["Descripcion"] is not None else "-"
|
||||||
|
bus_info["direction"] = data["Descripcion"][-1] if data["Descripcion"] is not None else "-"
|
||||||
|
bus_info["number_background_color"] = "#{}".format(data["colorFondo"])
|
||||||
|
bus_info["letter_background_color"] = "#{}".format(data["colorTexto"])
|
||||||
|
|
||||||
|
data_main[data["Descripcion"]] = bus_info
|
||||||
|
|
||||||
|
# data_main = sorted(data_main, key=lambda x: x['timeRemaining'])
|
||||||
|
salida = []
|
||||||
|
for servicio in self.servicios:
|
||||||
|
if servicio in data_main:
|
||||||
|
salida.append(data_main[servicio])
|
||||||
|
|
||||||
|
self.bus_list = salida
|
||||||
|
|
||||||
|
# for d in data_main:
|
||||||
|
# print(d['timeRemaining'], d['timeLabel'])
|
||||||
|
|
||||||
|
def __serialize_data(self, response):
|
||||||
|
data = response.json()
|
||||||
|
self.__generate_bus_list(data)
|
||||||
|
|
||||||
|
return self.bus_list
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
def api_request():
|
||||||
|
# URL de la API para "autentificación"
|
||||||
|
url_auth = 'https://transporte.hz.kursor.cl/api/auth/'
|
||||||
|
|
||||||
|
# Datos para autentificar
|
||||||
|
auth = '''{
|
||||||
|
"username": "usuario1",
|
||||||
|
"password": "usuario1"
|
||||||
|
}'''
|
||||||
|
|
||||||
|
# Request
|
||||||
|
token = requests.post(url_auth, data=auth)
|
||||||
|
token = token.json()['token']
|
||||||
|
|
||||||
|
# URL de la API para info del paradero
|
||||||
|
url_whoami = 'https://transporte.hz.kursor.cl/api/dispositivos/whoami/'
|
||||||
|
|
||||||
|
# Datos de la solicitud
|
||||||
|
data_whoami = {
|
||||||
|
"whoami": {
|
||||||
|
"idDispositivo": "pled30-gtr",
|
||||||
|
"KeyAuthorizacion": "token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Aquí se ingresa el token obtenido anteriormente
|
||||||
|
headers_whoami = {
|
||||||
|
'Authorization': f'Bearer {token}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
response_whoami = requests.post(url_whoami, json=data_whoami, headers=headers_whoami) #Request
|
||||||
|
Paradero = response_whoami.json()
|
||||||
|
|
||||||
|
url_getinfodevice = 'https://transporte.hz.kursor.cl/api/dispositivos/getInfoDevice/' # URL de la API para obtener los datos de los recorridos
|
||||||
|
|
||||||
|
# Datos para la solicitud
|
||||||
|
data_getinfodevice = {
|
||||||
|
"GetInfoDevice": {
|
||||||
|
"idDispositivo": "00000000160f3b42b8:27:eb:0f:3b:42",
|
||||||
|
"KeyAuthorizacion": "tokenSinUso"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Aquí se ingresa el token obtenido anteriormente
|
||||||
|
headers_getinfodevice = {
|
||||||
|
'Authorization': f'Bearer {token}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Request
|
||||||
|
response_getinfodevice = requests.post(url_getinfodevice, json=data_getinfodevice, headers=headers_getinfodevice)
|
||||||
|
info = response_getinfodevice.json()
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#Haciendo una lista de todos los buses de este paradero
|
||||||
|
|
||||||
|
def lista_buses(info):
|
||||||
|
data_main = []
|
||||||
|
hora_actual = datetime.now().time()
|
||||||
|
|
||||||
|
for i in range(len(info["GetInfoDeviceResponse"]["DetalleLineas"])):
|
||||||
|
|
||||||
|
data = info["GetInfoDeviceResponse"]["DetalleLineas"][i]
|
||||||
|
|
||||||
|
bus_info = {}
|
||||||
|
bus_info["distance"] = data["Llegadas"][0]["DistanciaGPS"] if data["Llegadas"][0]["DistanciaGPS"] is not None else "-"
|
||||||
|
bus_info["timeLabel"] = data["Llegadas"][0]["EstimadaGPS"] if data["Llegadas"][0]["EstimadaGPS"] is not None else "-"
|
||||||
|
bus_info["route"] = data["Descripcion"][:-1] if data["Descripcion"] is not None else "-"
|
||||||
|
bus_info["direction"] = data["Descripcion"][-1] if data["Descripcion"] is not None else "-"
|
||||||
|
bus_info["number_background_color"] = data["colorFondo"]
|
||||||
|
bus_info["letter_background_color"] = data["colorTexto"]
|
||||||
|
bus_info["patente"] = data["Llegadas"][0]["patente"]
|
||||||
|
diff = timedelta(hours = datetime.strptime(bus_info["timeLabel"], "%H:%M:%S").time().hour - hora_actual.hour,minutes = datetime.strptime(bus_info["timeLabel"], "%H:%M:%S").time().minute - hora_actual.minute,seconds=datetime.strptime(bus_info["timeLabel"], "%H:%M:%S").time().second - hora_actual.second)
|
||||||
|
bus_info["timeRemaining"] = int(abs(diff.total_seconds() // 60))
|
||||||
|
data_main.append(bus_info)
|
||||||
|
|
||||||
|
return data_main
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#Exportando datos
|
||||||
|
def export_data():
|
||||||
|
X = api_request()
|
||||||
|
data_main = lista_buses(X)
|
||||||
|
data_time = sorted(data_main, key=lambda x: x['timeRemaining'],reverse=True)
|
||||||
|
|
||||||
|
data_x = (data_time[0],data_time[1])
|
||||||
|
return data_x
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Este script requiere permisos de root."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set pwd to current directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
#limpia el contenido del directorio de trabajo
|
||||||
|
rm -rf /srv/parada*
|
||||||
|
|
||||||
|
mkdir /srv/parada_led
|
||||||
|
|
||||||
|
cp -rf *.py assets scripts /srv/parada_led
|
||||||
|
|
||||||
|
#Crea el servicio
|
||||||
|
cp -rf parada_led.service /etc/systemd/system/parada_led.service
|
||||||
|
|
||||||
|
# Recarga e inicia automaticamente al prender.
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl unmask parada_led.service
|
||||||
|
systemctl enable parada_led.service
|
||||||
|
|
||||||
|
# Mensajes de salida
|
||||||
|
echo "Debe reiniciar la Raspberry para que el servicio pueda iniciarse"
|
||||||
|
echo "Luego para actualizar, solo debe modificar el el archivo '/srv/ledram/current.png' para actualizar la pantalla"
|
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=WebService Datos Parada
|
||||||
|
After=local-fs.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/srv/parada_led
|
||||||
|
ExecStart=/usr/bin/python3 /srv/parada_led/app.py
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -2,3 +2,4 @@ matplotlib
|
||||||
requests
|
requests
|
||||||
Pillow
|
Pillow
|
||||||
numpy
|
numpy
|
||||||
|
pytz
|
|
@ -6,7 +6,6 @@ data_path=$project_folder_path/data
|
||||||
scripts_path=$project_folder_path/scripts
|
scripts_path=$project_folder_path/scripts
|
||||||
assets_path=$project_folder_path/assets
|
assets_path=$project_folder_path/assets
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Ejecuta el contenedor con el enlace de carpeta local
|
# Ejecuta el contenedor con el enlace de carpeta local
|
||||||
docker run --rm -d -p 8888:8888 --name make_poster -v $data_path:/app/data -v $assets_path:/app/assets -v $scripts_path:/app/scripts bus_poster
|
# docker run --rm -d -p 8888:8888 --name make_poster -v $data_path:/app/data -v $assets_path:/app/assets -v $scripts_path:/app/scripts -v $project_folder_path:/app bus_poster
|
||||||
|
docker run --rm -d --name make_poster -v $project_folder_path:/app bus_poster
|
|
@ -37,10 +37,12 @@ class BusPoster(MyDraw):
|
||||||
def set_bus_number(self, bus_number="11"):
|
def set_bus_number(self, bus_number="11"):
|
||||||
text_color = 'black'
|
text_color = 'black'
|
||||||
width_border = self.prms['width_border']
|
width_border = self.prms['width_border']
|
||||||
|
# width_border = 0
|
||||||
text_bbox = self.font.getbbox(str(bus_number))
|
text_bbox = self.font.getbbox(str(bus_number))
|
||||||
font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
||||||
offset_width = np.round((self.prms['proportion']*self.width-width_border)/2) - np.round(font_width/2)
|
offset_width = np.round((self.prms['proportion']*self.width-width_border)/2) - np.round(font_width/2)
|
||||||
text_position = (offset_width,0)
|
# offset_width = 0
|
||||||
|
text_position = (offset_width,-15)
|
||||||
self.draw.text(
|
self.draw.text(
|
||||||
text_position,
|
text_position,
|
||||||
bus_number,
|
bus_number,
|
||||||
|
@ -57,7 +59,7 @@ class BusPoster(MyDraw):
|
||||||
text_bbox = self.font.getbbox(str(bus_letter))
|
text_bbox = self.font.getbbox(str(bus_letter))
|
||||||
font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
||||||
offset_width = np.round((proportion*self.width-width_border)) + 0.75*np.round(font_width/2)
|
offset_width = np.round((proportion*self.width-width_border)) + 0.75*np.round(font_width/2)
|
||||||
text_position = (1.1*offset_width,0)
|
text_position = (1.1*offset_width,-15)
|
||||||
self.draw.text(
|
self.draw.text(
|
||||||
text_position,
|
text_position,
|
||||||
bus_letter,
|
bus_letter,
|
|
@ -65,7 +65,8 @@ class MyDraw():
|
||||||
|
|
||||||
def load_barlow(self, font_size=None):
|
def load_barlow(self, font_size=None):
|
||||||
# Ruta a la fuente TTF personalizada
|
# Ruta a la fuente TTF personalizada
|
||||||
font_path = "/app/assets/fonts/Barlow-Medium.ttf"
|
# font_path = "../../assets/fonts/Barlow-Medium.ttf"
|
||||||
|
font_path = "assets/fonts/Barlow-Medium.ttf"
|
||||||
# Carga la fuente
|
# Carga la fuente
|
||||||
if font_size is None:
|
if font_size is None:
|
||||||
self.font = ImageFont.truetype(font_path, self.prms['font_size'])
|
self.font = ImageFont.truetype(font_path, self.prms['font_size'])
|
|
@ -45,25 +45,49 @@ class TimeAnnouncement(MyDraw):
|
||||||
|
|
||||||
text = "Tiempo aprox"
|
text = "Tiempo aprox"
|
||||||
text_color = self.theme_params['text_color']
|
text_color = self.theme_params['text_color']
|
||||||
self.load_barlow(font_size=11)
|
self.load_barlow(font_size=70)
|
||||||
text_bbox = self.font.getbbox(text)
|
text_bbox = self.font.getbbox(text)
|
||||||
base_font_width, base_font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
base_font_width, base_font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
||||||
|
|
||||||
|
try:
|
||||||
if (int(max_time) <= 1):
|
if (int(max_time) <= 1):
|
||||||
text = "< 1 min"
|
text = "< 1 min"
|
||||||
elif (int(min_time) >= 10):
|
elif (int(min_time) >= 10):
|
||||||
text = "> 10 min"
|
print(max_time)
|
||||||
|
text = f"> {max_time} min"
|
||||||
else:
|
else:
|
||||||
text = f'{min_time} a {max_time} min'
|
text = f'{min_time} a {max_time} min'
|
||||||
|
except:
|
||||||
|
text = 'N/A'
|
||||||
|
|
||||||
self.load_barlow(font_size=12)
|
self.load_barlow(font_size=70)
|
||||||
text_bbox = self.font.getbbox(text)
|
text_bbox = self.font.getbbox(text)
|
||||||
font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
||||||
# print(font_width, font_height)
|
# print(font_width, font_height)
|
||||||
offset_width = (np.round((self.width-self.border)) - np.round(font_width))/2
|
offset_width = (np.round((self.width-self.border)) - np.round(font_width))/2
|
||||||
offset_height = (np.round((self.height-self.border)) - np.round(base_font_height))/2
|
offset_height = (np.round((self.height-self.border)) - np.round(base_font_height))/2
|
||||||
# text_position = (offset_width,5+offset_height)
|
# text_position = (offset_width,5+offset_height)
|
||||||
text_position = (offset_width,offset_height)
|
text_position = (offset_width,offset_height-10)
|
||||||
|
# text_position = (0, 0)
|
||||||
|
self.draw.text(
|
||||||
|
text_position,
|
||||||
|
text,
|
||||||
|
fill=text_color,
|
||||||
|
font=self.font,
|
||||||
|
align ="center"
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_remaining_text(self, text):
|
||||||
|
|
||||||
|
text_color = self.theme_params['text_color']
|
||||||
|
self.load_barlow(font_size=70)
|
||||||
|
text_bbox = self.font.getbbox(text)
|
||||||
|
font_width, font_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
|
||||||
|
# print(font_width, font_height)
|
||||||
|
offset_width = (np.round((self.width-self.border)) - np.round(font_width))/2
|
||||||
|
offset_height = (np.round((self.height-self.border)) - np.round(font_height))/2
|
||||||
|
# text_position = (offset_width,5+offset_height)
|
||||||
|
text_position = (offset_width,offset_height-10)
|
||||||
# text_position = (0, 0)
|
# text_position = (0, 0)
|
||||||
self.draw.text(
|
self.draw.text(
|
||||||
text_position,
|
text_position,
|
|
@ -1,92 +0,0 @@
|
||||||
# TODO: This contains a lot of {c|p}ython build boilerplate, this needs cleanup.
|
|
||||||
PYTHON ?= python
|
|
||||||
SETUP := setup.py
|
|
||||||
BUILD_ARGS := build --build-lib .
|
|
||||||
INST_ARGS := install
|
|
||||||
ifdef DESTDIR
|
|
||||||
INST_ARGS += --root=$(DESTDIR)
|
|
||||||
endif
|
|
||||||
CLEAN_ARGS := clean --all
|
|
||||||
|
|
||||||
MANPAGES := $(patsubst %.txt,%,$(wildcard *.txt))
|
|
||||||
TXTTOMAN := a2x -f manpage
|
|
||||||
|
|
||||||
# Where our library resides. It is split between includes and the binary
|
|
||||||
# library in lib
|
|
||||||
RGB_LIBDIR=../../lib
|
|
||||||
RGB_LIBRARY_NAME=rgbmatrix
|
|
||||||
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
|
|
||||||
|
|
||||||
ifneq "$(wildcard debian/changelog)" ""
|
|
||||||
PKGNAME := $(shell dpkg-parsechangelog | sed -n 's/^Source: //p')
|
|
||||||
VERSION := $(shell dpkg-parsechangelog | sed -n 's/^Version: \([^-]*\).*/\1/p')
|
|
||||||
UPSDIST := $(PKGNAME)-$(VERSION).tar.gz
|
|
||||||
DEBDIST := $(PKGNAME)_$(VERSION).orig.tar.gz
|
|
||||||
endif
|
|
||||||
|
|
||||||
all: build
|
|
||||||
build: build-python
|
|
||||||
install: install-python
|
|
||||||
clean: clean-python
|
|
||||||
find ./rgbmatrix -type f -name \*.so -delete
|
|
||||||
find . -type f -name \*.pyc -delete
|
|
||||||
$(RM) build-* install-* test-*
|
|
||||||
|
|
||||||
$(RGB_LIBRARY): FORCE
|
|
||||||
$(MAKE) -C $(RGB_LIBDIR)
|
|
||||||
|
|
||||||
test: test-python
|
|
||||||
test-python:
|
|
||||||
ifneq "$(wildcard tests/*.py)" ""
|
|
||||||
nosetests -v -w tests
|
|
||||||
else
|
|
||||||
$(info Test suite is not implemented...)
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifneq "$(wildcard debian/control)" ""
|
|
||||||
PYVERS := $(shell pyversions -r -v debian/control)
|
|
||||||
PYEXEC := $(shell pyversions -d)
|
|
||||||
BUILD_ARGS += --executable=/usr/bin/$(PYEXEC)
|
|
||||||
INST_ARGS += --no-compile -O0
|
|
||||||
|
|
||||||
build-python: $(PYVERS:%=build-python-%)
|
|
||||||
build-python-%: $(RGB_LIBRARY)
|
|
||||||
$(info * Doing build for $(PYTHON)$* ...)
|
|
||||||
$(PYTHON)$* $(SETUP) $(BUILD_ARGS)
|
|
||||||
|
|
||||||
install-python: $(PYVERS:%=install-python-%)
|
|
||||||
install-python-%:
|
|
||||||
$(info * Doing install for $(PYTHON)$* ...)
|
|
||||||
$(PYTHON)$* $(SETUP) $(INST_ARGS)
|
|
||||||
|
|
||||||
clean-python: $(PYVERS:%=clean-python-%)
|
|
||||||
clean-python-%:
|
|
||||||
$(PYTHON)$* $(SETUP) $(CLEAN_ARGS)
|
|
||||||
else
|
|
||||||
build-python: $(RGB_LIBRARY)
|
|
||||||
$(PYTHON) $(SETUP) $(BUILD_ARGS)
|
|
||||||
|
|
||||||
install-python:
|
|
||||||
$(PYTHON) $(SETUP) $(INST_ARGS)
|
|
||||||
|
|
||||||
clean-python:
|
|
||||||
$(PYTHON) $(SETUP) $(CLEAN_ARGS)
|
|
||||||
endif
|
|
||||||
|
|
||||||
distclean: clean
|
|
||||||
dist: distclean
|
|
||||||
$(info * Creating ../$(UPSDIST) and ../$(DEBDIST))
|
|
||||||
@tar --exclude='.svn' \
|
|
||||||
--exclude='*.swp' \
|
|
||||||
--exclude='debian' \
|
|
||||||
-czvf ../$(UPSDIST) \
|
|
||||||
-C ../ $(notdir $(CURDIR))
|
|
||||||
@cp ../$(UPSDIST) ../$(DEBDIST)
|
|
||||||
@if test -d ../tarballs; then \
|
|
||||||
mv -v ../$(DEBDIST) ../tarballs; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
FORCE:
|
|
||||||
.PHONY: FORCE
|
|
||||||
.PHONY: build install test clean dist distclean
|
|
||||||
.PHONY: build-python install-python clean-python
|
|
|
@ -1,23 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cd samples
|
|
||||||
echo "Este es un Menú para controlar Módulos LED"
|
|
||||||
echo "¿Que acción desea realizar?:"
|
|
||||||
echo "1. Editar código base"
|
|
||||||
echo "2. Desplegar Imagen"
|
|
||||||
read N
|
|
||||||
|
|
||||||
if [ $N -eq 1 ]
|
|
||||||
then
|
|
||||||
|
|
||||||
nano image-viewer.py
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
ls
|
|
||||||
echo "Ingrese el nombre del archivo que desea desplegar:"
|
|
||||||
read filename
|
|
||||||
|
|
||||||
sudo python3 image-viewer.py $filename
|
|
||||||
|
|
||||||
fi
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Guía de configuración para Modulo LED
|
||||||
|
|
||||||
|
## Materiales Necesarios
|
||||||
|
|
||||||
|
- Raspberry Pi, versión 3 o superior.
|
||||||
|
- Hat de conexión HUB75
|
||||||
|
- Paneles LED P4 con I/O HUB75
|
||||||
|
- Fuente de alimentación con salida 5V/3A
|
||||||
|
- Cables de Alimentación 4-pin 5V/3A
|
||||||
|
- Cables de datos con conexión HUB75
|
||||||
|
|
||||||
|
## Configuración inicial
|
||||||
|
|
||||||
|
### Conexionado
|
||||||
|
|
||||||
|
Para las conexiones de datos, se utilizan cables hembra HUB75 de 16-pines, estos se conectan en un extremo al HAT HUB75 para Raspberry PI. En caso de tener un HAT con multiples conexiones de HUB75, siempre utilizar la salida TOP para la primera fila del conjunto de paneles, y usar el resto de las salidas para las filas inferiores. El extremo opuesto del cable de datos debe conectarse en el "input" del panel, estos modulos tienen etiquetada su entrada y salida en la parte posterior. Ver las imagenes mostradas como referencia visual.
|
||||||
|
|
||||||
|
Para la conexión electrica, cada uno de los paneles LED tiene una entrada de cuatro pines para cables de alimentación. Es necesario tener una fuente de poder con salida de 5V y un minimo de 3A, para cumplir con los criterios de alimentación en los modulos LED.
|
||||||
|
|
||||||
|
  
|
||||||
|
|
||||||
|
|
||||||
|
A continuación, se presenta un diagrama de conexiones, tanto para los datos como para la alimentación. Notar que la referencia (0,0) corresponde a la orientación superior izquierda de las gráficas que se deseen desplegar en el módulo, también se señala la orientación de las entradas y salidas de cada panel en la configuración.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Configuración de Raspberry Pi
|
||||||
|
|
||||||
|
Luego de haber descargado los datos almancenados en este reposotorio. En el directorio ModuloLED, encontrará un script llamado `MenuPantalla.sh`, en este menú se puede hacer dos acciones en concreto:
|
||||||
|
|
||||||
|
- Configurar parámetros de la implementación, modificando variables del codigo base.
|
||||||
|
- Desplegar una imagen a partir de un archivo imagen jpg o png almacenada en el sistema.
|
||||||
|
|
||||||
|
#### Configuración de Parámetros
|
||||||
|
|
||||||
|
Entre las opciones de configuración en el codigo base para el funcionamiento del panel se muestran en la siguiente tabla:
|
||||||
|
|
||||||
|
| Parámetro | Variable código base | Rango de valores |
|
||||||
|
| ------------ | ------------ | ------------ |
|
||||||
|
| N° de filas de pixeles en un panel. | `options.rows` | PANEL |
|
||||||
|
| N° de columnas de pixeles en un panel. | `options.col` | PANEL |
|
||||||
|
| N° de filas de paneles montados | `options.parallel` | 1 - 3 |
|
||||||
|
| N° de columnas de paneles montados | `options.chain_length` | 1 - 3 |
|
||||||
|
| Brillo | `options.brightness` | 0 - 100 |
|
||||||
|
| Mapeo GPIO para HAT | `options.hardware_mapping` | `regular`, ver otras opciones [aquí](https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md#alternative-hardware-mappings) |
|
||||||
|
| Multiplexación | `options.multiplexing` | 1 (por defecto) - 17|
|
||||||
|
| Retardo GPIO | `options.gpio_slowdown` | 1 - 5 |
|
||||||
|
|
||||||
|
**PANEL: Dado por el fabricante del panel comprado.**
|
||||||
|
|
||||||
|
## Despliegue de imagenes
|
||||||
|
|
||||||
|
Al seleccionar esta opción, se mostrarán todos los archivos de imagen guardados en el sistema de la Raspberry Pi. Se recomienda utilizar imagenes con la mimsa relacion de aspecto que la resolución de la pantalla montada para el despliegue. De lo contrario, la pantalla tendrá espacios en negro.
|
||||||
|
|
||||||
|
### Variables del Sistema Operativo
|
||||||
|
|
||||||
|
Se propone un Sub-sistema que se encargue de manera dedicada al renderizado en la Matriz LED en forma continua. Para eso se implementa un *demonio* de Linux que esta continuamente dibujando en el display.
|
||||||
|
|
||||||
|
Para ello se reserva el procesador 3, entregandoselele la `CPUAffinity=3` al proceso, de manera de garantizar el recurso computacional. Adicionalmente se modifica el Sistema Operativo para que quite el procesador del itineradoe usando `isocpus=3` en la variable de inicio `/boot/cmdline.txt`.
|
||||||
|
|
||||||
|
Además se monta un directorio de `/srv/ledram` que tiene un tamaño de 32MB para disponer una *memoria de video* en RAM que permita a otros procesos actualizar el contenido que se despliega en el display. Para ello se modifica el `/etc/fstab` para que se cree el recurso automaticamente al iniciarse la Raspberry.
|
||||||
|
|
||||||
|
#### Sub-sistema de renderizado
|
||||||
|
|
||||||
|
Se define el directorio `/srv` donde se aloja el sub-sistema de renderizado. Esta compuesto del script `/srv/subsystem/led-driver.py` que es iniciado en forma automática por `systemd`.
|
||||||
|
|
||||||
|
El script ve la hora de modificación del archivo `/srv/ledram/current.png` para determinar si debe o no actualizar la imagen que se está desplegando actualmente en el display cada 100 ms.
|
||||||
|
|
||||||
|
De esta manera, cualquier usuario o proceso (`chmod 666`) puede escribir ese archivo. Siendo este sub sistema el encargado de leer el contenido de la imagen y renderizarlo en el display led en forma permanente.
|
||||||
|
|
||||||
|
#### Instalación como servicio
|
||||||
|
|
||||||
|
Todos estos pasos están automatizados en el script `install-service.sh` que debe ser ejecutado como `root`.
|
||||||
|
|
||||||
|
#### TODO
|
||||||
|
|
||||||
|
Falta que el script detecte cuando se le solicita salir, para que elimine el archivo `/srv/ledram/current.png`, de tal manera de poder `systemctl stop led-driver.service` y `systemctl start led-driver.service` sin depender que la imagen se elimine en forma automatica al ser un directorio volatil
|
|
@ -1,7 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__version__ = "0.0.1"
|
|
||||||
__author__ = "Christoph Friedrich <christoph.friedrich@vonaffenfels.de>"
|
|
||||||
|
|
||||||
from .core import RGBMatrix, FrameCanvas, RGBMatrixOptions
|
|
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Este script requiere permisos de root."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set pwd to current directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
#limpia el contenido del directorio de trabajo
|
||||||
|
rm -rf /srv/ledram/*
|
||||||
|
rm -rf /srv/*
|
||||||
|
|
||||||
|
# Configura un directorio `/srv/ledram` como buffer de video
|
||||||
|
sed -i -e '/srv/d' /etc/fstab
|
||||||
|
sed -i -e '$a/tmpfs /srv/ledram tmpfs rw,nosuid,nodev,size=32m 0 0' /etc/fstab
|
||||||
|
|
||||||
|
# Crea directorio donde se almacena el buffer de video
|
||||||
|
mkdir /srv/ledram
|
||||||
|
|
||||||
|
# Desocupa el tercer procesador para ser usado exclusivamente por el sub-proceso de renderizado
|
||||||
|
sed -i -e 's/ isocpus=3//g' /boot/cmdline.txt
|
||||||
|
sed -i -e 's/$/ isocpus=3/' /boot/cmdline.txt
|
||||||
|
|
||||||
|
#copia la biblioteca al directorio de trabajo
|
||||||
|
cp -R /home/raspi/rpi-rgb-led-matrix/bindings/python/rgbmatrix /srv/rgbmatrix
|
||||||
|
|
||||||
|
#copia el sub-sistema de renderizado
|
||||||
|
mkdir /srv/subsystem
|
||||||
|
cp -rf init.png /srv
|
||||||
|
cp -rf led-driver.py /srv/subsystem
|
||||||
|
|
||||||
|
#Crea el servicio
|
||||||
|
cp -rf led-driver.service /etc/systemd/system/led-driver.service
|
||||||
|
|
||||||
|
# Recarga e inicia automaticamente al prender.
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl unmask led-driver.service
|
||||||
|
systemctl enable led-driver.service
|
||||||
|
|
||||||
|
# Mensajes de salida
|
||||||
|
echo "Debe reiniciar la Raspberry para que el servicio pueda iniciarse"
|
||||||
|
echo "Luego para actualizar, solo debe modificar el el archivo '/srv/ledram/current.png' para actualizar la pantalla"
|
|
@ -0,0 +1,56 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from rgbmatrix import RGBMatrix, RGBMatrixOptions
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Configuration for the matrix
|
||||||
|
options = RGBMatrixOptions()
|
||||||
|
options.rows = 40
|
||||||
|
options.cols = 80
|
||||||
|
options.chain_length = 2
|
||||||
|
options.parallel = 1
|
||||||
|
options.gpio_slowdown = 4
|
||||||
|
#options.row_address_type = 0
|
||||||
|
|
||||||
|
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
|
||||||
|
options.multiplexing = 1
|
||||||
|
options.brightness = 100
|
||||||
|
#options.pwm_lsb_nanoseconds = 300
|
||||||
|
#options.pwm_bits = 11
|
||||||
|
|
||||||
|
matrix = RGBMatrix(options = options)
|
||||||
|
|
||||||
|
#Bufer que se copia en la pantalla led
|
||||||
|
img_path='/srv/ledram/current.png'
|
||||||
|
#imagen por defecto a mostrar al inicializarla
|
||||||
|
init_file='/srv/init.png'
|
||||||
|
|
||||||
|
# Revisa si el bufer existe, si no existe lo crea
|
||||||
|
# y si existe sale ya que hay otro proceso que lo
|
||||||
|
if not os.path.isfile(img_path):
|
||||||
|
shutil.copy(init_file, img_path)
|
||||||
|
os.chmod(img_path, 0o666)
|
||||||
|
else:
|
||||||
|
print("El archivo de buffer ya existe!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
#guarda el tiempo de modificación
|
||||||
|
tstam = os.stat(img_path).st_mtime
|
||||||
|
|
||||||
|
with Image.open(img_path) as image:
|
||||||
|
matrix.SetImage(image.convert('RGB'))
|
||||||
|
|
||||||
|
#matrix.SetImage(Image.open(img_path).convert('RGB'))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(0.1)
|
||||||
|
ntstam = os.stat(img_path).st_mtime
|
||||||
|
#si el bufer fue modificado, lo carga en la pantalla led
|
||||||
|
if ntstam > tstam:
|
||||||
|
with Image.open(img_path) as image:
|
||||||
|
matrix.SetImage(image.convert('RGB'))
|
||||||
|
tstam = ntstam
|
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=LED Rendering Service
|
||||||
|
After=local-fs.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
CPUAffinity=3
|
||||||
|
Nice=-20
|
||||||
|
WorkingDirectory=/srv
|
||||||
|
ExecStart=/usr/bin/python3 /srv/subsystem/led-driver.py
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -1,15 +0,0 @@
|
||||||
# The *.cpp files are included in the distribution; this is only needed when
|
|
||||||
# working on the pyx files.
|
|
||||||
#
|
|
||||||
# Please check in modified *.cpp files with distribution to not require cython
|
|
||||||
# to be installed on the users' machine.
|
|
||||||
# for python3: make PYTHON=$(which python3) CYTHON=$(which cython3)
|
|
||||||
CYTHON ?= cython
|
|
||||||
|
|
||||||
all : core.cpp graphics.cpp
|
|
||||||
|
|
||||||
%.cpp : %.pyx
|
|
||||||
$(CYTHON) --cplus -o $@ $^
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf core.cpp graphics.cpp
|
|
|
@ -1,7 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__version__ = "0.0.1"
|
|
||||||
__author__ = "Christoph Friedrich <christoph.friedrich@vonaffenfels.de>"
|
|
||||||
|
|
||||||
from .core import RGBMatrix, FrameCanvas, RGBMatrixOptions
|
|
|
@ -1,24 +0,0 @@
|
||||||
cimport cppinc
|
|
||||||
|
|
||||||
cdef class Canvas:
|
|
||||||
cdef cppinc.Canvas *__getCanvas(self) except +
|
|
||||||
|
|
||||||
cdef class FrameCanvas(Canvas):
|
|
||||||
cdef cppinc.FrameCanvas *__canvas
|
|
||||||
|
|
||||||
cdef class RGBMatrix(Canvas):
|
|
||||||
cdef cppinc.RGBMatrix *__matrix
|
|
||||||
|
|
||||||
cdef class RGBMatrixOptions:
|
|
||||||
cdef cppinc.Options __options
|
|
||||||
cdef cppinc.RuntimeOptions __runtime_options
|
|
||||||
# Must keep a reference to the encoded bytes for the strings,
|
|
||||||
# otherwise, when the Options struct is used, it will be garbage collected
|
|
||||||
cdef bytes __py_encoded_hardware_mapping
|
|
||||||
cdef bytes __py_encoded_led_rgb_sequence
|
|
||||||
cdef bytes __py_encoded_pixel_mapper_config
|
|
||||||
cdef bytes __py_encoded_panel_type
|
|
||||||
|
|
||||||
# Local Variables:
|
|
||||||
# mode: python
|
|
||||||
# End:
|
|
|
@ -1,272 +0,0 @@
|
||||||
# distutils: language = c++
|
|
||||||
|
|
||||||
from libcpp cimport bool
|
|
||||||
from libc.stdint cimport uint8_t, uint32_t, uintptr_t
|
|
||||||
from PIL import Image
|
|
||||||
import cython
|
|
||||||
|
|
||||||
cdef class Canvas:
|
|
||||||
cdef cppinc.Canvas* __getCanvas(self) except +:
|
|
||||||
raise Exception("Not implemented")
|
|
||||||
|
|
||||||
def SetImage(self, image, int offset_x = 0, int offset_y = 0, unsafe=True):
|
|
||||||
if (image.mode != "RGB"):
|
|
||||||
raise Exception("Currently, only RGB mode is supported for SetImage(). Please create images with mode 'RGB' or convert first with image = image.convert('RGB'). Pull requests to support more modes natively are also welcome :)")
|
|
||||||
|
|
||||||
if unsafe:
|
|
||||||
#In unsafe mode we directly access the underlying PIL image array
|
|
||||||
#in cython, which is considered unsafe pointer accecss,
|
|
||||||
#however it's super fast and seems to work fine
|
|
||||||
#https://groups.google.com/forum/#!topic/cython-users/Dc1ft5W6KM4
|
|
||||||
img_width, img_height = image.size
|
|
||||||
self.SetPixelsPillow(offset_x, offset_y, img_width, img_height, image)
|
|
||||||
else:
|
|
||||||
# First implementation of a SetImage(). OPTIMIZE_ME: A more native
|
|
||||||
# implementation that directly reads the buffer and calls the underlying
|
|
||||||
# C functions can certainly be faster.
|
|
||||||
img_width, img_height = image.size
|
|
||||||
pixels = image.load()
|
|
||||||
for x in range(max(0, -offset_x), min(img_width, self.width - offset_x)):
|
|
||||||
for y in range(max(0, -offset_y), min(img_height, self.height - offset_y)):
|
|
||||||
(r, g, b) = pixels[x, y]
|
|
||||||
self.SetPixel(x + offset_x, y + offset_y, r, g, b)
|
|
||||||
|
|
||||||
@cython.boundscheck(False)
|
|
||||||
@cython.wraparound(False)
|
|
||||||
def SetPixelsPillow(self, int xstart, int ystart, int width, int height, image):
|
|
||||||
cdef cppinc.FrameCanvas* my_canvas = <cppinc.FrameCanvas*>self.__getCanvas()
|
|
||||||
cdef int frame_width = my_canvas.width()
|
|
||||||
cdef int frame_height = my_canvas.height()
|
|
||||||
cdef int row, col
|
|
||||||
cdef uint8_t r, g, b
|
|
||||||
cdef uint32_t **image_ptr
|
|
||||||
cdef uint32_t pixel
|
|
||||||
image.load()
|
|
||||||
ptr_tmp = dict(image.im.unsafe_ptrs)['image32']
|
|
||||||
image_ptr = (<uint32_t **>(<uintptr_t>ptr_tmp))
|
|
||||||
|
|
||||||
for col in range(max(0, -xstart), min(width, frame_width - xstart)):
|
|
||||||
for row in range(max(0, -ystart), min(height, frame_height - ystart)):
|
|
||||||
pixel = image_ptr[row][col]
|
|
||||||
r = (pixel ) & 0xFF
|
|
||||||
g = (pixel >> 8) & 0xFF
|
|
||||||
b = (pixel >> 16) & 0xFF
|
|
||||||
my_canvas.SetPixel(xstart+col, ystart+row, r, g, b)
|
|
||||||
|
|
||||||
cdef class FrameCanvas(Canvas):
|
|
||||||
def __dealloc__(self):
|
|
||||||
if <void*>self.__canvas != NULL:
|
|
||||||
self.__canvas = NULL
|
|
||||||
|
|
||||||
cdef cppinc.Canvas* __getCanvas(self) except *:
|
|
||||||
if <void*>self.__canvas != NULL:
|
|
||||||
return self.__canvas
|
|
||||||
raise Exception("Canvas was destroyed or not initialized, you cannot use this object anymore")
|
|
||||||
|
|
||||||
def Fill(self, uint8_t red, uint8_t green, uint8_t blue):
|
|
||||||
(<cppinc.FrameCanvas*>self.__getCanvas()).Fill(red, green, blue)
|
|
||||||
|
|
||||||
def Clear(self):
|
|
||||||
(<cppinc.FrameCanvas*>self.__getCanvas()).Clear()
|
|
||||||
|
|
||||||
def SetPixel(self, int x, int y, uint8_t red, uint8_t green, uint8_t blue):
|
|
||||||
(<cppinc.FrameCanvas*>self.__getCanvas()).SetPixel(x, y, red, green, blue)
|
|
||||||
|
|
||||||
|
|
||||||
property width:
|
|
||||||
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).width()
|
|
||||||
|
|
||||||
property height:
|
|
||||||
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).height()
|
|
||||||
|
|
||||||
property pwmBits:
|
|
||||||
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).pwmbits()
|
|
||||||
def __set__(self, pwmBits): (<cppinc.FrameCanvas*>self.__getCanvas()).SetPWMBits(pwmBits)
|
|
||||||
|
|
||||||
property brightness:
|
|
||||||
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).brightness()
|
|
||||||
def __set__(self, val): (<cppinc.FrameCanvas*>self.__getCanvas()).SetBrightness(val)
|
|
||||||
|
|
||||||
|
|
||||||
cdef class RGBMatrixOptions:
|
|
||||||
def __cinit__(self):
|
|
||||||
self.__options = cppinc.Options()
|
|
||||||
self.__runtime_options = cppinc.RuntimeOptions()
|
|
||||||
|
|
||||||
# RGBMatrix::Options properties
|
|
||||||
property hardware_mapping:
|
|
||||||
def __get__(self): return self.__options.hardware_mapping
|
|
||||||
def __set__(self, value):
|
|
||||||
self.__py_encoded_hardware_mapping = value.encode('utf-8')
|
|
||||||
self.__options.hardware_mapping = self.__py_encoded_hardware_mapping
|
|
||||||
|
|
||||||
property rows:
|
|
||||||
def __get__(self): return self.__options.rows
|
|
||||||
def __set__(self, uint8_t value): self.__options.rows = value
|
|
||||||
|
|
||||||
property cols:
|
|
||||||
def __get__(self): return self.__options.cols
|
|
||||||
def __set__(self, uint32_t value): self.__options.cols = value
|
|
||||||
|
|
||||||
property chain_length:
|
|
||||||
def __get__(self): return self.__options.chain_length
|
|
||||||
def __set__(self, uint8_t value): self.__options.chain_length = value
|
|
||||||
|
|
||||||
property parallel:
|
|
||||||
def __get__(self): return self.__options.parallel
|
|
||||||
def __set__(self, uint8_t value): self.__options.parallel = value
|
|
||||||
|
|
||||||
property pwm_bits:
|
|
||||||
def __get__(self): return self.__options.pwm_bits
|
|
||||||
def __set__(self, uint8_t value): self.__options.pwm_bits = value
|
|
||||||
|
|
||||||
property pwm_lsb_nanoseconds:
|
|
||||||
def __get__(self): return self.__options.pwm_lsb_nanoseconds
|
|
||||||
def __set__(self, uint32_t value): self.__options.pwm_lsb_nanoseconds = value
|
|
||||||
|
|
||||||
property brightness:
|
|
||||||
def __get__(self): return self.__options.brightness
|
|
||||||
def __set__(self, uint8_t value): self.__options.brightness = value
|
|
||||||
|
|
||||||
property scan_mode:
|
|
||||||
def __get__(self): return self.__options.scan_mode
|
|
||||||
def __set__(self, uint8_t value): self.__options.scan_mode = value
|
|
||||||
|
|
||||||
property multiplexing:
|
|
||||||
def __get__(self): return self.__options.multiplexing
|
|
||||||
def __set__(self, uint8_t value): self.__options.multiplexing = value
|
|
||||||
|
|
||||||
property row_address_type:
|
|
||||||
def __get__(self): return self.__options.row_address_type
|
|
||||||
def __set__(self, uint8_t value): self.__options.row_address_type = value
|
|
||||||
|
|
||||||
property disable_hardware_pulsing:
|
|
||||||
def __get__(self): return self.__options.disable_hardware_pulsing
|
|
||||||
def __set__(self, value): self.__options.disable_hardware_pulsing = value
|
|
||||||
|
|
||||||
property show_refresh_rate:
|
|
||||||
def __get__(self): return self.__options.show_refresh_rate
|
|
||||||
def __set__(self, value): self.__options.show_refresh_rate = value
|
|
||||||
|
|
||||||
property inverse_colors:
|
|
||||||
def __get__(self): return self.__options.inverse_colors
|
|
||||||
def __set__(self, value): self.__options.inverse_colors = value
|
|
||||||
|
|
||||||
property led_rgb_sequence:
|
|
||||||
def __get__(self): return self.__options.led_rgb_sequence
|
|
||||||
def __set__(self, value):
|
|
||||||
self.__py_encoded_led_rgb_sequence = value.encode('utf-8')
|
|
||||||
self.__options.led_rgb_sequence = self.__py_encoded_led_rgb_sequence
|
|
||||||
|
|
||||||
property pixel_mapper_config:
|
|
||||||
def __get__(self): return self.__options.pixel_mapper_config
|
|
||||||
def __set__(self, value):
|
|
||||||
self.__py_encoded_pixel_mapper_config = value.encode('utf-8')
|
|
||||||
self.__options.pixel_mapper_config = self.__py_encoded_pixel_mapper_config
|
|
||||||
|
|
||||||
property panel_type:
|
|
||||||
def __get__(self): return self.__options.panel_type
|
|
||||||
def __set__(self, value):
|
|
||||||
self.__py_encoded_panel_type = value.encode('utf-8')
|
|
||||||
self.__options.panel_type = self.__py_encoded_panel_type
|
|
||||||
|
|
||||||
property pwm_dither_bits:
|
|
||||||
def __get__(self): return self.__options.pwm_dither_bits
|
|
||||||
def __set__(self, uint8_t value): self.__options.pwm_dither_bits = value
|
|
||||||
|
|
||||||
property limit_refresh_rate_hz:
|
|
||||||
def __get__(self): return self.__options.limit_refresh_rate_hz
|
|
||||||
def __set__(self, value): self.__options.limit_refresh_rate_hz = value
|
|
||||||
|
|
||||||
|
|
||||||
# RuntimeOptions properties
|
|
||||||
|
|
||||||
property gpio_slowdown:
|
|
||||||
def __get__(self): return self.__runtime_options.gpio_slowdown
|
|
||||||
def __set__(self, uint8_t value): self.__runtime_options.gpio_slowdown = value
|
|
||||||
|
|
||||||
property daemon:
|
|
||||||
def __get__(self): return self.__runtime_options.daemon
|
|
||||||
def __set__(self, uint8_t value): self.__runtime_options.daemon = value
|
|
||||||
|
|
||||||
property drop_privileges:
|
|
||||||
def __get__(self): return self.__runtime_options.drop_privileges
|
|
||||||
def __set__(self, uint8_t value): self.__runtime_options.drop_privileges = value
|
|
||||||
|
|
||||||
cdef class RGBMatrix(Canvas):
|
|
||||||
def __cinit__(self, int rows = 0, int chains = 0, int parallel = 0,
|
|
||||||
RGBMatrixOptions options = None):
|
|
||||||
|
|
||||||
# If RGBMatrixOptions not provided, create defaults and set any optional
|
|
||||||
# parameters supplied
|
|
||||||
if options == None:
|
|
||||||
options = RGBMatrixOptions()
|
|
||||||
|
|
||||||
if rows > 0:
|
|
||||||
options.rows = rows
|
|
||||||
if chains > 0:
|
|
||||||
options.chain_length = chains
|
|
||||||
if parallel > 0:
|
|
||||||
options.parallel = parallel
|
|
||||||
|
|
||||||
self.__matrix = cppinc.CreateMatrixFromOptions(options.__options,
|
|
||||||
options.__runtime_options)
|
|
||||||
|
|
||||||
def __dealloc__(self):
|
|
||||||
self.__matrix.Clear()
|
|
||||||
del self.__matrix
|
|
||||||
|
|
||||||
cdef cppinc.Canvas* __getCanvas(self) except *:
|
|
||||||
if <void*>self.__matrix != NULL:
|
|
||||||
return self.__matrix
|
|
||||||
raise Exception("Canvas was destroyed or not initialized, you cannot use this object anymore")
|
|
||||||
|
|
||||||
def Fill(self, uint8_t red, uint8_t green, uint8_t blue):
|
|
||||||
self.__matrix.Fill(red, green, blue)
|
|
||||||
|
|
||||||
def SetPixel(self, int x, int y, uint8_t red, uint8_t green, uint8_t blue):
|
|
||||||
self.__matrix.SetPixel(x, y, red, green, blue)
|
|
||||||
|
|
||||||
def Clear(self):
|
|
||||||
self.__matrix.Clear()
|
|
||||||
|
|
||||||
def CreateFrameCanvas(self):
|
|
||||||
return __createFrameCanvas(self.__matrix.CreateFrameCanvas())
|
|
||||||
|
|
||||||
# The optional "framerate_fraction" parameter allows to choose which
|
|
||||||
# multiple of the global frame-count to use. So it slows down your animation
|
|
||||||
# to an exact integer fraction of the refresh rate.
|
|
||||||
# Default is 1, so immediately next available frame.
|
|
||||||
# (Say you have 140Hz refresh rate, then a value of 5 would give you an
|
|
||||||
# 28Hz animation, nicely locked to the refresh-rate).
|
|
||||||
# If you combine this with RGBMatrixOptions.limit_refresh_rate_hz you can create
|
|
||||||
# time-correct animations.
|
|
||||||
def SwapOnVSync(self, FrameCanvas newFrame, uint8_t framerate_fraction = 1):
|
|
||||||
return __createFrameCanvas(self.__matrix.SwapOnVSync(newFrame.__canvas, framerate_fraction))
|
|
||||||
|
|
||||||
property luminanceCorrect:
|
|
||||||
def __get__(self): return self.__matrix.luminance_correct()
|
|
||||||
def __set__(self, luminanceCorrect): self.__matrix.set_luminance_correct(luminanceCorrect)
|
|
||||||
|
|
||||||
property pwmBits:
|
|
||||||
def __get__(self): return self.__matrix.pwmbits()
|
|
||||||
def __set__(self, pwmBits): self.__matrix.SetPWMBits(pwmBits)
|
|
||||||
|
|
||||||
property brightness:
|
|
||||||
def __get__(self): return self.__matrix.brightness()
|
|
||||||
def __set__(self, brightness): self.__matrix.SetBrightness(brightness)
|
|
||||||
|
|
||||||
property height:
|
|
||||||
def __get__(self): return self.__matrix.height()
|
|
||||||
|
|
||||||
property width:
|
|
||||||
def __get__(self): return self.__matrix.width()
|
|
||||||
|
|
||||||
cdef __createFrameCanvas(cppinc.FrameCanvas* newCanvas):
|
|
||||||
canvas = FrameCanvas()
|
|
||||||
canvas.__canvas = newCanvas
|
|
||||||
return canvas
|
|
||||||
|
|
||||||
# Local Variables:
|
|
||||||
# mode: python
|
|
||||||
# End:
|
|
|
@ -1,88 +0,0 @@
|
||||||
from libcpp cimport bool
|
|
||||||
from libc.stdint cimport uint8_t, uint32_t
|
|
||||||
|
|
||||||
########################
|
|
||||||
### External classes ###
|
|
||||||
########################
|
|
||||||
|
|
||||||
cdef extern from "canvas.h" namespace "rgb_matrix":
|
|
||||||
cdef cppclass Canvas:
|
|
||||||
int width()
|
|
||||||
int height()
|
|
||||||
void SetPixel(int, int, uint8_t, uint8_t, uint8_t) nogil
|
|
||||||
void Clear() nogil
|
|
||||||
void Fill(uint8_t, uint8_t, uint8_t) nogil
|
|
||||||
|
|
||||||
cdef extern from "led-matrix.h" namespace "rgb_matrix":
|
|
||||||
cdef cppclass RGBMatrix(Canvas):
|
|
||||||
bool SetPWMBits(uint8_t)
|
|
||||||
uint8_t pwmbits()
|
|
||||||
void set_luminance_correct(bool)
|
|
||||||
bool luminance_correct()
|
|
||||||
void SetBrightness(uint8_t)
|
|
||||||
uint8_t brightness()
|
|
||||||
FrameCanvas *CreateFrameCanvas()
|
|
||||||
FrameCanvas *SwapOnVSync(FrameCanvas*, uint8_t)
|
|
||||||
|
|
||||||
cdef cppclass FrameCanvas(Canvas):
|
|
||||||
bool SetPWMBits(uint8_t)
|
|
||||||
uint8_t pwmbits()
|
|
||||||
void SetBrightness(uint8_t)
|
|
||||||
uint8_t brightness()
|
|
||||||
|
|
||||||
struct RuntimeOptions:
|
|
||||||
RuntimeOptions() except +
|
|
||||||
int gpio_slowdown
|
|
||||||
int daemon
|
|
||||||
int drop_privileges
|
|
||||||
|
|
||||||
|
|
||||||
RGBMatrix *CreateMatrixFromOptions(Options &options, RuntimeOptions runtime_options)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cdef extern from "led-matrix.h" namespace "rgb_matrix::RGBMatrix":
|
|
||||||
cdef struct Options:
|
|
||||||
Options() except +
|
|
||||||
|
|
||||||
const char *hardware_mapping
|
|
||||||
|
|
||||||
int rows
|
|
||||||
int cols
|
|
||||||
int chain_length
|
|
||||||
int parallel
|
|
||||||
int pwm_bits
|
|
||||||
int pwm_lsb_nanoseconds
|
|
||||||
int brightness
|
|
||||||
int scan_mode
|
|
||||||
int row_address_type
|
|
||||||
int multiplexing
|
|
||||||
int pwm_dither_bits
|
|
||||||
int limit_refresh_rate_hz
|
|
||||||
|
|
||||||
bool disable_hardware_pulsing
|
|
||||||
bool show_refresh_rate
|
|
||||||
bool inverse_colors
|
|
||||||
|
|
||||||
const char *led_rgb_sequence
|
|
||||||
const char *pixel_mapper_config
|
|
||||||
const char *panel_type
|
|
||||||
|
|
||||||
cdef extern from "graphics.h" namespace "rgb_matrix":
|
|
||||||
cdef struct Color:
|
|
||||||
Color(uint8_t, uint8_t, uint8_t) except +
|
|
||||||
uint8_t r
|
|
||||||
uint8_t g
|
|
||||||
uint8_t b
|
|
||||||
|
|
||||||
cdef cppclass Font:
|
|
||||||
Font() except +
|
|
||||||
bool LoadFont(const char*)
|
|
||||||
int height()
|
|
||||||
int baseline()
|
|
||||||
int CharacterWidth(uint32_t)
|
|
||||||
int DrawGlyph(Canvas*, int, int, const Color, uint32_t);
|
|
||||||
|
|
||||||
cdef int DrawText(Canvas*, const Font, int, int, const Color, const char*)
|
|
||||||
cdef void DrawCircle(Canvas*, int, int, int, const Color)
|
|
||||||
cdef void DrawLine(Canvas*, int, int, int, int, const Color)
|
|
|
@ -1,11 +0,0 @@
|
||||||
cimport cppinc
|
|
||||||
|
|
||||||
cdef class Color:
|
|
||||||
cdef cppinc.Color __color
|
|
||||||
|
|
||||||
cdef class Font:
|
|
||||||
cdef cppinc.Font __font
|
|
||||||
|
|
||||||
# Local Variables:
|
|
||||||
# mode: python
|
|
||||||
# End:
|
|
|
@ -1,54 +0,0 @@
|
||||||
# distutils: language = c++
|
|
||||||
|
|
||||||
from libcpp cimport bool
|
|
||||||
from libc.stdint cimport uint8_t, uint32_t
|
|
||||||
|
|
||||||
cimport core
|
|
||||||
|
|
||||||
cdef class Color:
|
|
||||||
def __init__(self, uint8_t red = 0, uint8_t green = 0, uint8_t blue = 0):
|
|
||||||
self.__color.r = red
|
|
||||||
self.__color.g = green
|
|
||||||
self.__color.b = blue
|
|
||||||
|
|
||||||
property red:
|
|
||||||
def __get__(self): return self.__color.r
|
|
||||||
def __set__(self, uint8_t value): self.__color.r = value
|
|
||||||
|
|
||||||
property green:
|
|
||||||
def __get__(self): return self.__color.g
|
|
||||||
def __set__(self, uint8_t value): self.__color.g = value
|
|
||||||
|
|
||||||
property blue:
|
|
||||||
def __get__(self): return self.__color.b
|
|
||||||
def __set__(self, uint8_t value): self.__color.b = value
|
|
||||||
|
|
||||||
cdef class Font:
|
|
||||||
def CharacterWidth(self, uint32_t char):
|
|
||||||
return self.__font.CharacterWidth(char)
|
|
||||||
|
|
||||||
def LoadFont(self, file):
|
|
||||||
if (not self.__font.LoadFont(file.encode('utf-8'))):
|
|
||||||
raise Exception("Couldn't load font " + file)
|
|
||||||
|
|
||||||
def DrawGlyph(self, core.Canvas c, int x, int y, Color color, uint32_t char):
|
|
||||||
return self.__font.DrawGlyph(c.__getCanvas(), x, y, color.__color, char)
|
|
||||||
|
|
||||||
property height:
|
|
||||||
def __get__(self): return self.__font.height()
|
|
||||||
|
|
||||||
property baseline:
|
|
||||||
def __get__(self): return self.__font.baseline()
|
|
||||||
|
|
||||||
def DrawText(core.Canvas c, Font f, int x, int y, Color color, text):
|
|
||||||
return cppinc.DrawText(c.__getCanvas(), f.__font, x, y, color.__color, text.encode('utf-8'))
|
|
||||||
|
|
||||||
def DrawCircle(core.Canvas c, int x, int y, int r, Color color):
|
|
||||||
cppinc.DrawCircle(c.__getCanvas(), x, y, r, color.__color)
|
|
||||||
|
|
||||||
def DrawLine(core.Canvas c, int x1, int y1, int x2, int y2, Color color):
|
|
||||||
cppinc.DrawLine(c.__getCanvas(), x1, y1, x2, y2, color.__color)
|
|
||||||
|
|
||||||
# Local Variables:
|
|
||||||
# mode: python
|
|
||||||
# End:
|
|
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 293 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 579 KiB |
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1,62 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from rgbmatrix import RGBMatrix, RGBMatrixOptions
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
sys.exit("Require a gif argument")
|
|
||||||
else:
|
|
||||||
image_file = sys.argv[1]
|
|
||||||
|
|
||||||
gif = Image.open(image_file)
|
|
||||||
|
|
||||||
try:
|
|
||||||
num_frames = gif.n_frames
|
|
||||||
except Exception:
|
|
||||||
sys.exit("provided image is not a gif")
|
|
||||||
|
|
||||||
|
|
||||||
# Configuration for the matrix
|
|
||||||
options = RGBMatrixOptions()
|
|
||||||
options.rows = 40
|
|
||||||
options.cols = 80
|
|
||||||
options.chain_length = 3
|
|
||||||
options.parallel = 3
|
|
||||||
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
|
|
||||||
options.multiplexing = 1
|
|
||||||
options.brightness = 10
|
|
||||||
|
|
||||||
matrix = RGBMatrix(options = options)
|
|
||||||
|
|
||||||
# Preprocess the gifs frames into canvases to improve playback performance
|
|
||||||
canvases = []
|
|
||||||
print("Preprocessing gif, this may take a moment depending on the size of the gif...")
|
|
||||||
for frame_index in range(0, num_frames):
|
|
||||||
gif.seek(frame_index)
|
|
||||||
# must copy the frame out of the gif, since thumbnail() modifies the image in-place
|
|
||||||
frame = gif.copy()
|
|
||||||
frame.thumbnail((matrix.width, matrix.height), Image.ANTIALIAS)
|
|
||||||
canvas = matrix.CreateFrameCanvas()
|
|
||||||
canvas.SetImage(frame.convert("RGB"))
|
|
||||||
canvases.append(canvas)
|
|
||||||
# Close the gif file to save memory now that we have copied out all of the frames
|
|
||||||
gif.close()
|
|
||||||
|
|
||||||
print("Completed Preprocessing, displaying gif")
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("Press CTRL-C to stop.")
|
|
||||||
|
|
||||||
# Infinitely loop through the gif
|
|
||||||
cur_frame = 0
|
|
||||||
while(True):
|
|
||||||
matrix.SwapOnVSync(canvases[cur_frame], framerate_fraction=30)
|
|
||||||
if cur_frame == num_frames - 1:
|
|
||||||
cur_frame = 0
|
|
||||||
else:
|
|
||||||
cur_frame += 1
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(0)
|
|
|
@ -1,32 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
from samplebase import SampleBase
|
|
||||||
from rgbmatrix import graphics
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class GraphicsTest(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(GraphicsTest, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
canvas = self.matrix
|
|
||||||
font = graphics.Font()
|
|
||||||
font.LoadFont("../../../fonts/7x13.bdf")
|
|
||||||
|
|
||||||
red = graphics.Color(255, 0, 0)
|
|
||||||
graphics.DrawLine(canvas, 5, 5, 22, 13, red)
|
|
||||||
|
|
||||||
green = graphics.Color(0, 255, 0)
|
|
||||||
graphics.DrawCircle(canvas, 15, 15, 10, green)
|
|
||||||
|
|
||||||
blue = graphics.Color(0, 0, 255)
|
|
||||||
graphics.DrawText(canvas, font, 2, 10, blue, "Text")
|
|
||||||
|
|
||||||
time.sleep(10) # show display for 10 seconds before exit
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
if __name__ == "__main__":
|
|
||||||
graphics_test = GraphicsTest()
|
|
||||||
if (not graphics_test.process()):
|
|
||||||
graphics_test.print_help()
|
|
|
@ -1,39 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
from samplebase import SampleBase
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class GrayscaleBlock(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(GrayscaleBlock, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
sub_blocks = 16
|
|
||||||
width = self.matrix.width
|
|
||||||
height = self.matrix.height
|
|
||||||
x_step = max(1, width / sub_blocks)
|
|
||||||
y_step = max(1, height / sub_blocks)
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
for y in range(0, height):
|
|
||||||
for x in range(0, width):
|
|
||||||
c = sub_blocks * int(y / y_step) + int(x / x_step)
|
|
||||||
if count % 4 == 0:
|
|
||||||
self.matrix.SetPixel(x, y, c, c, c)
|
|
||||||
elif count % 4 == 1:
|
|
||||||
self.matrix.SetPixel(x, y, c, 0, 0)
|
|
||||||
elif count % 4 == 2:
|
|
||||||
self.matrix.SetPixel(x, y, 0, c, 0)
|
|
||||||
elif count % 4 == 3:
|
|
||||||
self.matrix.SetPixel(x, y, 0, 0, c)
|
|
||||||
|
|
||||||
count += 1
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
if __name__ == "__main__":
|
|
||||||
grayscale_block = GrayscaleBlock()
|
|
||||||
if (not grayscale_block.process()):
|
|
||||||
grayscale_block.print_help()
|
|
|
@ -1,48 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# (This is an example similar to an example from the Adafruit fork
|
|
||||||
# to show the similarities. Most important difference currently is, that
|
|
||||||
# this library wants RGB mode.)
|
|
||||||
#
|
|
||||||
# A more complex RGBMatrix example works with the Python Imaging Library,
|
|
||||||
# demonstrating a few graphics primitives and image loading.
|
|
||||||
# Note that PIL graphics do not have an immediate effect on the display --
|
|
||||||
# image is drawn into a separate buffer, which is then copied to the matrix
|
|
||||||
# using the SetImage() function (see examples below).
|
|
||||||
# Requires rgbmatrix.so present in the same directory.
|
|
||||||
|
|
||||||
# PIL Image module (create or load images) is explained here:
|
|
||||||
# http://effbot.org/imagingbook/image.htm
|
|
||||||
# PIL ImageDraw module (draw shapes to images) explained here:
|
|
||||||
# http://effbot.org/imagingbook/imagedraw.htm
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from PIL import ImageDraw
|
|
||||||
import time
|
|
||||||
from rgbmatrix import RGBMatrix, RGBMatrixOptions
|
|
||||||
|
|
||||||
# Configuration for the matrix
|
|
||||||
options = RGBMatrixOptions()
|
|
||||||
options.rows = 32
|
|
||||||
options.chain_length = 1
|
|
||||||
options.parallel = 1
|
|
||||||
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
|
|
||||||
|
|
||||||
matrix = RGBMatrix(options = options)
|
|
||||||
|
|
||||||
# RGB example w/graphics prims.
|
|
||||||
# Note, only "RGB" mode is supported currently.
|
|
||||||
image = Image.new("RGB", (32, 32)) # Can be larger than matrix if wanted!!
|
|
||||||
draw = ImageDraw.Draw(image) # Declare Draw instance before prims
|
|
||||||
# Draw some shapes into image (no immediate effect on matrix)...
|
|
||||||
draw.rectangle((0, 0, 31, 31), fill=(0, 0, 0), outline=(0, 0, 255))
|
|
||||||
draw.line((0, 0, 31, 31), fill=(255, 0, 0))
|
|
||||||
draw.line((0, 31, 31, 0), fill=(0, 255, 0))
|
|
||||||
|
|
||||||
# Then scroll image across matrix...
|
|
||||||
for n in range(-32, 33): # Start off top-left, move off bottom-right
|
|
||||||
matrix.Clear()
|
|
||||||
matrix.SetImage(image, n, n)
|
|
||||||
time.sleep(0.05)
|
|
||||||
|
|
||||||
matrix.Clear()
|
|
|
@ -1,40 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import time
|
|
||||||
from samplebase import SampleBase
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
|
|
||||||
class ImageScroller(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(ImageScroller, self).__init__(*args, **kwargs)
|
|
||||||
self.parser.add_argument("-i", "--image", help="The image to display", default="../../../examples-api-use/runtext.ppm")
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not 'image' in self.__dict__:
|
|
||||||
self.image = Image.open(self.args.image).convert('RGB')
|
|
||||||
self.image.resize((self.matrix.width, self.matrix.height), Image.ANTIALIAS)
|
|
||||||
|
|
||||||
double_buffer = self.matrix.CreateFrameCanvas()
|
|
||||||
img_width, img_height = self.image.size
|
|
||||||
|
|
||||||
# let's scroll
|
|
||||||
xpos = 0
|
|
||||||
while True:
|
|
||||||
xpos += 1
|
|
||||||
if (xpos > img_width):
|
|
||||||
xpos = 0
|
|
||||||
|
|
||||||
double_buffer.SetImage(self.image, -xpos)
|
|
||||||
double_buffer.SetImage(self.image, -xpos + img_width)
|
|
||||||
|
|
||||||
double_buffer = self.matrix.SwapOnVSync(double_buffer)
|
|
||||||
time.sleep(0.01)
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
# e.g. call with
|
|
||||||
# sudo ./image-scroller.py --chain=4
|
|
||||||
# if you have a chain of four
|
|
||||||
if __name__ == "__main__":
|
|
||||||
image_scroller = ImageScroller()
|
|
||||||
if (not image_scroller.process()):
|
|
||||||
image_scroller.print_help()
|
|
|
@ -1,43 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from rgbmatrix import RGBMatrix, RGBMatrixOptions
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
sys.exit("Require an image argument")
|
|
||||||
else:
|
|
||||||
image_file = sys.argv[1]
|
|
||||||
|
|
||||||
image = Image.open(image_file)
|
|
||||||
|
|
||||||
# Configuration for the matrix
|
|
||||||
options = RGBMatrixOptions()
|
|
||||||
options.rows = 40
|
|
||||||
options.cols = 80
|
|
||||||
options.chain_length = 2
|
|
||||||
options.parallel = 1
|
|
||||||
options.gpio_slowdown = 4
|
|
||||||
#options.row_address_type = 0
|
|
||||||
|
|
||||||
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
|
|
||||||
options.multiplexing = 1
|
|
||||||
options.brightness = 20
|
|
||||||
#options.pwm_lsb_nanoseconds = 300
|
|
||||||
#options.pwm_bits = 11
|
|
||||||
|
|
||||||
|
|
||||||
matrix = RGBMatrix(options = options)
|
|
||||||
|
|
||||||
# Make image fit our screen.
|
|
||||||
image.thumbnail((matrix.width, matrix.height), Image.ANTIALIAS)
|
|
||||||
|
|
||||||
matrix.SetImage(image.convert('RGB'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("Press CTRL-C to stop.")
|
|
||||||
while True:
|
|
||||||
time.sleep(100)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(0)
|
|
Before Width: | Height: | Size: 95 KiB |
|
@ -1,56 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from rgbmatrix import RGBMatrix, RGBMatrixOptions
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
def resize_image(input_path, output_path):
|
|
||||||
try:
|
|
||||||
# Abrir la imagen de entrada
|
|
||||||
image = Image.open(input_path)
|
|
||||||
# Obtener el tamaño original de la imagen
|
|
||||||
original_width, original_height = image.size
|
|
||||||
# Calcular el nuevo alto (mitad del ancho)
|
|
||||||
new_height = original_width // 2
|
|
||||||
# Redimensionar la imagen
|
|
||||||
resized_image = image.resize((original_width, new_height))
|
|
||||||
# Guardar la imagen redimensionada en la carpeta img_processed
|
|
||||||
resized_image.save(output_path)
|
|
||||||
print("Imagen redimensionada y guardada correctamente.")
|
|
||||||
return resized_image
|
|
||||||
except Exception as e:
|
|
||||||
print("Error:", e)
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
sys.exit("Require an image argument")
|
|
||||||
else:
|
|
||||||
image_file = sys.argv[1]
|
|
||||||
image = resize_image(image_file, image_file.replace(".jpeg","_r.jpeg"))
|
|
||||||
|
|
||||||
|
|
||||||
# Configuration for the matrix
|
|
||||||
options = RGBMatrixOptions()
|
|
||||||
options.rows = 40
|
|
||||||
options.cols = 80
|
|
||||||
options.chain_length = 3
|
|
||||||
options.parallel = 3
|
|
||||||
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
|
|
||||||
options.multiplexing = 1
|
|
||||||
options.brightness = 30
|
|
||||||
|
|
||||||
matrix = RGBMatrix(options = options)
|
|
||||||
|
|
||||||
# Make image fit our screen.
|
|
||||||
image.thumbnail((matrix.width, matrix.height), Image.ANTIALIAS)
|
|
||||||
|
|
||||||
matrix.SetImage(image.convert('RGB'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("Press CTRL-C to stop.")
|
|
||||||
while True:
|
|
||||||
time.sleep(100)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(0)
|
|
Before Width: | Height: | Size: 117 KiB |
|
@ -1,36 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
from samplebase import SampleBase
|
|
||||||
|
|
||||||
|
|
||||||
class GrayscaleBlock(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(GrayscaleBlock, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
max_brightness = self.matrix.brightness
|
|
||||||
count = 0
|
|
||||||
c = 255
|
|
||||||
|
|
||||||
while (True):
|
|
||||||
if self.matrix.brightness < 1:
|
|
||||||
self.matrix.brightness = max_brightness
|
|
||||||
count += 1
|
|
||||||
else:
|
|
||||||
self.matrix.brightness -= 1
|
|
||||||
|
|
||||||
if count % 4 == 0:
|
|
||||||
self.matrix.Fill(c, 0, 0)
|
|
||||||
elif count % 4 == 1:
|
|
||||||
self.matrix.Fill(0, c, 0)
|
|
||||||
elif count % 4 == 2:
|
|
||||||
self.matrix.Fill(0, 0, c)
|
|
||||||
elif count % 4 == 3:
|
|
||||||
self.matrix.Fill(c, c, c)
|
|
||||||
|
|
||||||
self.usleep(20 * 1000)
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
if __name__ == "__main__":
|
|
||||||
grayscale_block = GrayscaleBlock()
|
|
||||||
if (not grayscale_block.process()):
|
|
||||||
grayscale_block.print_help()
|
|
|
@ -1,42 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
from samplebase import SampleBase
|
|
||||||
|
|
||||||
|
|
||||||
class PulsingColors(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(PulsingColors, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.offscreen_canvas = self.matrix.CreateFrameCanvas()
|
|
||||||
continuum = 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
self.usleep(5 * 1000)
|
|
||||||
continuum += 1
|
|
||||||
continuum %= 3 * 255
|
|
||||||
|
|
||||||
red = 0
|
|
||||||
green = 0
|
|
||||||
blue = 0
|
|
||||||
|
|
||||||
if continuum <= 255:
|
|
||||||
c = continuum
|
|
||||||
blue = 255 - c
|
|
||||||
red = c
|
|
||||||
elif continuum > 255 and continuum <= 511:
|
|
||||||
c = continuum - 256
|
|
||||||
red = 255 - c
|
|
||||||
green = c
|
|
||||||
else:
|
|
||||||
c = continuum - 512
|
|
||||||
green = 255 - c
|
|
||||||
blue = c
|
|
||||||
|
|
||||||
self.offscreen_canvas.Fill(red, green, blue)
|
|
||||||
self.offscreen_canvas = self.matrix.SwapOnVSync(self.offscreen_canvas)
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
if __name__ == "__main__":
|
|
||||||
pulsing_colors = PulsingColors()
|
|
||||||
if (not pulsing_colors.process()):
|
|
||||||
pulsing_colors.print_help()
|
|
|
@ -1,72 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
from samplebase import SampleBase
|
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
def scale_col(val, lo, hi):
|
|
||||||
if val < lo:
|
|
||||||
return 0
|
|
||||||
if val > hi:
|
|
||||||
return 255
|
|
||||||
return 255 * (val - lo) / (hi - lo)
|
|
||||||
|
|
||||||
|
|
||||||
def rotate(x, y, sin, cos):
|
|
||||||
return x * cos - y * sin, x * sin + y * cos
|
|
||||||
|
|
||||||
|
|
||||||
class RotatingBlockGenerator(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(RotatingBlockGenerator, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
cent_x = self.matrix.width / 2
|
|
||||||
cent_y = self.matrix.height / 2
|
|
||||||
|
|
||||||
rotate_square = min(self.matrix.width, self.matrix.height) * 1.41
|
|
||||||
min_rotate = cent_x - rotate_square / 2
|
|
||||||
max_rotate = cent_x + rotate_square / 2
|
|
||||||
|
|
||||||
display_square = min(self.matrix.width, self.matrix.height) * 0.7
|
|
||||||
min_display = cent_x - display_square / 2
|
|
||||||
max_display = cent_x + display_square / 2
|
|
||||||
|
|
||||||
deg_to_rad = 2 * 3.14159265 / 360
|
|
||||||
rotation = 0
|
|
||||||
|
|
||||||
# Pre calculate colors
|
|
||||||
col_table = []
|
|
||||||
for x in range(int(min_rotate), int(max_rotate)):
|
|
||||||
col_table.insert(x, scale_col(x, min_display, max_display))
|
|
||||||
|
|
||||||
offset_canvas = self.matrix.CreateFrameCanvas()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
rotation += 1
|
|
||||||
rotation %= 360
|
|
||||||
|
|
||||||
# calculate sin and cos once for each frame
|
|
||||||
angle = rotation * deg_to_rad
|
|
||||||
sin = math.sin(angle)
|
|
||||||
cos = math.cos(angle)
|
|
||||||
|
|
||||||
for x in range(int(min_rotate), int(max_rotate)):
|
|
||||||
for y in range(int(min_rotate), int(max_rotate)):
|
|
||||||
# Our rotate center is always offset by cent_x
|
|
||||||
rot_x, rot_y = rotate(x - cent_x, y - cent_x, sin, cos)
|
|
||||||
|
|
||||||
if x >= min_display and x < max_display and y >= min_display and y < max_display:
|
|
||||||
x_col = col_table[x]
|
|
||||||
y_col = col_table[y]
|
|
||||||
offset_canvas.SetPixel(rot_x + cent_x, rot_y + cent_y, x_col, 255 - y_col, y_col)
|
|
||||||
else:
|
|
||||||
offset_canvas.SetPixel(rot_x + cent_x, rot_y + cent_y, 0, 0, 0)
|
|
||||||
|
|
||||||
offset_canvas = self.matrix.SwapOnVSync(offset_canvas)
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
if __name__ == "__main__":
|
|
||||||
rotating_block_generator = RotatingBlockGenerator()
|
|
||||||
if (not rotating_block_generator.process()):
|
|
||||||
rotating_block_generator.print_help()
|
|
|
@ -1,36 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# Display a runtext with double-buffering.
|
|
||||||
from samplebase import SampleBase
|
|
||||||
from rgbmatrix import graphics
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class RunText(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(RunText, self).__init__(*args, **kwargs)
|
|
||||||
self.parser.add_argument("-t", "--text", help="The text to scroll on the RGB LED panel", default="Hello world!")
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
offscreen_canvas = self.matrix.CreateFrameCanvas()
|
|
||||||
font = graphics.Font()
|
|
||||||
font.LoadFont("../../../fonts/7x13.bdf")
|
|
||||||
textColor = graphics.Color(255, 255, 255)
|
|
||||||
pos = offscreen_canvas.width
|
|
||||||
my_text = self.args.text
|
|
||||||
|
|
||||||
while True:
|
|
||||||
offscreen_canvas.Clear()
|
|
||||||
len = graphics.DrawText(offscreen_canvas, font, pos, 10, textColor, my_text)
|
|
||||||
pos -= 1
|
|
||||||
if (pos + len < 0):
|
|
||||||
pos = offscreen_canvas.width
|
|
||||||
|
|
||||||
time.sleep(0.05)
|
|
||||||
offscreen_canvas = self.matrix.SwapOnVSync(offscreen_canvas)
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
if __name__ == "__main__":
|
|
||||||
run_text = RunText()
|
|
||||||
if (not run_text.process()):
|
|
||||||
run_text.print_help()
|
|
|
@ -1,81 +0,0 @@
|
||||||
import argparse
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/..'))
|
|
||||||
from rgbmatrix import RGBMatrix, RGBMatrixOptions
|
|
||||||
|
|
||||||
|
|
||||||
class SampleBase(object):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.parser = argparse.ArgumentParser()
|
|
||||||
|
|
||||||
self.parser.add_argument("-r", "--led-rows", action="store", help="Display rows. 16 for 16x32, 32 for 32x32. Default: 32", default=32, type=int)
|
|
||||||
self.parser.add_argument("--led-cols", action="store", help="Panel columns. Typically 32 or 64. (Default: 32)", default=32, type=int)
|
|
||||||
self.parser.add_argument("-c", "--led-chain", action="store", help="Daisy-chained boards. Default: 1.", default=1, type=int)
|
|
||||||
self.parser.add_argument("-P", "--led-parallel", action="store", help="For Plus-models or RPi2: parallel chains. 1..3. Default: 1", default=1, type=int)
|
|
||||||
self.parser.add_argument("-p", "--led-pwm-bits", action="store", help="Bits used for PWM. Something between 1..11. Default: 11", default=11, type=int)
|
|
||||||
self.parser.add_argument("-b", "--led-brightness", action="store", help="Sets brightness level. Default: 100. Range: 1..100", default=100, type=int)
|
|
||||||
self.parser.add_argument("-m", "--led-gpio-mapping", help="Hardware Mapping: regular, adafruit-hat, adafruit-hat-pwm" , choices=['regular', 'regular-pi1', 'adafruit-hat', 'adafruit-hat-pwm'], type=str)
|
|
||||||
self.parser.add_argument("--led-scan-mode", action="store", help="Progressive or interlaced scan. 0 Progressive, 1 Interlaced (default)", default=1, choices=range(2), type=int)
|
|
||||||
self.parser.add_argument("--led-pwm-lsb-nanoseconds", action="store", help="Base time-unit for the on-time in the lowest significant bit in nanoseconds. Default: 130", default=130, type=int)
|
|
||||||
self.parser.add_argument("--led-show-refresh", action="store_true", help="Shows the current refresh rate of the LED panel")
|
|
||||||
self.parser.add_argument("--led-slowdown-gpio", action="store", help="Slow down writing to GPIO. Range: 0..4. Default: 1", default=1, type=int)
|
|
||||||
self.parser.add_argument("--led-no-hardware-pulse", action="store", help="Don't use hardware pin-pulse generation")
|
|
||||||
self.parser.add_argument("--led-rgb-sequence", action="store", help="Switch if your matrix has led colors swapped. Default: RGB", default="RGB", type=str)
|
|
||||||
self.parser.add_argument("--led-pixel-mapper", action="store", help="Apply pixel mappers. e.g \"Rotate:90\"", default="", type=str)
|
|
||||||
self.parser.add_argument("--led-row-addr-type", action="store", help="0 = default; 1=AB-addressed panels; 2=row direct; 3=ABC-addressed panels; 4 = ABC Shift + DE direct", default=0, type=int, choices=[0,1,2,3,4])
|
|
||||||
self.parser.add_argument("--led-multiplexing", action="store", help="Multiplexing type: 0=direct; 1=strip; 2=checker; 3=spiral; 4=ZStripe; 5=ZnMirrorZStripe; 6=coreman; 7=Kaler2Scan; 8=ZStripeUneven... (Default: 0)", default=0, type=int)
|
|
||||||
self.parser.add_argument("--led-panel-type", action="store", help="Needed to initialize special panels. Supported: 'FM6126A'", default="", type=str)
|
|
||||||
self.parser.add_argument("--led-no-drop-privs", dest="drop_privileges", help="Don't drop privileges from 'root' after initializing the hardware.", action='store_false')
|
|
||||||
self.parser.set_defaults(drop_privileges=True)
|
|
||||||
|
|
||||||
def usleep(self, value):
|
|
||||||
time.sleep(value / 1000000.0)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
print("Running")
|
|
||||||
|
|
||||||
def process(self):
|
|
||||||
self.args = self.parser.parse_args()
|
|
||||||
|
|
||||||
options = RGBMatrixOptions()
|
|
||||||
|
|
||||||
if self.args.led_gpio_mapping != None:
|
|
||||||
options.hardware_mapping = self.args.led_gpio_mapping
|
|
||||||
options.rows = self.args.led_rows
|
|
||||||
options.cols = self.args.led_cols
|
|
||||||
options.chain_length = self.args.led_chain
|
|
||||||
options.parallel = self.args.led_parallel
|
|
||||||
options.row_address_type = self.args.led_row_addr_type
|
|
||||||
options.multiplexing = self.args.led_multiplexing
|
|
||||||
options.pwm_bits = self.args.led_pwm_bits
|
|
||||||
options.brightness = self.args.led_brightness
|
|
||||||
options.pwm_lsb_nanoseconds = self.args.led_pwm_lsb_nanoseconds
|
|
||||||
options.led_rgb_sequence = self.args.led_rgb_sequence
|
|
||||||
options.pixel_mapper_config = self.args.led_pixel_mapper
|
|
||||||
options.panel_type = self.args.led_panel_type
|
|
||||||
|
|
||||||
|
|
||||||
if self.args.led_show_refresh:
|
|
||||||
options.show_refresh_rate = 1
|
|
||||||
|
|
||||||
if self.args.led_slowdown_gpio != None:
|
|
||||||
options.gpio_slowdown = self.args.led_slowdown_gpio
|
|
||||||
if self.args.led_no_hardware_pulse:
|
|
||||||
options.disable_hardware_pulsing = True
|
|
||||||
if not self.args.drop_privileges:
|
|
||||||
options.drop_privileges=False
|
|
||||||
|
|
||||||
self.matrix = RGBMatrix(options = options)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Start loop
|
|
||||||
print("Press CTRL-C to stop sample")
|
|
||||||
self.run()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Exiting\n")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
return True
|
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
from samplebase import SampleBase
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSquare(SampleBase):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSquare, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
offset_canvas = self.matrix.CreateFrameCanvas()
|
|
||||||
while True:
|
|
||||||
for x in range(0, self.matrix.width):
|
|
||||||
offset_canvas.SetPixel(x, x, 255, 255, 255)
|
|
||||||
offset_canvas.SetPixel(offset_canvas.height - 1 - x, x, 255, 0, 255)
|
|
||||||
|
|
||||||
for x in range(0, offset_canvas.width):
|
|
||||||
offset_canvas.SetPixel(x, 0, 255, 0, 0)
|
|
||||||
offset_canvas.SetPixel(x, offset_canvas.height - 1, 255, 255, 0)
|
|
||||||
|
|
||||||
for y in range(0, offset_canvas.height):
|
|
||||||
offset_canvas.SetPixel(0, y, 0, 0, 255)
|
|
||||||
offset_canvas.SetPixel(offset_canvas.width - 1, y, 0, 255, 0)
|
|
||||||
offset_canvas = self.matrix.SwapOnVSync(offset_canvas)
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
if __name__ == "__main__":
|
|
||||||
simple_square = SimpleSquare()
|
|
||||||
if (not simple_square.process()):
|
|
||||||
simple_square.print_help()
|
|
|
@ -1,33 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
from distutils.core import setup, Extension
|
|
||||||
|
|
||||||
core_ext = Extension(
|
|
||||||
name = 'core',
|
|
||||||
sources = ['rgbmatrix/core.cpp'],
|
|
||||||
include_dirs = ['../../include'],
|
|
||||||
library_dirs = ['../../lib'],
|
|
||||||
libraries = ['rgbmatrix'],
|
|
||||||
extra_compile_args = ["-O3", "-Wall"],
|
|
||||||
language = 'c++'
|
|
||||||
)
|
|
||||||
|
|
||||||
graphics_ext = Extension(
|
|
||||||
name = 'graphics',
|
|
||||||
sources = ['rgbmatrix/graphics.cpp'],
|
|
||||||
include_dirs = ['../../include'],
|
|
||||||
library_dirs = ['../../lib'],
|
|
||||||
libraries = ['rgbmatrix'],
|
|
||||||
extra_compile_args = ["-O3", "-Wall"],
|
|
||||||
language = 'c++'
|
|
||||||
)
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name = 'rgbmatrix',
|
|
||||||
version = '0.0.1',
|
|
||||||
author = 'Christoph Friedrich',
|
|
||||||
author_email = 'christoph.friedrich@vonaffenfels.de',
|
|
||||||
classifiers = ['Development Status :: 3 - Alpha'],
|
|
||||||
ext_package = 'rgbmatrix',
|
|
||||||
ext_modules = [core_ext, graphics_ext],
|
|
||||||
packages = ['rgbmatrix']
|
|
||||||
)
|
|
71
README.md
|
@ -1,49 +1,60 @@
|
||||||
# Visualización para pantallas LED de paradas de bus
|
# Configurar la RPI
|
||||||
|
|
||||||
Este repositorio contiene los archivos necesarios para ejecutar una aplicación Jupyter Notebook con ciertas bibliotecas de visualización y procesamiento de imágenes.
|
### Instalar el software para inicializar tarjetas SD
|
||||||
|
|
||||||
## Contenido
|
`apt install rpi-imager`
|
||||||
|
|
||||||
- `Dockerfile`: Define cómo construir la imagen Docker para ejecutar la aplicación.
|
### Instalar la imagen Raspbian 64 bit Lite
|
||||||
- `requirements.txt`: Lista las bibliotecas y dependencias necesarias para la aplicación.
|
|
||||||
|
|
||||||
## Dockerfile
|
Copiar la versión lite:
|
||||||
|
https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz
|
||||||
|
|
||||||
### Descripción
|
### Configurar la imagen
|
||||||
|
|
||||||
El `Dockerfile` especifica cómo construir una imagen Docker basada en Python 3.8 que tiene todas las dependencias necesarias para ejecutar la aplicación.
|
`rpi-imager` permite habilitar SSH, cambiar el nombre al dispositivo, configurar una cuenta de usuario, etc.
|
||||||
|
|
||||||
### Instrucciones
|
Para mas información revisar: http://rptl.io/newuser
|
||||||
|
|
||||||
1. **Imagen base**: Utiliza Python 3.8.
|
|
||||||
2. **Directorio de trabajo**: Establece `/app` como el directorio de trabajo en el contenedor.
|
|
||||||
3. **Instalación de dependencias**: Copia y utiliza `requirements.txt` para instalar las bibliotecas necesarias.
|
|
||||||
4. **Configuración de Jupyter**: Copia el archivo de configuración de Jupyter al contenedor.
|
|
||||||
5. **Puerto**: Expone el puerto 8888 para Jupyter Notebook.
|
|
||||||
6. **Comando de inicio**: Al iniciar el contenedor, se ejecuta Jupyter Notebook en el puerto 8888.
|
|
||||||
|
|
||||||
## requirements.txt
|
## Preparacipon de ambientes y dispositivo
|
||||||
|
|
||||||
### Descripción
|
Este presupone que se usa la cuenta de soporte y el directorio principal `/home/soporte`
|
||||||
|
|
||||||
El archivo `requirements.txt` lista las bibliotecas y dependencias que se requieren para la aplicación.
|
### Quitar la tarjeta de sonido para habilitar
|
||||||
|
|
||||||
### Bibliotecas y dependencias
|
`cat "blacklist snd_bcm2835" | sudo tee /etc/modprobe.d/blacklist-rgb-matrix.conf `
|
||||||
|
|
||||||
- `matplotlib`: Biblioteca de visualización de datos.
|
`sudo update-initramfs -u`
|
||||||
- `seaborn`: Biblioteca de visualización de datos basada en matplotlib.
|
|
||||||
- `plotly`: Biblioteca para gráficos interactivos.
|
|
||||||
- `opencv-python`: Biblioteca de procesamiento de imágenes y visión por computadora.
|
|
||||||
- `jupyter`: Entorno de desarrollo interactivo.
|
|
||||||
|
|
||||||
## Cómo ejecutar
|
`sudo reboot`
|
||||||
|
|
||||||
1. Construye la imagen Docker:
|
### Preparación de paquetes
|
||||||
|
|
||||||
`docker build -t bus_stop_visualization .`
|
`sudo apt-get update`
|
||||||
|
|
||||||
2. Ejecuta el contenedor:
|
paquetes basicos:
|
||||||
|
|
||||||
`docker run -d --name bus_stop_vis -v /scripts:/app/scripts -p 8888:8888 bus_stop_visualization`
|
`sudo apt-get install git python3-pip -y `
|
||||||
|
|
||||||
3. Abre un navegador y accede a `http://localhost:8888` para comenzar a usar Jupyter Notebook.
|
Requerimientos del LED-MATRIX
|
||||||
|
|
||||||
|
`sudo apt-get install python3-dev python3-pillow -y`
|
||||||
|
|
||||||
|
Requerimientos de pantallas led:
|
||||||
|
|
||||||
|
`sudo apt-get install python3-matplotlib python3-requests python3-numpy python3-pytzdata python3-apscheduler -y`
|
||||||
|
|
||||||
|
### Clonar los repositorios
|
||||||
|
|
||||||
|
`git clone https://dev.ilab.cl/TDTP/pantallas-led`
|
||||||
|
|
||||||
|
`git clone https://github.com/hzeller/rpi-rgb-led-matrix/`
|
||||||
|
|
||||||
|
`cd rpi-rgb-led-matrix/bindings/python`
|
||||||
|
|
||||||
|
|
||||||
|
`make build-python PYTHON=$(command -v python3)`
|
||||||
|
|
||||||
|
`sudo make install-python PYTHON=$(command -v python3)`
|
||||||
|
|
||||||
|
Return to the folder and proceed with the installation
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
IMAGE=$HOME/pantallas-led/GenPoster/example/poster.png
|
||||||
|
|
||||||
|
#sudo python3 testapi_full.py
|
||||||
|
cd pantallas-led/GenPoster
|
||||||
|
#docker build -t bus_poster .
|
||||||
|
./run_container.sh
|
||||||
|
|
||||||
|
cd $HOME/rpi-rgb-led-matrix/bindings/python/samples/
|
||||||
|
|
||||||
|
sudo python3 image-viewer.py $IMAGE
|
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 20 KiB |