Compare commits

...
Sign in to create a new pull request.

15 commits

102 changed files with 4107 additions and 1822 deletions

View file

@ -0,0 +1,35 @@
# Multimedijski krmilnik FRI
> *Also known as **PolžProjekt** or **KatjaKontroler***
## Arhitektura
Sistem sestavljata dva kartična računalnika (trenutno Raspberry Pi 4) - *krmilnik* in *zaslon*. Na krmilniku je zagnan MQTT broker (mosquitto) in več *driverjev*, ki služijo kot mostovi med napravami v predavalnici in MQTT. Na zaslonu teče spletni brskalnik (chromium) z odprtim *frontendom* (VueJS aplikacija, servirana s krmilnika). Frontend komunicira neposredno z MQTT brokerjem preko WebSockets povezave.
### Driverji
- `tse_serial` - relejno polje *TSE ___*, ki krmili napajanje različnih komponent predavalnice, projekcijska platna in senčila
- `barco_telnet` - en projektor tipa *Barco G62* preko novega "Barco Telnet" protokola. Za več projektorjev je potrebnih več instanc driverja
- `projector_motors` - dvigala za projektorje preko dveh modulov *Grove I2C 4-channel SPDT relay*
- `lucke` - razsvetljava v predavalnici preko A-rosso sistema WebSCADA
- `extron_audio_matrix` - avdio matrika - **NI DOKONČAN**
- `barco_rlmw_{http,tcp}` - en projektor serije *Barco RLM W*. Obstajata dve različici (http in tcp)
## Namestitev
Namestitev poteka z orodjem *Ansible*, potrebna pa je tudi lokalna namestitev orodja `npm` (za izgradnjo frontenda). V mapi `ansible_deploy` sta ločeni mapi z playbooki za krmilnik in zaslon.
Osnovni operacijski sistem za krmilnik je *RaspberryPi OS Lite* za zaslon pa *RaspberryPi OS Full*. Namestite ga lahko z orodjem *Raspberry Pi Imager* - pri tem bodite pozorni da nastavite uporabniško ime `pi` in geslo, ki je shranjeno v RC.
Za namestitev mora imeti Raspberry Pi dostop do interneta, zato ga priključite v omrežje z DHCP. Njegov IP naslov se izpiše na HDMI izhodu, lahko ga pa najdete tudi na drug način. Ker se po prvem zagonu na vgrajeni mrežni kartici konfigurira statičen IP naslov, je priporočeno, da se za namestitev uporablja USB Ethernet adapter.
Primer namestitve krmilnika za predavalnico P01 (ime v inventory datoteki je `p01_controller`), ki ima trenutno IP naslov `10.32.50.123` (ker se razlikuje od inventory datoteke ga je treba določiti z `-e ansible_host=`):
```sh
cd ansible_deploy/controller
ansible-playbook playbook.yml -i inventory.yml -l p01_controller -e ansible_host=10.32.50.123
```
## Posodobitev
Posodobitev poteka enako kot namestitev. Če se izvaja v predavalnici je potrebo poskrbeti, da imajo ciljne naprave dostop do interneta. Za to ni dobrega načina, zato je priporočen enak pristop kot pri namestitvi (USB Ethernet adapter in omrežje z DHCP - lahko celo deljena povezava s prenosnika).

View file

@ -1,6 +0,0 @@
all:
hosts:
mmctrl-p1:
projectors:
- projector-p1-center
- projector-p1-side

View file

@ -1,30 +0,0 @@
Hostname: {{ ansible_facts['hostname'] }} running {{ansible_facts['distribution'] }} on address {{ addr }}
Connected projectors: {{ barco_G62[0].ip }}
[global]
room = {{ room }}
mqttIp = {{ mqtt_ip }}
mqttPort = {{ mqtt_port }}
{% if barco_G62 %}
{%+ for projector in barco_G62 +%}
[{{projector.model}}.{{projector.position}}]
ip = {{projector.ip}}
port = {{projector.port}}
{% endfor %}
{% endif %}
{%+ if tse_box +%}
serial_device = {{ tse_box.serial_device }}
{% endif %}
{%+ if projector_motors +%} {# change to appropriate thingy for running them #}
{% end if %}
{%+ if extron_audio +%}
{% endif %}
{%+ if extron_video +%}
{% endif %}

View file

@ -0,0 +1,16 @@
[Unit]
Description={{ script_file }}
After=multi-user.target
PartOf=mm-controller.target
[Service]
ExecStart=/usr/local/bin/poetry run python3 -u {{ script_file }}
Type=simple
Restart=always
User=pi
Group=pi
RestartSec=10
WorkingDirectory={{ INSTALL_BASE }}
[Install]
WantedBy=mm-controller.target

View file

@ -0,0 +1,82 @@
predavalnice_pi:
# Skupne spremenljivke za vse krmilnike
vars:
ansible_user: pi
# Multimedia network
static_ip_cidr: "24"
static_ip_gateway: "192.168.192.1"
static_ip_dns_servers:
- "212.235.188.28"
- "212.235.188.29"
# Lokalni MQTT
mqtt_ip: localhost
mqtt_port: 1883
# Nastavitve za vse posamezne predavalnice
hosts:
p01_controller:
room: P01
ansible_host: 192.168.192.42
static_ip: 192.168.192.42
# Novi barco projektorji
barco_G62:
- position: main
model: barco_G62
port: 3023
ip: 192.168.192.12
- position: side
model: barco_G62
port: 3023
ip: 192.168.192.13
tse_box:
projector_motors:
- position: main
i2c_address: 0x42
offset: 0 #for when using single 8 channel relay board
- position: side
i2c_address: 0x43
offset: 4
lucke:
url: http://192.168.190.90:8091
roomId: 1
bearer_token: 0954afe1-4111-4f89-a123-fea08a55dc46
p22_controller:
room: P22
ansible_host: 192.168.192.43
static_ip: 192.168.192.43
# Novi barco projektorji
barco_G62:
- position: main
model: barco_G62
port: 3023
ip: 192.168.192.22
- position: side
model: barco_G62
port: 3023
ip: 192.168.192.23
tse_box:
projector_motors:
- position: main
i2c_address: 0x42
offset: 0 #for when using single 8 channel relay board
- position: side
i2c_address: 0x43
offset: 4
lucke:
url: http://192.168.190.90:8091
roomId: 2
bearer_token: b44c8cdc-d848-4b49-9deb-79023a60a62a

View file

@ -0,0 +1,38 @@
[global]
room = "{{ room }}"
mqttHost = "{{ mqtt_ip }}"
mqttPort = {{ mqtt_port }}
{% if barco_G62 is defined %}
{%+ for projector in barco_G62 +%}
[{{projector.model}}.{{projector.position}}]
ip = "{{projector.ip}}"
port = {{projector.port}}
{% endfor %}
{% endif %}
{%+ if tse_box is defined +%}
{# serial_device = "{{ tse_box.serial_device }}" #}
{% endif %}
{%+ if projector_motors is defined +%} {# change to appropriate thingy for running them #}
{%+ for motor in projector_motors +%}
[projector_motors.{{motor.position}}]
i2c_address = {{motor.i2c_address}}
{% endfor %}
{% endif %}
{%+ if extron_audio is defined +%}
{% endif %}
{%+ if extron_video is defined +%}
{% endif %}
{%+ if lucke is defined +%}
[lucke]
url = "{{ lucke.url }}"
roomId = "{{ lucke.roomId }}"
bearer_token = "{{ lucke.bearer_token }}"
{% endif %}

View file

@ -0,0 +1,3 @@
[Unit]
Description=Krmilnik multimedijskega sistema
After=multi-user.target

View file

@ -472,7 +472,7 @@ allow_anonymous true
# Note that if the broker is running as a Windows service it will default to
# "log_dest none" and neither stdout nor stderr logging is available.
# Use "log_dest none" if you wish to disable logging.
#log_dest stderr
log_dest syslog
# Types of messages to log. Use multiple log_type lines for logging
# multiple types of messages.

View file

@ -0,0 +1,236 @@
- name: Test playbook
vars:
PROJECT_BASE: "{{playbook_dir}}/../../"
INSTALL_BASE: "/home/pi/pyServices"
hosts:
- p01_controller
- p22_controller
handlers:
- name: restart NetworkManager
ansible.builtin.service:
name: NetworkManager
state: restarted
- name: Restart mosquitto
become: true
ansible.builtin.systemd_service:
name: mosquitto.service
state: restarted
tasks:
#
# NETWORK CONFIGURATION
#
- name: set eth0 static IP
become: true
community.general.nmcli:
conn_name: "Multimedia network"
ifname: eth0
type: ethernet
ip4: "{{ static_ip }}/{{ static_ip_cidr }}"
# Sorry timi
method6: disabled
# Multimedia net doesn't have Internet access, so this iface shouldn't be used for Internet access
never_default4: true
routes4_extended:
- ip: 192.168.0.0/16
next_hop: "{{ static_ip_gateway }}"
metric: 9999
- ip: 10.0.0.0/8
next_hop: "{{ static_ip_gateway }}"
metric: 9999
# gw4: "{{ static_ip_gateway }}"
state: present
#
# SYSTEM DEPENDENCIES
#
- name: Install pkgs
become: true
apt:
name:
- python3-pip
- mosquitto
- nginx
state: latest
update_cache: true
- name: Install Poetry
become: true
pip:
break_system_packages: true
name:
- poetry
#
# MOSQUITTO
#
- name: mosquitto enable
become: true
ansible.builtin.systemd_service:
name: mosquitto.service
enabled: true
- name: Copy mosqitconfig
become: true
ansible.builtin.copy:
src: mosquitto.conf
dest: /etc/mosquitto/mosquitto.conf
owner: root
group: root
mode: '0644'
backup: yes
notify: Restart mosquitto
#
# INSTALL CONTROLLER
#
- name: Create installation directory
file:
path: "{{INSTALL_BASE}}"
state: directory
- name: Install controller scripts
ansible.posix.synchronize:
src: "{{PROJECT_BASE}}/controller/"
dest: "{{INSTALL_BASE}}/"
delete: true
archive: false
recursive: true
- name: template config.toml
ansible.builtin.template:
src: ./malinaConfig.toml.j2
dest: "{{INSTALL_BASE}}/malinaConfig.toml"
- name: Install python libraries
ansible.builtin.shell:
cmd: "poetry install"
chdir: "{{INSTALL_BASE}}"
#
# INSTALL FRONTEND
#
- name: Build frontend (localhost)
delegate_to: localhost
ansible.builtin.shell:
cmd: "npm install --dev && npm run build"
chdir: "{{PROJECT_BASE}}/frontend"
- name: Install frontend
become: true
ansible.posix.synchronize:
src: "{{PROJECT_BASE}}/frontend/dist/"
dest: "/var/www/html/"
delete: true
archive: false
recursive: true
- name: Fix www root permission
become: true
ansible.builtin.file:
path: "/var/www/html"
owner: pi
group: pi
recurse: true
mode: 'u=rwX,g=rX,o=rX'
#
# SERVICES
#
- name: template target
become: true
ansible.builtin.template:
src: ./mm-controller.target.j2
dest: /etc/systemd/system/mm-controller.target
- name: Generate systemd services
become: true
block:
# Barco G62 (novi projektor)
- name: Barco G62 services
when: barco_G62 is defined
block:
- name: template service
become: true
vars:
script_file: "{{INSTALL_BASE}}/barco_telnet/barco_G62_control.py %i"
ansible.builtin.template:
src: ./controller_script.service.j2
dest: /etc/systemd/system/barco@.service
- name: enable service
ansible.builtin.systemd_service:
name: "{{item}}"
enabled: true
state: restarted
daemon_reload: true
loop:
- barco@main.service
- barco@side.service
# Lifti za projektorje (naši releji)
- name: template projector motors service
when: projector_motors is defined
block:
- name: template service
become: true
vars:
script_file: "{{INSTALL_BASE}}/projector_motors/projector_motors.py"
ansible.builtin.template:
src: ./controller_script.service.j2
dest: /etc/systemd/system/projector_motors.service
- name: enable service
ansible.builtin.systemd_service:
name: projector_motors.service
enabled: true
state: restarted
daemon_reload: true
# Power, platna, etc. (TSE relay box)
- name: template tse serial box service
when: tse_box is defined
block:
- name: template service
become: true
vars:
script_file: "{{INSTALL_BASE}}/tse_serial/tse_serial_controler.py"
ansible.builtin.template:
src: ./controller_script.service.j2
dest: /etc/systemd/system/tse_box.service
- name: enable service
ansible.builtin.systemd_service:
name: tse_box.service
enabled: true
state: restarted
daemon_reload: true
# a-rosso lučke kontroler
- name: template lucke service
when: lucke is defined
block:
- name: tmeplate service
become: true
vars:
script_file: "{{INSTALL_BASE}}/lucke/luckeControl.py"
ansible.builtin.template:
src: ./controller_script.service.j2
dest: /etc/systemd/system/lucke.service
- name: enable service
ansible.builtin.systemd_service:
name: lucke.service
enabled: true
state: restarted
daemon_reload: true
- name: daemon reload
become: true
ansible.builtin.systemd_service:
daemon_reload: true

View file

@ -0,0 +1,27 @@
predavalnice_pi:
# Skupne spremenljivke za vse zaslone
vars:
ansible_user: pi
# Check here: https://github.com/leukipp/touchkio/releases
touchkio_version: "1.3.1"
# Multimedia network
static_ip_cidr: "24"
static_ip_gateway: "192.168.192.1"
static_ip_dns_servers:
- "212.235.188.28"
- "212.235.188.29"
hosts:
p01_touch_display:
predavalnica: p01
static_ip: "192.168.192.111"
hostname: "p01_touch_display.local"
kiosk_url: "http://192.168.192.42?room=P01"
mqtt_host: "192.168.192.42"
p22_touch_display:
predavalnica: p22
static_ip: "192.168.192.112"
hostname: "p22_touch_display.local"
kiosk_url: "http://192.168.192.43?room=P22"
mqtt_host: "192.168.192.43"

View file

@ -0,0 +1,37 @@
# - name: Change splash image
# become: true
# copy:
# src: splash.png
# dest: /usr/share/plymouth/themes/pix/splash.png
- name: Enable boot splash screen
become: true
shell: "raspi-config nonint get_boot_splash && raspi-config nonint do_boot_splash 0"
register: boot_splash
changed_when:
- boot_splash == "1"
- name: Disable color splash
become: true
community.general.ini_file:
path: /boot/firmware/config.txt
option: disable_splash
value: 1
no_extra_spaces: true
- name: Remove desktop bloat
become: true
apt:
name:
- gvfs
- gnome-keyring
- cups
state: absent
- name: Switch to wayfire
become: true
shell: "raspi-config nonint is_wayfire && raspi-config nonint do_wayland W2"
register: result
failed_when: ( result.rc not in [ 0, 1 ] )
changed_when: ( result.rc == 1 )

View file

@ -0,0 +1,7 @@
- hosts: predavalnice_pi
# TODO: better include (import playbook)
tasks:
- include_tasks: static_ip.yml
# - include_tasks: wifi_temp.yml # TODO: remove this when we don't need wifi anymore
- include_tasks: pi_stuff.yml
- include_tasks: touch_display.yml

View file

@ -0,0 +1,32 @@
- name: Configure static IP address (using Network Manager)
become: yes
community.general.nmcli:
conn_name: "Multimedia net"
ifname: eth0
type: ethernet
ip4: "{{ static_ip }}/{{ static_ip_cidr }}"
# Sorry timi
method6: disabled
# Multimedia net doesn't have Internet access, so this iface shouldn't be used for Internet access
never_default4: true
routes4_extended:
- ip: 192.168.0.0/16
next_hop: "{{ static_ip_gateway }}"
metric: 9999
- ip: 10.0.0.0/8
next_hop: "{{ static_ip_gateway }}"
metric: 9999
# gw4: "{{ static_ip_gateway }}"
state: present
conn_reload: true
- name: Wait for network to be available
become: yes
wait_for_connection:
timeout: 60
- name: Display new IP configuration
debug:
msg: "Static IP configured: {{ static_ip }}/{{ static_ip_cidr }}"

View file

@ -0,0 +1,38 @@
- name: Fix fonts
become: true
apt:
name:
- fonts-noto-core
state: present
- name: Download .deb file
get_url:
url: "https://github.com/leukipp/touchkio/releases/download/v{{ touchkio_version }}/touchkio_{{ touchkio_version }}_arm64.deb"
dest: "/home/pi/touchkio_{{ touchkio_version }}_arm64.deb"
register: deb_download
- name: Install the latest .deb package
become: yes
apt:
deb: "/home/pi/touchkio_{{ touchkio_version }}_arm64.deb"
when: deb_download is succeeded
- name: Create systemd user service directory
file:
path: "{{ ansible_env.HOME }}/.config/systemd/user"
state: directory
- name: Create systemd user service
template:
src: touchkio.service.j2
dest: "{{ ansible_env.HOME }}/.config/systemd/user/touchkio.service"
- name: Enable systemd service
ansible.builtin.systemd_service:
name: touchkio
enabled: true
state: restarted
scope: user
daemon_reload: true
when: ansible_check_mode == false

View file

@ -0,0 +1,12 @@
[Unit]
Description=Kiosk browser
After=graphical.target
[Service]
ExecStart=/usr/bin/touchkio --web-url="{{ kiosk_url }}" --web-zoom=1.0 "--mqtt-url=mqtt://{{ mqtt_host }}"
#ExecStart=/usr/bin/chromium-browser --ozone-platform=x11 --noerrdialogs --disable-infobars --kiosk --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222 --app="{{ kiosk_url }}"
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target

View file

@ -0,0 +1,26 @@
- name: Set wifi country
become: true
shell: "raspi-config nonint get_wifi_country && raspi-config nonint do_wifi_country SI; true"
register: wifi_country
changed_when:
- wifi_country != "SI"
- name: Configure P2P wifi
become: yes
community.general.nmcli:
conn_name: "MALINCA"
ifname: wlan0
type: wifi
ssid: "MALINCA"
wifi_sec:
key-mgmt: wpa-psk
psk: "MALINCA123"
autoconnect: true
method4: auto
state: present
- name: Restart NetworkManager
become: yes
service:
name: NetworkManager
state: restarted

View file

@ -1,4 +0,0 @@
[prHosts]
192.168.122.245

View file

@ -1,23 +0,0 @@
P01:
hosts:
192.168.122.245:
vars:
room: P01
mqtt_ip: localhost
mqtt_port: 1883
barco_G62:
- position: main
model: barco_G62
port: 3023
ip: 192.168.192.13
- position : side
model: barco_G62
port: 3023
ip: 192.168.192.14
barco_old:
main_ip: 1.1.1.1
P22:

View file

@ -1,152 +0,0 @@
- name: test playbook
hosts: P01
#vars_files:
# - secret
vars:
addr: "192.168.122.245"
os_environment:
- key: VITE_MQTT_HOST
value: polztest.local
tasks:
- name: ping hosts
ansible.builtin.ping:
- name: install pkgs
become: true
#become_user: root
apt:
name:
- mosquitto
- python3-poetry
#- npm
- nginx
state: latest
#- name: set env
- name: ensures services dir exists
file:
path: "/home/kat/pyServices"
state: directory
- name: ensures services dir exists
file:
path: "/home/kat/pyServices/fri-mm-maline"
state: directory
- name: Copy poetry
become: true
ansible.builtin.copy:
#seuser: root
src: "../{{ item }}"
dest: "/home/kat/pyServices/fri-mm-maline/{{ item }}"
owner: kat
group: kat
mode: '0644'
backup: yes
loop:
- poetry.lock
- pyproject.toml
- README.md
- name: copy python scripts
become: true
ansible.builtin.copy:
src: "../{{ item }}"
dest: "/home/kat/pyServices/fri-mm-maline"
owner: kat
group: kat
mode: '0644'
backup: yes
loop:
- barco_telnet/barco_G62_control.py
- extron_audio_matrix/extron_audio_matrix_telnet_control.py
- extron_audio_matrix/extron_audio_matrix_telnet_interpreter.py
- projector_motors/projector_motors.py
- tse_serial/tse_serial_controler.py
- tse_serial/tse_serial_interpreter.py
- config.toml #TODO GENERATE CONFIG
- name: poetry installation from thing
ansible.builtin.shell:
cmd: "poetry install"
chdir: "/home/kat/pyServices/fri-mm-maline"
- name: Copy barco config
become: true
ansible.builtin.copy:
#seuser: root
src: "../{{ item }}"
dest: /lib/systemd/system
owner: root
group: root
mode: '0644'
#backup: yes
loop:
- barco@.service
- extron_audio.service
- mqtt_init.service
- projector_motors.service
- tse_box.service
- name: Copy mosqitconfig
become: true
ansible.builtin.copy:
#seuser: root
src: ../mosquitto.conf
dest: /etc/mosquitto/conf.d
owner: root
group: root
mode: '0644'
backup: yes
# - name: set env
# become: true
# ansible.builtin.lineinfile:
# dest: "/etc/environment"
# state: present
# regexp: "^{{ item.key }}="
# line: "{{ item.key }}={{ item.value }}"
# with_items: "{{ os_environment }}"
- name: copy frontend to webroot
become: true
ansible.builtin.copy:
src: /home/kat/Documents/polzp/fri_multimedia_rework/frontend/vju_display/dist
#src: /home/kat/fri_multimedia_rework/frontend/vju_display/dist
#remote_src: true
dest: /srv/www
#dest: /home/kat/testoa
owner: root
group: root
mode: '0755'
backup: yes
- name: enable modules
become: true
ansible.builtin.systemd_service:
#name: "{{[ 'barco@main.service', 'barco@side.service' ]}}"
name: "{{ item }}"
state: stopped
enabled: false
loop:
- barco@main.service
- barco@side.service
- extron_audio.service
- mqtt_init.service
- projector_motors.service
- tse_box.service
- name: mosquitto service and reload
become: true
ansible.builtin.systemd_service:
name: mosquitto.service
state: started
daemon_reload: true

View file

@ -1,15 +0,0 @@
[Unit]
Description={{ script_file }}
After=multi-user.target
[Service]
ExecStart=/usr/bin/poetry run /usr/bin/python3 {{ script_file }}
Type=simple
Restart=always
User=kat
Group=kat
RestartSec=10
[Install]
WantedBy=multi-user.target
DefaultInstance=main

View file

@ -1,37 +0,0 @@
- name: test playbook
hosts: P01
#vars_files:
# - secret
vars:
addr: "192.168.122.245"
os_environment:
- key: VITE_MQTT_HOST
value: polztest.local
tasks:
- name: test things
when: barco_g62 is defined
block:
#- name: ping hosts
# ansible.builtin.ping:
- name: template config.toml
ansible.builtin.template:
src: ./conf.j2
dest: /home/kat/testo/conf.txt
- name: template barco systemd service
vars:
script_file: "/home/kat/pyServices/fri-mm-maline/barco_G62_control.py %i"
ansible.builtin.template:
src: ./service.j2
dest: /home/kat/testo/barc.serv
- name: enable barcos
ansible.builtin.ping:
#itd itd itd
- name: test second
when: barco_old is defined
block:
- name: pingerino
ansible.builtin.ping:

View file

@ -1,16 +0,0 @@
[Unit]
Description=Barco projector control
After=mqtt_init.service
[Service]
ExecStart=/usr/bin/python3 /home/rpi/barco_telnet_control.py
User=rpi
Group=rpi
Type=simple
Restart=always
[Install]
WantedBy=mqtt_init.service

View file

@ -1,16 +0,0 @@
[Unit]
Description=Barco projector control
After=multi-user.target
[Service]
ExecStart=/usr/bin/poetry run python3 /home/kat/pyServices/fri-mm-maline/barco_G62_control.py %i
Type=simple
Restart=always
WorkingDirectory=/home/kat/pyServices/fri-mm-maline
User=kat
Group=kat
RestartSec=10
[Install]
WantedBy=multi-user.target
DefaultInstance=main

View file

@ -1,127 +0,0 @@
import asyncio
import socket
import aiomqtt
import telnetlib3
import toml
import sys
#GLOBALS
room = "undefined"
position = "undefined"
barcoIP = "undefined"
telnetPort = 3023
mqttPort = 1883
mqttIP = 'localhost'
cmdMap = {
'power': 'POWR',
'shutter': 'PMUT',
'freeze': 'FRZE'
}
reverseCmdMap = {v: k for k, v in cmdMap.items()}
def parse_barco_response(raw: str):
raw = raw[1:-1] # strip square brackets
#print(raw)
if raw.startswith("ERR"):
return None # TODO parse type error - "disabled control" is special case which shouldnt normally happen
cmd, status = raw.split("!", maxsplit=2)
#print(cmd + " " + status)
cmd = reverseCmdMap[cmd]
status = '1' if status == '01' else '0'
return cmd, status
#TODO MAKE THESE USE GLOBAL INSTEAD OF PASSING BARCO POSITION IN MAIN
async def barco_telnet_command(client, writer, select: str):
global room
onSub = f"{room}/projectors/{select}/#"
#print('TEST', onSub)
onMatch = f"{room}/projectors/{select}/command/+"
await client.subscribe(onSub)
#async with client.messages as msgs:
async for mesg in client.messages:
# print(mesg.topic.value)
# print(mesg.payload.decode())
# print('on', select)
if mesg.topic.matches(onMatch):
# print("test")
cmd = mesg.topic.value.split("/")[-1]
#val = '1' if mesg.payload.decode() == 'ON' else '0'
val = '1' if mesg.payload.decode() == '1' else '0' # refactor to direct 0 and 1
barcoCmd = f"[{cmdMap[cmd]}{val}]"
print("Received: [" + mesg.topic.value + "] payload: [" + mesg.payload.decode() + "]")
print("Sending command to Barco: " + barcoCmd)
writer.write(barcoCmd)
async def barco_telnet_read_status(client, reader, select: str):
global room
while True:
output = await reader.readuntil(b']')
raw_response: str = output.decode().strip() # strip not necessary? needed for local netcat testing though
print("Received: " + raw_response + " from Barco (" + select + ')')
parsed = parse_barco_response(raw_response)
if parsed == None:
continue #TODO alert for errors
print(f"Updating topic [{parsed[0]}] with value [{parsed[1]}]")
await client.publish(f"{room}/projectors/{select}/status/{parsed[0]}", payload=parsed[1])
async def barco_telnet_query_status(writer, select: str):
while True:
for val in cmdMap.values():
print(f"Querying Barco {select} with: [{val}?]")
writer.write(f"[{val}?]" + '\r\n') # TODO test if funny CRLF necessary (it probably gets ignored)
await asyncio.sleep(0.2) # sleep between writes necessary, otherwise it gets confused.
# simultaneous commands from control could break this? TODO fix later
await asyncio.sleep(30) # TODO find appropriate period
# async def shell(reader, writer):
# async with aiomqtt.Client('localhost', 1883) as client:
# task_status_query = asyncio.create_task(barco_telnet_query_status(writer))
# task_status_reader = asyncio.create_task(barco_telnet_read_status(client, reader))
# task_control = asyncio.create_task(barco_telnet_command(client, writer))
# await asyncio.gather(task_status_query, task_status_reader, task_control)
async def main():
global barcoPosition, barcoIP, telnetPort, mqttIP, mqttPort
if len(sys.argv) < 2:
sys.exit("No position provided")
else:
barcoPosition = sys.argv[1]
conf = toml.load('./config.toml')
g62Barcos = {k: v for k,v in barcos["barco_G62"].items()}
currentBarco = newBarcos[barcoPosition]
barcoIP = currentBarco['ip']
barcoReader, barcoWriter = await telnetlib3.open_connection(barcoIP, telnetPort)
async with aiomqtt.Client(mqttIP, mqttPort) as client:
task_status_query_barco = asyncio.create_task(barco_telnet_query_status(barcoWriter, barcoPosition))
task_status_reader_barco = asyncio.create_task(barco_telnet_read_status(client, barcoReader, barcoPosition))
task_control_barco = asyncio.create_task(barco_telnet_command(client, barcoWriter, barcoPosition))
await asyncio.gather(task_status_query_barco, task_status_reader_barco, task_control_barco)
### fuj to, ne tk delat
# if __name__ == '__main__':
# loop = asyncio.get_event_loop()
# coro = telnetlib3.open_connection(mainBarcoIP, 3023, shell=shell)
# coro = telnetlib3.open_connection(mainBarcoIP, 3023, shell=shell)
# # coro = telnetlib3.open_connection('localhost', 1234, shell=shell)
# reader, writer = loop.run_until_complete(coro)
# loop.run_until_complete(writer.protocol.waiter_closed)
#mqttIP = sys.argv[1]
#barcoIP = sys.argv[2]
asyncio.run(main())

View file

@ -1,14 +0,0 @@
[globals]
room = 'P01'
mqttIp = 'localhost'
mqttPort = '1883'
[barco.novi.glavni]
ip = '192.168.192.12'
port = '3023'
[barco.novi.stranski]
ip = '192.168.192.16' # or smth
port = '3023'

View file

@ -0,0 +1,67 @@
import re
import asyncio
import aiohttp
from asyncio.exceptions import CancelledError
PORT = 43680
RE_STATUS = re.compile(r".stats.txt(.+).value='(.+)';")
REMOTEKEY_TIMEOUT = aiohttp.ClientTimeout(total=1)
class BarcoRLM_Control:
def __init__(self, projector_ip):
self.projector_ip = projector_ip
self.session = None
async def _request(self, method, path, *args, **kwargs):
if not self.session:
self.session = aiohttp.ClientSession()
url = f"http://{self.projector_ip}{path}"
resp = await self.session.request(method, url, *args, **kwargs)
resp.raise_for_status()
return await resp.text()
async def get_status(self):
resp = await self._request("GET", "/tgi/firststatus.tgi")
matches = RE_STATUS.findall(resp)
status = dict(matches)
return status
async def toggle_power(self):
await self._request("GET", "/tgi/general.tgi?powertog")
async def click_remote(self, key):
try:
await self._request("GET", f"/tgi/remote.tgi?{key}", timeout=REMOTEKEY_TIMEOUT, raise_for_status=False)
except TimeoutError:
pass
async def set_shutter(self, shutter):
endpoint = f"/tgi/general.tgi?pause{onoff(shutter)}"
await self._request("GET", endpoint)
async def set_power(self, power):
key = onoff(power) + "ky"
await self.click_remote(key)
async def set_input(self, input):
endpoint = f"/tgi/input.tgi?{input}"
await self._request("GET", endpoint)
def onoff(state: bool) -> str:
if state:
return "on"
else:
return "off"
if __name__ == "__main__":
async def main():
barco = BarcoRLM_Control("192.168.192.12")
status = await barco.get_status()
print(status)
await barco.click_remote("kymenu")
asyncio.run(main())

View file

@ -0,0 +1,80 @@
import asyncio
import aiomqtt
from collections import defaultdict
from barco_legacy import BarcoRLM_Control
# TODO MAKE THIS CONFIGURALBE
PROJECTOR_IP = "192.168.192.12"
MQTT_PREFIX = "p22/projektorji/glavni"
MQTT_HOST = "-----"
POLLING_PERIOD_SEC = 10
class BarcoRLM_MQTT:
def __init__(self, projector_ip, mqtt_prefix):
self.projector_ip = projector_ip
self.mqtt_prefix = mqtt_prefix
self.barco = BarcoRLM_Control(projector_ip)
self.last_status = defaultdict(lambda: '')
async def run(self):
async with aiomqtt.Client(MQTT_HOST, 1883) as client:
self.client = client
task_polling = asyncio.create_task(self.task_polling())
task_control = asyncio.create_task(self.task_control())
await asyncio.gather(task_control, task_polling)
async def task_control(self):
topicMatch = f"{self.mqtt_prefix}/ukaz/+"
await self.client.subscribe(topicMatch)
async for mesg in self.client.messages:
cmd = mesg.topic.value.split("/")[-1]
val = mesg.payload.decode()
if cmd == "power":
await self.barco.set_power(onoff(val))
elif cmd == "shutter":
await self.barco.set_shutter(onoff(val))
async def task_polling(self):
while True:
status = await self.barco.get_status()
for key, val in status.items():
if self.last_status[key] != val:
print(f"Status change {key}={val}")
await self.on_status_change(key, val)
self.last_status = status
await asyncio.sleep(POLLING_PERIOD_SEC)
async def on_status_change(self, key, val):
mkey = mval = None
if key == "status":
mkey = "power"
if val == "Imaging":
mval = "1"
if val == "Standby":
mval = "0"
if key == "src":
mkey = "input"
mval = val
if key == "fmr":
mkey = "input_format"
mval = val
if mkey is not None and mval is not None:
await self.client.publish(f"{self.mqtt_prefix}/status/{mkey}", payload=mval)
def onoff(input):
#if input == "1":
# return True
#elif input == "0":
# return False
return input == "1"
if __name__ == '__main__':
barco = BarcoRLM_MQTT(PROJECTOR_IP, MQTT_PREFIX)
asyncio.run(barco.run())

View file

@ -0,0 +1,98 @@
import re
import asyncio
import telnetlib3
from dataclasses import dataclass
from enum import StrEnum
PORT = 43680
RE_STATUS = re.compile(r"OP (.+?)(?:$| = (.+)$)")
@dataclass(frozen=True)
class ACK:
command: str
@dataclass(frozen=True)
class ValueUpdate:
key: str
value: str
class Status(StrEnum):
standby = "0"
warmup = "1"
imaging = "2"
cooling = "3"
warning = "4"
class LampMode(StrEnum):
economy = "0"
standard = "1"
dimming = "2"
class ValueType(StrEnum):
status = "status"
picture_mute = "picture.mute"
lamp_mode = "lamp.mode"
class BarcoRLMW_TCP:
def __init__(self, projector_ip):
self.projector_ip = projector_ip
self.session = None
async def init(self):
self.reader, self.writer = await telnetlib3.open_connection(self.projector_ip, 3023)
async def exec(self, cmd):
await self.writer.write("op " + cmd + "\r")
async def set(self, key, val):
await self.writer.write("op " + key + " = " + val + "\r")
async def query(self, key):
await self.writer.write("op " + key + " ?\r")
async def set_power(self, power):
await self.exec(f"power.{onoff(power)}")
async def set_shutter(self, shutter):
await self.set("picture.mute", int(shutter))
async def iter_messages(self):
"""Async iterator that processes feedback from the projector.
Yields ACK (acknowledgement) and ValueUpdate (value has changed) messages."""
while True:
line = await self.reader.readuntil('\r').decode().strip()
msg = self.parse_response(line)
if msg:
yield msg
def parse_response(self, line):
matches = RE_STATUS.findall(line)
if len(matches) == 0:
return None
match = matches[0]
if len(match) == 2:
return ACK(match[1])
if len(match) == 3:
return ValueUpdate(match[1], match[2])
def onoff(state: bool) -> str:
if state:
return "on"
else:
return "off"
if __name__ == "__main__":
async def main():
barco = BarcoRLMW_TCP("192.168.192.12")
status = await barco.get_status()
print(status)
await barco.click_remote("kymenu")
asyncio.run(main())

View file

@ -0,0 +1,72 @@
import asyncio
import aiomqtt
from collections import defaultdict
from barco_rlmw_tcp import BarcoRLMW_TCP, ValueUpdate, Status, ACK, ValueType
# TODO MAKE THIS CONFIGURALBE
PROJECTOR_IP = "192.168.192.12"
MQTT_PREFIX = "p22/projektorji/glavni"
MQTT_HOST = "-------"
POLLING_PERIOD_SEC = 10
class BarcoRLMW_TCP_MQTT:
def __init__(self, projector_ip, mqtt_prefix):
self.projector_ip = projector_ip
self.mqtt_prefix = mqtt_prefix
self.barco = BarcoRLMW_TCP(projector_ip)
self.last_status = defaultdict(lambda: '')
async def run(self):
async with aiomqtt.Client(MQTT_HOST, 1883) as client:
self.client = client
task_polling = asyncio.create_task(self.task_polling())
task_control = asyncio.create_task(self.task_control())
await asyncio.gather(task_control, task_polling)
async def task_control(self):
topicMatch = f"{self.mqtt_prefix}/ukaz/+"
await self.client.subscribe(topicMatch)
async for mesg in self.client.messages:
cmd = mesg.topic.value.split("/")[-1]
val = mesg.payload.decode()
if cmd == "power":
await self.barco.set_power(onoff(val))
elif cmd == "shutter":
await self.barco.set_shutter(onoff(val))
async def task_polling(self):
while True:
await self.barco.query("status")
await asyncio.sleep(POLLING_PERIOD_SEC)
async def write_status(self, status, value):
await self.client.publish(f"{self.mqtt_prefix}/status/{status}", payload=value)
async def task_process_messages(self):
async for msg in self.barco.iter_messages():
# We only care about value updates
if isinstance(msg, ValueUpdate):
# Power status
if msg.key == ValueType.status:
if msg.value in (Status.imaging, Status.warmup):
self.write_status("power", 1)
elif msg.value in (Status.standby, Status.cooling):
self.write_status("power", 0)
# Video mute
if msg.key == ValueType.picture_mute:
self.write_status("shutter", msg.value)
def onoff(input):
if input == "1":
return True
elif input == "0":
return False
if __name__ == '__main__':
barco = BarcoRLMW_TCP_MQTT(PROJECTOR_IP, MQTT_PREFIX)
asyncio.run(barco.run())

View file

@ -0,0 +1,138 @@
import asyncio
from collections import defaultdict
import aiomqtt
import telnetlib3
import toml
import sys
import os
#GLOBALS
room: str
barcoPosition: str
barcoReached: bool
lastState = defaultdict(lambda: None)
cmdMap = {
'power': 'POWR',
'shutter': 'PMUT',
'freeze': 'FRZE',
#'test_pattern': 'TPRN',
}
reverseCmdMap = {v: k for k, v in cmdMap.items()}
# There needs to be a minimum time between writes. Since we have two "threads", we use a lock and a sleep in barco_send() to enforce it
lock = asyncio.Lock()
def parse_barco_response(raw: str):
raw = raw[1:-1] # strip square brackets
if raw.startswith("ERR"):
print("ERROR:", raw)
return None # TODO parse type error - "disabled control" is special case which shouldnt normally happen
cmd, status = raw.split("!", maxsplit=2)
cmd = reverseCmdMap[cmd]
status = int(status)
barcoReached = True
return cmd, status
async def barco_send(writer, value):
async with lock:
writer.write(value + '\r\n')
print("Writing", value)
await asyncio.sleep(0.2) # sleep between writes necessary, otherwise it gets confused.
async def barco_telnet_command(client, writer, select: str):
"""Receive commands from MQTT and send them to the projector"""
await client.subscribe(f"{room}/projectors/{select}/#")
async for mesg in client.messages:
if mesg.topic.matches(f"{room}/projectors/{select}/set/+"):
cmd = mesg.topic.value.rsplit("/", maxsplit=1)[-1]
val = mesg.payload.decode()
if val not in ("0", "1") or cmd not in cmdMap:
print("INVALID COMMAND OR VALUE:", cmd, val)
continue
barcoCmd = cmdMap[cmd]
# Send command to projector
await barco_send(writer, f"[{barcoCmd}{val}]")
# Immediately ask for a status
await barco_send(writer, f"[{barcoCmd}?]")
async def barco_telnet_read_status(client, reader, select: str):
"""Read status reports (we trigger them in the polling task as well as whenver sending a command)"""
while True:
output = await reader.readuntil(b']')
raw_response: str = output.decode()
print("Received: " + raw_response + " from Barco (" + select + ')')
try:
key, val = parse_barco_response(raw_response)
except:
print("NOT PARSED:", raw_response)
continue
await client.publish(f"{room}/projectors/{select}/status/{key}", payload=val)
async def barco_telnet_query_status(writer, select: str):
"""Periodically ask the projector for its status"""
while True:
# Most queries only work when turned on, so if we're not sure, only query power
if lastState[cmdMap["power"]] == "01":
queries = cmdMap.values()
else:
queries = [cmdMap["power"]]
for val in queries:
await barco_send(writer, f"[{val}?]")
await asyncio.sleep(2) # TODO find appropriate period
async def main():
global room, barcoReached, barcoPosition
if len(sys.argv) < 2:
sys.exit("No position provided")
else:
barcoPosition = sys.argv[1]
config_file = os.getenv('MM_CONFIG_PATH', './malinaConfig.toml')
conf = toml.load(config_file)
room = conf['global']['room']
mqttHost = conf['global']['mqttHost']
mqttPort = conf['global']['mqttPort']
g62Barcos = {k: v for k,v in conf["barco_G62"].items()}
currentBarco = g62Barcos[barcoPosition]
barcoIP = currentBarco['ip']
telnetPort = int(currentBarco["port"])
barcoReached = False
try:
barcoReader, barcoWriter = await telnetlib3.open_connection(barcoIP, telnetPort)
barcoReached = True
except Exception as e:
print("Connection failed: " + barcoIP + ": " + str(e))
barcoReached = False
else:
async with aiomqtt.Client(mqttHost, mqttPort) as client:
task_status_query_barco = asyncio.create_task(barco_telnet_query_status(barcoWriter, barcoPosition))
task_status_reader_barco = asyncio.create_task(barco_telnet_read_status(client, barcoReader, barcoPosition))
task_control_barco = asyncio.create_task(barco_telnet_command(client, barcoWriter, barcoPosition))
await asyncio.gather(task_status_query_barco, task_status_reader_barco, task_control_barco)
await client.publish(f"{room}/projectors/{barcoPosition}/error", payload=("UNREACHABLE" if not barcoReached else "OK"))
if __name__ == '__main__':
asyncio.run(main())

View file

@ -47,12 +47,17 @@ async def shell(reader, writer):
async with aiomqtt.Client('localhost', 1883) as client:
task_status_query = asyncio.create_task(extron_audio_telnet_status(client, writer, reader))
task_control = asyncio.create_task(extron_audio_telnet_control(client, writer))
await asyncio.gather(task_control)
await asyncio.gather(task_status_query, task_control)
if __name__ == '__main__':
config_file = os.getenv('MM_CONFIG_PATH', './malinaConfig.toml')
conf = toml.load(config_file)
room = conf['global']['room']
mqttHost = conf['global']['mqttHost']
mqttPort = conf['global']['mqttPort']
loop = asyncio.get_event_loop()
#coro = telnetlib3.open_connection('localhost', 1234, shell=shell)
coro = telnetlib3.open_connection('192.168.192.14', 23, shell=shell)
reader, writer = loop.run_until_complete(coro)
loop.run_until_complete(writer.protocol.waiter_closed)

View file

@ -0,0 +1,110 @@
from http import HTTPStatus
import aiomqtt
import asyncio
import toml
import aiohttp
import os
lucke_bearer_token: str
room: str
url: str
roomId: int|str
async def sendSceneRequest(client, scene):
endpoint = url + f"/rest/v2/fri-fkkt/lecture-halls/{roomId}/scenes/{scene}/activate"
async with aiohttp.request("GET", endpoint, headers={"Authorization": "Bearer " + lucke_bearer_token}) as resp:
if resp.status == 204:
await client.publish(f'{room}/lucke/preset/current', payload=scene, qos=1, retain=True)
else:
print(resp.status, await resp.text())
async def setLight(client, lightNum, intensity: int):
endpoint = url + f"/rest/v2/fri-fkkt/lecture-halls/{roomId}/lights/{lightNum}"
async with aiohttp.request("PUT", endpoint, headers={"Authorization": "Bearer " + lucke_bearer_token}, json={
"stateOn": intensity != 0,
"dimmValue": intensity
}) as resp:
if resp.status == 204:
await client.publish(f'{room}/lucke/brightness/{lightNum}', payload=intensity, qos=1, retain=True)
else:
print("setLight error:", resp.status, await resp.text())
async def saveScene(client, sceneNum):
endpoint = url + f"/rest/v2/fri-fkkt/lecture-halls/{roomId}/scenes/{sceneNum}/save"
async with aiohttp.request("GET", endpoint, headers={"Authorization": "Bearer " + lucke_bearer_token}) as resp:
if resp.status == 204:
pass
else:
print("saveScene error:", resp.status, await resp.text())
async def task_luckeCommand(controlClient):
await controlClient.subscribe(f"{room}/lucke/#")
async for mesg in controlClient.messages:
mesg: aiomqtt.Message
if mesg.topic.matches(f"{room}/lucke/preset/recall"):
sceneNum = mesg.payload.decode()
print("Received: " + str(mesg.topic) + " payload: [" + sceneNum + "]")
await sendSceneRequest(controlClient, sceneNum)
elif mesg.topic.matches(f"{room}/lucke/preset/save"):
sceneNum = mesg.payload.decode()
print("Received: " + str(mesg.topic) + " payload: [" + sceneNum + "]")
await saveScene(controlClient, sceneNum)
elif mesg.topic.matches(f"{room}/lucke/set/+"):
lightNum = mesg.topic.value.rsplit("/", maxsplit=1)[-1]
try:
intensity = int(mesg.payload.decode())
assert 0 <= intensity <= 100
except:
print("Invalid message:", mesg)
else:
await setLight(controlClient, lightNum, intensity)
await asyncio.sleep(0.01)
async def task_luckePoll(client):
"""Polls the API and sends light brightness to the API"""
while True:
endpoint = url + f"/rest/v2/fri-fkkt/lecture-halls/{roomId}/lights/"
async with aiohttp.ClientSession() as session:
async with session.get(endpoint, headers={"Authorization": f"Bearer {lucke_bearer_token}"}) as response:
if response.status == 200:
lights = await response.json()
for light in lights:
await client.publish(f'{room}/lucke/is_dimmable/{light["id"]}', payload=light["dimmable"], qos=1, retain=True)
# TODO: find a better way to handle non-dimmable lights
if light["stateOn"]:
dimValue = light.get("dimmValue", 100)
else:
dimValue = 0
await client.publish(f'{room}/lucke/brightness/{light["id"]}', payload=dimValue, qos=1, retain=True)
else:
print(f"Failed to fetch lights: {response.status}", await response.text())
await asyncio.sleep(.5)
async def main():
global room, lucke_bearer_token, url, roomId
config_file = os.getenv('MM_CONFIG_PATH', './malinaConfig.toml')
conf = toml.load(config_file)
room = conf['global']['room']
mqttHost = conf['global']['mqttHost']
mqttPort = conf['global']['mqttPort']
url = conf["lucke"]['url']
roomId = conf["lucke"]['roomId']
lucke_bearer_token = conf["lucke"]['bearer_token']
async with aiomqtt.Client(mqttHost, mqttPort) as client:
task_control = asyncio.create_task(task_luckeCommand(client))
task_control = asyncio.create_task(task_luckePoll(client))
await asyncio.gather(task_control)
if __name__ == '__main__':
asyncio.run(main())

View file

@ -0,0 +1,17 @@
[global]
mqttHost = '192.168.192.42'
mqttPort = 1883
room = 'P01'
[lucke]
url = 'http://192.168.190.90'
roomId = 1
bearer_token = '0954afe1-4111-4f89-a123-fea08a55dc46'
[barco_G62.main]
ip = '192.168.192.12'
port = 3023
[barco_G62.side]
ip = '192.168.192.13'
port = 3023

825
controller/poetry.lock generated Normal file
View file

@ -0,0 +1,825 @@
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
version = "2.6.1"
description = "Happy Eyeballs for asyncio"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
{file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
]
[[package]]
name = "aiohttp"
version = "3.12.13"
description = "Async http client/server framework (asyncio)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29"},
{file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0"},
{file = "aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d"},
{file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa"},
{file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294"},
{file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce"},
{file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe"},
{file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5"},
{file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073"},
{file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6"},
{file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795"},
{file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0"},
{file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a"},
{file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40"},
{file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6"},
{file = "aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad"},
{file = "aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178"},
{file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c"},
{file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358"},
{file = "aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014"},
{file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7"},
{file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013"},
{file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47"},
{file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a"},
{file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc"},
{file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7"},
{file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b"},
{file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9"},
{file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a"},
{file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d"},
{file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2"},
{file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3"},
{file = "aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd"},
{file = "aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9"},
{file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73"},
{file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347"},
{file = "aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f"},
{file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6"},
{file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5"},
{file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b"},
{file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75"},
{file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6"},
{file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8"},
{file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710"},
{file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462"},
{file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae"},
{file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e"},
{file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a"},
{file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5"},
{file = "aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf"},
{file = "aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e"},
{file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938"},
{file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace"},
{file = "aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb"},
{file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7"},
{file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b"},
{file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177"},
{file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef"},
{file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103"},
{file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da"},
{file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d"},
{file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041"},
{file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1"},
{file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1"},
{file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911"},
{file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3"},
{file = "aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd"},
{file = "aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706"},
{file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36f6c973e003dc9b0bb4e8492a643641ea8ef0e97ff7aaa5c0f53d68839357b4"},
{file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6cbfc73179bd67c229eb171e2e3745d2afd5c711ccd1e40a68b90427f282eab1"},
{file = "aiohttp-3.12.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e8b27b2d414f7e3205aa23bb4a692e935ef877e3a71f40d1884f6e04fd7fa74"},
{file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eabded0c2b2ef56243289112c48556c395d70150ce4220d9008e6b4b3dd15690"},
{file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:003038e83f1a3ff97409999995ec02fe3008a1d675478949643281141f54751d"},
{file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b6f46613031dbc92bdcaad9c4c22c7209236ec501f9c0c5f5f0b6a689bf50f3"},
{file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c332c6bb04650d59fb94ed96491f43812549a3ba6e7a16a218e612f99f04145e"},
{file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fea41a2c931fb582cb15dc86a3037329e7b941df52b487a9f8b5aa960153cbd"},
{file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:846104f45d18fb390efd9b422b27d8f3cf8853f1218c537f36e71a385758c896"},
{file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d6c85ac7dd350f8da2520bac8205ce99df4435b399fa7f4dc4a70407073e390"},
{file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5a1ecce0ed281bec7da8550da052a6b89552db14d0a0a45554156f085a912f48"},
{file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5304d74867028cca8f64f1cc1215eb365388033c5a691ea7aa6b0dc47412f495"},
{file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:64d1f24ee95a2d1e094a4cd7a9b7d34d08db1bbcb8aa9fb717046b0a884ac294"},
{file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:119c79922a7001ca6a9e253228eb39b793ea994fd2eccb79481c64b5f9d2a055"},
{file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb18f00396d22e2f10cd8825d671d9f9a3ba968d708a559c02a627536b36d91c"},
{file = "aiohttp-3.12.13-cp39-cp39-win32.whl", hash = "sha256:0022de47ef63fd06b065d430ac79c6b0bd24cdae7feaf0e8c6bac23b805a23a8"},
{file = "aiohttp-3.12.13-cp39-cp39-win_amd64.whl", hash = "sha256:29e08111ccf81b2734ae03f1ad1cb03b9615e7d8f616764f22f71209c094f122"},
{file = "aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce"},
]
[package.dependencies]
aiohappyeyeballs = ">=2.5.0"
aiosignal = ">=1.1.2"
async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""}
attrs = ">=17.3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
propcache = ">=0.2.0"
yarl = ">=1.17.0,<2.0"
[package.extras]
speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""]
[[package]]
name = "aiomqtt"
version = "2.3.0"
description = "The idiomatic asyncio MQTT client, wrapped around paho-mqtt"
optional = false
python-versions = "<4.0,>=3.8"
groups = ["main"]
files = [
{file = "aiomqtt-2.3.0-py3-none-any.whl", hash = "sha256:127926717bd6b012d1630f9087f24552eb9c4af58205bc2964f09d6e304f7e63"},
{file = "aiomqtt-2.3.0.tar.gz", hash = "sha256:312feebe20bc76dc7c20916663011f3bd37aa6f42f9f687a19a1c58308d80d47"},
]
[package.dependencies]
paho-mqtt = ">=2.1.0,<3.0.0"
[[package]]
name = "aioserial"
version = "1.3.1"
description = "An asynchronous serial port library of Python"
optional = false
python-versions = ">=3.6,<4.0"
groups = ["main"]
files = [
{file = "aioserial-1.3.1.tar.gz", hash = "sha256:702bf03b0eb84b8ef2d8dac5cb925e1e685dce98f77b125569bc6fd2b3b58228"},
]
[package.dependencies]
pyserial = "*"
[[package]]
name = "aiosignal"
version = "1.3.2"
description = "aiosignal: a list of registered asynchronous callbacks"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"},
{file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"},
]
[package.dependencies]
frozenlist = ">=1.1.0"
[[package]]
name = "async-timeout"
version = "5.0.1"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version == \"3.10\""
files = [
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
]
[[package]]
name = "attrs"
version = "25.3.0"
description = "Classes Without Boilerplate"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
{file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
]
[package.extras]
benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
[[package]]
name = "colorzero"
version = "2.0"
description = "Yet another Python color library"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "colorzero-2.0-py2.py3-none-any.whl", hash = "sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8"},
{file = "colorzero-2.0.tar.gz", hash = "sha256:e7d5a5c26cd0dc37b164ebefc609f388de24f8593b659191e12d85f8f9d5eb58"},
]
[package.dependencies]
setuptools = "*"
[package.extras]
doc = ["pkginfo", "sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "frozenlist"
version = "1.7.0"
description = "A list-like structure which implements collections.abc.MutableSequence"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"},
{file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"},
{file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"},
{file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"},
{file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"},
{file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"},
{file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"},
{file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"},
{file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"},
{file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"},
{file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"},
{file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"},
{file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"},
{file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"},
{file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"},
{file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"},
{file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"},
{file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"},
{file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"},
{file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"},
{file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"},
{file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"},
{file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"},
{file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"},
{file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"},
{file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"},
{file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"},
{file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"},
{file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"},
{file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"},
{file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"},
{file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"},
{file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"},
{file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"},
{file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"},
{file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"},
{file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"},
{file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"},
{file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"},
{file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"},
{file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"},
{file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"},
{file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"},
{file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"},
{file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"},
{file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"},
{file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"},
{file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"},
{file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"},
{file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"},
{file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"},
{file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"},
{file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"},
{file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"},
{file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"},
{file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"},
{file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"},
{file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"},
{file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"},
{file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"},
{file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"},
{file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"},
{file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"},
{file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"},
{file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"},
{file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"},
{file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"},
{file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"},
{file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"},
{file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"},
{file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"},
{file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"},
{file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"},
{file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"},
{file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"},
{file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"},
{file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"},
{file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"},
{file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"},
{file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"},
{file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"},
{file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"},
{file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"},
{file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"},
{file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"},
{file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"},
{file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"},
{file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"},
{file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"},
{file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"},
{file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"},
{file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"},
{file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"},
{file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"},
{file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"},
{file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"},
{file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"},
{file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"},
{file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"},
{file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"},
{file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"},
{file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"},
{file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"},
{file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"},
]
[[package]]
name = "gpiozero"
version = "2.0.1"
description = "A simple interface to GPIO devices with Raspberry Pi"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "gpiozero-2.0.1-py3-none-any.whl", hash = "sha256:8f621de357171d574c0b7ea0e358cb66e560818a47b0eeedf41ce1cdbd20c70b"},
{file = "gpiozero-2.0.1.tar.gz", hash = "sha256:d4ea1952689ec7e331f9d4ebc9adb15f1d01c2c9dcfabb72e752c9869ab7e97e"},
]
[package.dependencies]
colorzero = "*"
[package.extras]
doc = ["sphinx (>=4.0)", "sphinx-rtd-theme (>=1.0)"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "idna"
version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "multidict"
version = "6.6.4"
description = "multidict implementation"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"},
{file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"},
{file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"},
{file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"},
{file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"},
{file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"},
{file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"},
{file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"},
{file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"},
{file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"},
{file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"},
{file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"},
{file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"},
{file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"},
{file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"},
{file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"},
{file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"},
{file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"},
{file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"},
{file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"},
{file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"},
{file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"},
{file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"},
{file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"},
{file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"},
{file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"},
{file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"},
{file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"},
{file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"},
{file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"},
{file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"},
{file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"},
{file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"},
{file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"},
{file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"},
{file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"},
{file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"},
{file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"},
{file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"},
{file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"},
{file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"},
{file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"},
{file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"},
{file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"},
{file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"},
{file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"},
{file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"},
{file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"},
{file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"},
{file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"},
{file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"},
{file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"},
{file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"},
{file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"},
{file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"},
{file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"},
{file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"},
{file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"},
{file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"},
{file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"},
{file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"},
{file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"},
{file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"},
{file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"},
{file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"},
{file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"},
{file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"},
{file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"},
{file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"},
{file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"},
{file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"},
{file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"},
{file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"},
{file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"},
{file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"},
{file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"},
{file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"},
{file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"},
{file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"},
{file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"},
{file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"},
{file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"},
{file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"},
{file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"},
{file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"},
{file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"},
{file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"},
{file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"},
{file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"},
{file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"},
{file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"},
{file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"},
{file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"},
{file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"},
{file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"},
{file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"},
{file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"},
{file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"},
{file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"},
{file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"},
{file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"},
{file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"},
{file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"},
{file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"},
{file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"},
{file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"},
{file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"},
{file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"},
{file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"},
{file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"},
]
[package.dependencies]
typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
[[package]]
name = "paho-mqtt"
version = "2.1.0"
description = "MQTT version 5.0/3.1.1 client class"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"},
{file = "paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834"},
]
[package.extras]
proxy = ["pysocks"]
[[package]]
name = "propcache"
version = "0.3.2"
description = "Accelerated property cache"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"},
{file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"},
{file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"},
{file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"},
{file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"},
{file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"},
{file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"},
{file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"},
{file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"},
{file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"},
{file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"},
{file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"},
{file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"},
{file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"},
{file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"},
{file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"},
{file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"},
{file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"},
{file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"},
{file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"},
{file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"},
{file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"},
{file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"},
{file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"},
{file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"},
{file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"},
{file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"},
{file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"},
{file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"},
{file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"},
{file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"},
{file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"},
{file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"},
{file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"},
{file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"},
{file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"},
{file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"},
{file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"},
{file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"},
{file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"},
{file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"},
{file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"},
{file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"},
{file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"},
{file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"},
{file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"},
{file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"},
{file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"},
{file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"},
{file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"},
{file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"},
{file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"},
{file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"},
{file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"},
{file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"},
{file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"},
{file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"},
{file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"},
{file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"},
{file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"},
{file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"},
{file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"},
{file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"},
{file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"},
{file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"},
{file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"},
{file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"},
{file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"},
{file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"},
{file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"},
{file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"},
{file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"},
{file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"},
{file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"},
{file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"},
{file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"},
{file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"},
{file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"},
{file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"},
{file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"},
{file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"},
{file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"},
{file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"},
{file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"},
{file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"},
{file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"},
{file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"},
{file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"},
{file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"},
{file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"},
{file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"},
{file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"},
{file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"},
{file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"},
{file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"},
{file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"},
{file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"},
{file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"},
]
[[package]]
name = "pyserial"
version = "3.5"
description = "Python Serial Port Extension"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"},
{file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"},
]
[package.extras]
cp2110 = ["hidapi"]
[[package]]
name = "setuptools"
version = "75.6.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"},
{file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"},
]
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.7.0) ; sys_platform != \"cygwin\""]
core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (>=1.12,<1.14)", "pytest-mypy"]
[[package]]
name = "smbus2"
version = "0.5.0"
description = "smbus2 is a drop-in replacement for smbus-cffi/smbus-python in pure Python"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "smbus2-0.5.0-py2.py3-none-any.whl", hash = "sha256:1a15c3b9fa69357beb038cc0b5d37939702f8bfde1ddc89ca9f17d8461dbe949"},
{file = "smbus2-0.5.0.tar.gz", hash = "sha256:4a5946fd82277870c2878befdb1a29bb28d15cda14ea4d8d2d54cf3d4bdcb035"},
]
[package.extras]
docs = ["sphinx (>=1.5.3)"]
qa = ["flake8"]
[[package]]
name = "telnetlib3"
version = "2.0.4"
description = "Python 3 asyncio Telnet server and client Protocol library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "telnetlib3-2.0.4-py2.py3-none-any.whl", hash = "sha256:b3c0f984a7fb1b6ee16e6fdaa410c56389b0dc492174a99c6661b1ba4c9d457d"},
{file = "telnetlib3-2.0.4.tar.gz", hash = "sha256:dbcbc16456a0e03a62431be7cfefff00515ab2f4ce2afbaf0d3a0e51a98c948d"},
]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
groups = ["main"]
files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
[[package]]
name = "typing-extensions"
version = "4.14.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version == \"3.10\""
files = [
{file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"},
{file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"},
]
[[package]]
name = "yarl"
version = "1.20.1"
description = "Yet another URL library"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"},
{file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"},
{file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"},
{file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"},
{file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"},
{file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"},
{file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"},
{file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"},
{file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"},
{file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"},
{file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"},
{file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"},
{file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"},
{file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"},
{file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"},
{file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"},
{file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"},
{file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"},
{file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"},
{file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"},
{file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"},
{file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"},
{file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"},
{file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"},
{file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"},
{file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"},
{file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"},
{file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"},
{file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"},
{file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"},
{file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"},
{file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"},
{file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"},
{file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"},
{file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"},
{file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"},
{file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"},
{file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"},
{file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"},
{file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"},
{file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"},
{file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"},
{file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"},
{file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"},
{file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"},
{file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"},
{file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"},
{file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"},
{file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"},
{file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"},
{file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"},
{file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"},
{file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"},
{file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"},
{file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"},
{file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"},
{file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"},
{file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"},
{file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"},
{file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"},
{file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"},
{file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"},
{file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"},
{file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"},
{file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"},
{file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"},
{file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"},
{file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"},
{file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"},
{file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"},
{file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"},
{file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"},
{file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"},
{file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"},
{file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"},
{file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"},
{file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"},
{file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"},
{file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"},
{file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"},
{file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"},
{file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"},
{file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"},
{file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"},
{file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"},
{file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"},
{file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"},
{file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"},
{file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"},
{file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"},
{file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"},
{file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"},
{file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"},
{file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"},
{file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"},
{file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"},
{file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"},
{file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"},
{file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"},
{file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"},
{file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"},
{file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"},
{file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"},
{file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"},
]
[package.dependencies]
idna = ">=2.0"
multidict = ">=4.0"
propcache = ">=0.2.1"
[metadata]
lock-version = "2.1"
python-versions = "^3.10"
content-hash = "b0cd468209f60a9f821063be3900cc54677b0e93b7959a05971c1594d9d8c06d"

View file

@ -0,0 +1,147 @@
MOCK = False
import aiomqtt
import asyncio
from smbus2 import SMBus
import toml
import os
room: str
use_offset = False
""" 0 1 2 3 """
relayMasks = [0b0001, 0b0010, 0b0100, 0b1000] #probably ne rabim
bus = SMBus(1)
i2c_map = {
'main': -1,
'side': -1,
}
relMapping = {
'service_down': 0,
'service_up': 1,
'down': 2,
'up': 3
}
currentState = {
'main': 0b0000,
'side': 0b0000
}
async def msgRelayBoard(projSelect, command, state: bool):
# Select the correct relay board
i2c_addr = i2c_map[projSelect]
# Get the relay position for the given command
maskShift = relMapping[command]
# Set the corresponding bit
mask = (1 << maskShift)
if state:
currentState[projSelect] = currentState[projSelect] | mask
else:
currentState[projSelect] = currentState[projSelect] & ~mask
# Write it to the I2C bus (0x10 is the register for relay states)
bus.write_byte_data(i2c_addr, 0x10, currentState[projSelect])
print(projSelect, "{:04b}".format(currentState[projSelect]))
"""
SrvDwn SrvUp OpDwn OpUp
MAIN: 0x42 0001 0010 0100 1000
SIDE: 0x43 0001 0010 0100 1000
"""
#old board
"""
MAIN: SrvDwn SrvUp OpDwn OpUp
0 1 2 3
SIDE: 4 5 6 7
"""
#dej like bolš to podukumentiraj or smth
async def task_command2relays(controlClient: aiomqtt.Client):
"""Read commands from MQTT and send them to the relays"""
await controlClient.subscribe(f"{room}/projectors/+/lift/#")
async for mesg in controlClient.messages:
msgTopicArr = mesg.topic.value.split('/')
value = mesg.payload.decode()
if mesg.topic.matches(f'{room}/projectors/+/lift/manual/+'):
command = msgTopicArr[-1]
projSel = msgTopicArr[-4]
if projSel not in ("main", "side") or command not in relMapping.keys() or value not in ("0", "1"):
print("Invalid manual command:", projSel, command, value)
continue
await msgRelayBoard(projSel, command, value == '1')
# Service move
if "service" in command:
# Manual control makes the position unknown, so we clear it
if value == "1":
status = "MOVING"
else:
status = "STOPPED"
await controlClient.publish(f'{room}/projectors/{projSel}/lift/status', payload=status, qos=1, retain=True)
# Normal move
else:
# HACK: if the press is too short it doesn't register, so we sleep for a bit
if value == "1":
await asyncio.sleep(.2)
#print("Pushing \'off\' to other relays to prevent conflicts")
elif mesg.topic.matches(f'{room}/projectors/+/lift/goto'):
projSel = msgTopicArr[-3]
if projSel not in ("main", "side") or value not in ("UP", "DOWN"):
print("Invalid goto command:", projSel, value)
continue
# Clear manual control
await msgRelayBoard(projSel, "service_down", False)
await msgRelayBoard(projSel, "service_up", False)
if value == "UP":
other = "down"
elif value == "DOWN":
other = "up"
await msgRelayBoard(projSel, other, False)
# Click the buttom for a bit and release it, then publish that the lift has moved
await msgRelayBoard(projSel, value.lower(), True)
await asyncio.sleep(1)
await msgRelayBoard(projSel, value.lower(), False)
await controlClient.publish(f'{room}/projectors/{projSel}/lift/status', payload=value, qos=1, retain=True)
await asyncio.sleep(0.01)
async def main():
global i2c_map, room
config_file = os.getenv('MM_CONFIG_PATH', './malinaConfig.toml')
conf = toml.load(config_file)
room = conf['global']['room']
mqttHost = conf['global']['mqttHost']
mqttPort = conf['global']['mqttPort']
projMotors = conf["projector_motors"]
i2c_map['main'] = projMotors["main"]['i2c_address']
i2c_map['side'] = projMotors["side"]['i2c_address']
async with aiomqtt.Client(mqttHost, mqttPort) as client:
task_control = asyncio.create_task(task_command2relays(client))
await asyncio.gather(task_control)
if __name__ == '__main__':
asyncio.run(main())

View file

@ -15,6 +15,8 @@ gpiozero = "^2.0.1"
telnetlib3 = "^2.0.4"
toml = "^0.10.2"
smbus2 = "^0.5.0"
aiohttp = "^3.12.13"
multidict = "^6.6.4"
[build-system]

View file

@ -0,0 +1,251 @@
import asyncio
import serial
import aioserial
import aiomqtt
from tse_serial_interpreter import *
from dataclasses import dataclass
import os
import sys
import toml
import serial.tools.list_ports
# GLOBALS
room: str
platnoBckgdMoving = {
'main': False,
'side': False,
}
# serPath = '/dev/serial/by-id/'
# devLst = os.listdir(serPath)
# if len(devLst) < 1:
# sys.exit("No serial device found.")
# serDev = devLst[0]
portList = serial.tools.list_ports.comports()
candidates = ('/dev/ttyUSB0', '/dev/ttyACM0', '/dev/ttyUSB1', '/dev/ttyACM1')
serport = ''
# Find first serial port that exists
for port in portList:
if port.device in candidates:
serport = port.device
break
else:
print(portList)
sys.exit("No serial port found")
aser: aioserial.AioSerial = aioserial.AioSerial(
port=serport,
baudrate=1200,
parity=serial.PARITY_NONE,
bytesize=serial.EIGHTBITS,
stopbits=serial.STOPBITS_ONE
)
# altho itak je ta script za tist specific tse box in so vsi isti
mapping_toggles = {
"master": 1,
"audio": 2,
"projectors": 3,
}
# 4 is not connected to anything
platno_mapping = {
"main": {
"DOWN": 5,
"UP": 6
},
"side": {
"DOWN": 7,
"UP": 8
}
}
shades_mapping = {
"DOWN": 9,
"UP": 10
}
reverse_lookup = {
1: ("power", "master", ""),
2: ("power", "audio", ""),
3: ("power", "projectors", ""),
5: ("platno", "main", "MOVING_DOWN"),
6: ("platno", "main", "MOVING_UP"),
7: ("platno", "side", "MOVING_DOWN"),
8: ("platno", "side", "MOVING_UP"),
9: ("shades", "MOVING_DOWN"),
10: ("shades", "MOVING_UP")
}
#TODO finish this
#TODO add doc comment to every task funciton
async def task_status2mqtt(statusClient: aiomqtt.Client):
while True:
data = await aser.read_until_async()
data = data.decode(errors='ignore').strip()
print("TSE box sent: " + data)
relState = resp_to_relay_state(data)
if relState.relay_id is None:
continue # TODO handling - nebi smelo do tega prit anyway
publishTopic = f"{room}/"
publishPayload = ""
lookup = reverse_lookup[relState.relay_id]
if lookup[0] == "power":
publishTopic += f"{lookup[0]}/{lookup[1]}/status"
publishPayload = '1' if relState.state else '0'
elif lookup[0] == "shades":
publishTopic += f"{lookup[0]}/status"
publishPayload = 'STOPPED' if not relState.state else lookup[1]
elif lookup[0] == "platno":
publishTopic += f"projectors/{lookup[1]}/{lookup[0]}/status"
publishPayload = 'UNKNOWN' if not relState.state else lookup[2]
#publishTopic = f"{room}/projectors/{}"
#publishPayload = "1" if relState.state else "0"
print("Publishing [" + publishPayload + "] to topic [" + publishTopic + "]")
await statusClient.publish(publishTopic, payload=publishPayload)
await asyncio.sleep(0.2)
async def executeAndPublish(mqttClient, pubTopic, pubPayload, relStat):
setRelayCmd = relay_state_to_cmd(relStat)
#print("Received: [" + topicVal + "] payload: [" + pubPayload + "]")
print("Sending to TSE box: " + setRelayCmd)
print(f"Also publishing topic [{pubTopic}] with status [{pubPayload}]")
print()
await aser.write_async(bytes(setRelayCmd + '\r\n', "ascii"))
await mqttClient.publish(pubTopic, payload=pubPayload)
await asyncio.sleep(0.1) #TODO probably remove
async def handleTsePower(client, sysSelect, cmd):
rel = RelayState(mapping_toggles[sysSelect], cmd == '1')
await executeAndPublish(client, f'{room}/power/{sysSelect}/status', cmd, rel)
async def handleTseSencilo(client, cmd):
#relName = tval.split('/')[3]
topicPub = f'{room}/shades/status'
if cmd == "MOVE_UP":
rel = RelayState(shades_mapping['UP'], True)
await executeAndPublish(client, topicPub, "MOVING_UP", rel)
elif cmd == "MOVE_DOWN":
rel = RelayState(shades_mapping['DOWN'], True)
await executeAndPublish(client, topicPub, "MOVING_DOWN", rel)
else:
await executeAndPublish(client, topicPub, "STOPPED", RelayState(shades_mapping['UP'], False))
await executeAndPublish(client, topicPub, "STOPPED", RelayState(shades_mapping['DOWN'], False))
async def platnoTimeout(mqttClient, pubTopic, pubPayload, relStat: RelayState, intent, select):
global platnoBckgdMoving
await asyncio.sleep(25) #TODO time actual delay
relStat.state = False
await executeAndPublish(mqttClient, pubTopic, intent, relStat)
platnoBckgdMoving[select] = False #TODO properly document why this is here and what it does
async def handleTsePlatno(client, proj, cmdType, cmd):
global platnoBckgdMoving
pubTop = f'{room}/projectors/{proj}/platno/status'
if not (proj == "main" or proj == "side"):
return
if platnoBckgdMoving[proj]:
if cmd == 'STOP':
pubPld = 'UNKNOWN'
rel1 = RelayState(platno_mapping[proj]['UP'], False)
rel2 = RelayState(platno_mapping[proj]['DOWN'], False)
await executeAndPublish(client,pubTop, pubPld, rel1)
await executeAndPublish(client,pubTop, pubPld, rel2)
platnoBckgdMoving[proj] = False
else:
print("Ignored command because already moving", proj, cmdType, cmd)
return
if cmdType == 'move':
print('move')
rel: RelayState
if cmd == 'UP':
rel = RelayState(platno_mapping[proj]['UP'], True)
pubPld = 'MOVING_UP'
elif cmd == 'DOWN':
rel = RelayState(platno_mapping[proj]['DOWN'], True)
pubPld = 'MOVING_DOWN'
else:
return # in case of invalid input skip
platnoBckgdMoving[proj] = True #TODO rename to moving, add comment how it works
await executeAndPublish(client, pubTop, pubPld, rel)
#TODO WTF HAPPENS IF YOU SEND UP AND DOWN AT ONCE?? (screenshot?)
#TODO daj ignore print ko je locked up
elif cmdType == 'goto':
print('received GOTO')
rel: RelayState
if cmd == 'UP':
rel = RelayState(platno_mapping[proj]['UP'], True)
intent = 'UP'
pubPld = 'MOVING_UP'
elif cmd == 'DOWN':
rel = RelayState(platno_mapping[proj]['DOWN'], True)
intent = 'DOWN'
pubPld = 'MOVING_DOWN'
else:
return # in case of invalid input skip
platnoBckgdMoving[proj] = True
# pubPld = 'MOVING'
await executeAndPublish(client, pubTop, pubPld, rel)
asyncio.create_task(platnoTimeout(client, pubTop, pubPld, rel, intent, proj))
else:
print('unknown command')
async def task_command2serial(controlClient: aiomqtt.Client):
await controlClient.subscribe(f"{room}/#")
async for mesg in controlClient.messages:
topicVal = mesg.topic.value
msgTopic = mesg.topic.value.split('/')
cmnd = mesg.payload.decode()
if mesg.topic.matches(f'{room}/projectors/+/platno/move') or mesg.topic.matches(f'{room}/projectors/+/platno/goto'):
proj = msgTopic[-3]
cmdType = msgTopic[-1] # move / goto
print(f'on {topicVal} received: {cmnd}')
await handleTsePlatno(controlClient, proj, cmdType, cmnd) #TODO odzadi index
elif mesg.topic.matches(f'{room}/power/+/set'):
systype = msgTopic[-2]
print(f'on {topicVal} received: {cmnd}')
print('calling power')
await handleTsePower(controlClient, systype, cmnd)
elif mesg.topic.matches(f'{room}/shades/move'):
print(f'on {topicVal} received: {cmnd}')
print('calling move')
await handleTseSencilo(controlClient, cmnd)
else:
continue
# code after if block doesnt execute in this case
#print("after")
await asyncio.sleep(0.01) #TODO do we need this? (probably)
async def main():
global room
config_file = os.getenv('MM_CONFIG_PATH', './malinaConfig.toml')
conf = toml.load(config_file)
room = conf['global']['room']
mqttHost = conf['global']['mqttHost']
mqttPort = conf['global']['mqttPort']
async with aiomqtt.Client(mqttHost, mqttPort) as client:
task_status = asyncio.create_task(task_status2mqtt(client))
task_control = asyncio.create_task(task_command2serial(client))
await asyncio.gather(task_status, task_control)
if __name__ == '__main__':
asyncio.run(main())

15
docs/Lučke.md Normal file
View file

@ -0,0 +1,15 @@
Lučke v velikih predavalnicah
===
## Mapping po predavalnicah
### P01
- 1 - Neonke tabla
- 2 - Neonke sredina
- 3 - Neonke vrata
- 4 - Okrogle 2 (vse razen ene stropne luči zgoraj)
- 5 - Neonke začetek
- 6 - Reflektorji 2 (ne dela)
- 7 - Okrogle 1 (ena stropna luč zgoraj)
- 8 - Stopnice

View file

@ -1,5 +1,6 @@
Prostor,Naprava,Model,IPv4,Naslov
-,Multimedija server,,192.168.192.9,multimedija.fri1.uni-lj.si
P01,Malinca,Raspberry Pi 5,192.168.192.42,
P01,Crestron krmilnik,Crestron AV3,192.168.192.10,
P01,Crestron tablica,Crestron TSW550,192.168.192.11,
P01,Glavni projektor,Barco G62,192.168.192.12,

1 Prostor Naprava Model IPv4 Naslov
2 - Multimedija server 192.168.192.9 multimedija.fri1.uni-lj.si
3 P01 Malinca Raspberry Pi 5 192.168.192.42
4 P01 Crestron krmilnik Crestron AV3 192.168.192.10
5 P01 Crestron tablica Crestron TSW550 192.168.192.11
6 P01 Glavni projektor Barco G62 192.168.192.12

View file

@ -1,8 +0,0 @@
[Match]
Name=eth0
[Network]
Address=192.168.192.42
Gateway=192.168.192.1

View file

@ -1,13 +0,0 @@
[Unit]
Description=Extron audio matrix control
After=multi-user.target
[Service]
ExecStart=/usr/bin/python3 /home/rpi/extron_audio_matrix_telnet_control.py
Type=simple
Restart=always
User=kat
Group=kat
[Install]
WantedBy=multi-user.target

17
frontend/README.md Normal file
View file

@ -0,0 +1,17 @@
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<title>MMM krmilnik</title>
</head>
<body>
<div id="app"></div>

114
frontend/src/App.vue Normal file
View file

@ -0,0 +1,114 @@
<script setup lang="ts">
import {ref, onMounted, watch} from 'vue'
import MainPage from './components/pages/MainPage.vue';
import VideoPage from './components/pages/VideoPage.vue';
import VerticalTabs from './components/tabs/VerticalTabs.vue';
import Tab from './components/tabs/Tab.vue';
import LightingPage from './components/pages/LightingPage.vue';
import ServisPage from './components/pages/ServisPage.vue';
import {$mqtt} from "vue-paho-mqtt"
import AudioPage from "@/components/pages/AudioPage.vue";
document.addEventListener('contextmenu', event => event.preventDefault());
let urlParams = new URLSearchParams(window.location.search);
const currentRoom = ref(urlParams.get('room') || 'none')
const pageNum = ref(0)
const mqttStat = ref($mqtt.status())
watch(mqttStat, (_, newState) => {
})
const servisActuve = ref(false)
watch(pageNum, (_, newState) => {
console.log(pageNum)
// console.log(newState)
servisActuve.value = (pageNum.value == 4);
})
//TODO display none namest uno
</script>
<template>
<div v-if="currentRoom == 'none'">
<h1>Missing room parameter! Add e.g. <code>?room=P01</code> to the URL</h1>
</div>
<div v-else id="wrapper">
<header class="sidebar">
<img class="logo" src="https://fri.uni-lj.si/sites/all/themes/fri_theme/images/fri_logo.png"/>
<h1>{{ currentRoom.toUpperCase() }}</h1>
<VerticalTabs id="nav">
<Tab @click="pageNum=0" :selected="pageNum==0">Priprava</Tab>
<Tab @click="pageNum=1" :selected="pageNum==1">Video</Tab>
<Tab @click="pageNum=2" :selected="pageNum==2">Audio</Tab>
<Tab @click="pageNum=3" :selected="pageNum==3">Lučke</Tab>
<Tab @click="pageNum=4" :selected="pageNum==4">Servis</Tab>
</VerticalTabs>
<div class="mstatus" v-if="$mqtt.status() != 'connected'">{{ $mqtt.status()?.toUpperCase() }}</div>
<button class="reload" v-if="$mqtt.status() != 'connected'" onclick="window.location.reload()">RELOAD</button>
</header>
<main>
<MainPage :class="{'hiddenPage': pageNum != 0}" :room="currentRoom"/>
<VideoPage :class="{'hiddenPage': pageNum != 1}" :room="currentRoom"/>
<AudioPage :class="{'hiddenPage': pageNum != 2}" :room="currentRoom"/>
<LightingPage :class="{'hiddenPage': pageNum != 3}" :room="currentRoom"/>
<ServisPage :class="{'hiddenPage': pageNum != 4}" :room="currentRoom" :currently-active="servisActuve"/>
</main>
</div>
</template>
<style scoped>
.reload {
opacity: .8;
font-size: .8em;
margin: 0 3em;
}
.hiddenPage {
display: none !important
}
#wrapper {
width: 100vw;
height: 100vh;
display: flex;
}
main {
flex: 1;
padding: 1rem;
max-height: 100vh;
overflow-y: auto;
display: flex;
}
main > * {
flex: 1;
}
.sidebar {
max-width: 10em;
}
.logo {
max-width: 100%;
padding-left: .8rem;
padding-top: .8rem;
}
.mstatus {
text-align: center;
opacity: .4;
}
</style>

View file

@ -0,0 +1,113 @@
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
:root {
--color-text: #000;
--color-background: #EEE;
--color-brand-ul-red: #e03127;
--color-brand-ul-light-grey: #E6E7E8;
--color-brand-ul-medium-grey: #A7A8AA;
--color-brand-ul-dark-grey: #58595b;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family: "Noto Sans", sans-serif;
font-size: 17px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
user-select: none;
}
html, body {
touch-action: manipulation;
margin: 0;
padding: 0;
}
h1 {
font-weight: bold;
font-size: 1.5em;
color: #58595b;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
}
.mstatus {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
button {
--bg-default: #ffffff;
--bg-active: lightgray;
background: var(--bg-default);
font-size: 1.3em;
font-weight: bold;
}
button {
margin: .1rem;
border: 1px solid #000000;
border-radius: 3px;
background: #ffffff;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
/* The last touched button keeps focus, so we shoudln't highlight that */
button:focus:not(:active) {
background: var(--bg-default);
}
button:active {
background: var(--bg-active);
}
h1 {
text-align: center;
}
h3 {
font-weight: bold;
}
.currentlyActive {
background-color: orange !important
}

View file

@ -0,0 +1,85 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import { ref, onMounted, reactive, watch } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
const props = defineProps({
room: String,
big: [Boolean, null]
})
const topicstrs = [ //TODO everything else
props.room + '/power/audio/status',
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
const audioStatus = ref(false)
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
audioStatus.value = msg == '1'
}
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
async function setAudio() {
let topicPref = props.room + "/power/audio/set"
let command = '0'
if (!audioStatus.value) {
command = '1'
}
publishMQTTMsg(topicPref, command)
//audioStatus.value = command == '1'
}
//TODO organize better, binds, etc.
const roomState = ref(0)
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
</script>
<template>
<div>
<h3>Ozvočenje</h3>
<button @click="setAudio()" :class="{big:big}">
{{ audioStatus ? 'IZKLOP' : 'VKLOP' }}
</button>
</div>
</template>
<style scoped>
button {
padding: 1rem;
width: 100%;
}
.big {
font-size: 1.8rem;
height: 5em;
}
</style>

View file

@ -0,0 +1,128 @@
<script setup lang="ts">
import {ref, onMounted, reactive, watch} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
import ProjectorShutter from './ProjectorShutter.vue'
const props = defineProps({
room: String,
position: String,
})
const status = reactive({
power: '?',
platno: '?',
lift: '?',
wait: false,
})
$mqtt.subscribe(props.room + '/projectors/' + props.position + '/status/power', (message) => {
console.debug("a")
status.power = message;
});
$mqtt.subscribe(props.room + '/projectors/' + props.position + '/platno/status', (message) => {
status.platno = message;
});
$mqtt.subscribe(props.room + '/projectors/' + props.position + '/lift/status', (message) => {
status.lift = message;
});
function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
function publishMQTTMsg(topic: string, msg: string) {
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Fnr')
console.log('sent')
}
async function startLecture() {
publishMQTTMsg(props.room + '/power/master/set', '1')
publishMQTTMsg(props.room + '/power/audio/set', '1')
publishMQTTMsg(props.room + '/power/projectors/set', '1')
await sleep(500)
publishMQTTMsg(props.room + '/projectors/' + props.position + '/set/power', '1')
publishMQTTMsg(props.room + '/projectors/' + props.position + '/platno/goto', "DOWN")
publishMQTTMsg(props.room + '/projectors/' + props.position + '/lift/goto', 'DOWN')
status.wait = true
setTimeout(() => {
status.wait = false
}, 10000)
}
async function stopLecture() {
publishMQTTMsg(props.room + '/projectors/' + props.position + '/set/power', '0')
publishMQTTMsg(props.room + '/projectors/' + props.position + '/platno/goto', "UP")
// publishMQTTMsg(props.room + '/projectors/' + props.position + 'lift/goto', 'DOWN')
status.wait = true
setTimeout(() => {
status.wait = false
}, 10000)
}
//TODO organize better, binds, etc.
</script>
<template>
<div>
<!-- TODO lepš -->
<div>
<small class="status">
PROJ: {{ status.power }}
PLAT: {{ status.platno }}
LIFT: {{ status.lift }}
</small>
<button class="currentlyActive" @click="stopLecture()" v-if="status.power == '1' || status.platno == 'DOWN'" :class="{waiting: status.wait}">
IZKLOP
</button>
<button @click="startLecture()" v-else="" :class="{waiting: status.wait}">
VKLOP
</button>
<ProjectorShutter :room="props.room" :position="props.position"/>
</div>
</div>
</template>
<style scoped>
.status {
font-size: .8em;
opacity: .8;
}
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
width: 100%;
height: 9rem;
font-size: 1.8rem;
}
.wait {
animation: fade-in-out 1s infinite;
pointer-events: none;
}
@keyframes fade-in-out {
0% {
opacity: .2;
}
50% {
opacity: .8;
}
100% {
opacity: .2;
}
}
</style>

View file

@ -0,0 +1,104 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import {ref, onMounted, reactive} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
import DownIcon from './icons/DownIcon.vue';
import UpIcon from './icons/UpIcon.vue';
const props = defineProps({
room: String,
position: String
})
const topicPrefix = `${props.room}/projectors/${props.position}`
const status = reactive({
status: "-",
})
$mqtt.subscribe(`${topicPrefix}/lift/status`, (message) => {
status.status = message;
});
function publishMQTTMsg(topic: string, msg: string) {
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Fnr')
}
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/lift/')
</script>
<!--
TODO: NE HARDCODANO
-->
<template>
<div style="display:flex; gap: 1rem">
<div>
<h4>Lifti ({{status.status}})</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'manual/up', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'manual/up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'manual/up', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'manual/up', '0')">
<UpIcon/>
</button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'manual/down', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'manual/down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'manual/down', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'manual/down', '0')">
<DownIcon/>
</button>
</div>
<div>
<h4>Servis</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'manual/service_up', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'manual/service_up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'manual/service_up', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'manual/service_up', '0')">
<UpIcon/>
</button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'manual/service_down', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'manual/service_down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'manual/service_down', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'manual/service_down', '0')">
<DownIcon/>
</button>
</div>
</div>
<div>
<div>
<h4>GOTO</h4>
<button
@click="publishMQTTMsg(publishPrefix + 'goto', 'UP')">
<UpIcon/>
</button>
<button
@click="publishMQTTMsg(publishPrefix + 'goto', 'DOWN')">
<DownIcon/>
</button>
</div>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
margin: 0.1rem;
width: 45%;
}
</style>

View file

@ -0,0 +1,122 @@
<script setup lang="ts">
import { reactive } from "vue";
import { $mqtt } from "vue-paho-mqtt";
const props = defineProps({
room: String,
id: Number,
name: String,
dimmable: Boolean,
});
const topicPrefix = `${props.room}/lucke`
const status = reactive({
brightness: 0,
})
$mqtt.subscribe(`${topicPrefix}/brightness/${props.id}`, (message) => {
status.brightness = parseInt(message);
});
function doBrightness(brightness: Number) {
$mqtt.publish(`${topicPrefix}/set/${props.id}`, brightness.toFixed(0), 'Fnr');
}
</script>
<template>
<div v-if="props.dimmable">
<label>{{props.name}}</label>
<input type="range" min="0" max="100" step="10" :value="status.brightness" @input="doBrightness(parseInt(($event.target as HTMLInputElement).value))" >
</div>
<span v-else>
<label>{{props.name}}</label>
<button @click="doBrightness(status.brightness != 0 ? 0 : 100)" :class="{currentlyActive: status.brightness != 0 }">
</button>
</span>
</template>
<style scoped>
.disabled {
opacity: 0.5;
pointer-events: none;
}
button {
border: 1px solid #000000;
height: 36px;
width: 36px;
border-radius: 3px;
background: #ffffff;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
span {
display: inline-flex
}
div {
display: flex;
}
label {
padding: .5rem
}
input[type=range] {
flex: 1;
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
--width: 100%; /* Specific width is required for Firefox. */
background: transparent; /* Otherwise white in Chrome */
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 8.4px;
cursor: pointer;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
background: lightgray;
border-radius: 1.3px;
border: 0.2px solid #010101;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8.4px;
cursor: pointer;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
background: lightgray;
border-radius: 1.3px;
border: 0.2px solid #010101;
}
input[type=range]::-moz-range-thumb {
border: 1px solid #000000;
height: 36px;
width: 32px;
border-radius: 3px;
background: #ffffff;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
-webkit-appearance: none;
border: 1px solid #000000;
height: 36px;
width: 32px;
border-radius: 3px;
background: #ffffff;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
margin-top: -14px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */
}
</style>

View file

@ -0,0 +1,87 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import { ref, onMounted, reactive, watch } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
const props = defineProps({
room: String,
})
const topicstrs = [ //TODO everything else
props.room + '/power/master/status',
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
const MasterStatus = ref(false)
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
MasterStatus.value = msg == '1'
}
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
async function setMaster() {
let topicPref = props.room + "/power/master/set"
let command = '0'
if (!MasterStatus.value) {
command = '1'
}
publishMQTTMsg(topicPref, command)
//audioStatus.value = command == '1'
}
//TODO organize better, binds, etc.
const roomState = ref(0)
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
</script>
<template>
<div>
<!-- TODO lepš -->
<div>
<h3>Sistem</h3>
<button style="" @click="setMaster()">
{{ MasterStatus ? 'IZKLOP' : 'VKLOP' }}
</button>
</div>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
width: 100%;
}
</style>

View file

@ -0,0 +1,123 @@
<script setup lang="ts">
import {ref, computed} from 'vue'
const emit = defineEmits(['submitPasscode'])
defineProps([])
// TODO: unhardocde this shit
const correctCode = '1337'
const passcodeLength = correctCode.length
const enteredCode = ref('')
const status = ref('')
const showStatus = ref(false)
const appendDigit = (digit: string) => {
if (enteredCode.value.length < passcodeLength) {
enteredCode.value += digit
// console.log('entered code', enteredCode.value)
}
}
const clear = () => {
enteredCode.value = ''
status.value = ''
}
const submit = () => {
if (enteredCode.value === correctCode) {
status.value = 'Access Granted'
emit('submitPasscode', true)
} else {
status.value = 'Access Denied'
enteredCode.value = "Access Denied"
showStatus.value = true
}
setTimeout(() => {
clear()
showStatus.value = false
}, 1000)
}
</script>
<template>
<div class="keypad">
<div class="keypadFeedback">
<span v-show="showStatus">Access Denied</span>
<span v-show="!showStatus" v-for="(_, i) in passcodeLength" :key="i">
<span>
{{ enteredCode[i] ? '•' : ' ' }}
</span>
</span>
</div>
<div class="">
<div class="keypadButtons">
<div class="keypadRow">
<button @click="appendDigit('1')">1</button>
<button @click="appendDigit('2')">2</button>
<button @click="appendDigit('3')">3</button>
</div>
<div class="keypadRow">
<button @click="appendDigit('4')">4</button>
<button @click="appendDigit('5')">5</button>
<button @click="appendDigit('6')">6</button>
</div>
<div class="keypadRow">
<button @click="appendDigit('7')">7</button>
<button @click="appendDigit('8')">8</button>
<button @click="appendDigit('9')">9</button>
</div>
<div class="keypadRow">
<button class="" @click="clear">Del</button>
<button @click="appendDigit('0')">0</button>
<button class="" @click="submit">OK</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
button {
margin: 0.1em;
flex: max-content;
padding: 1em;
text-align: center;
align-self: inherit;
justify-content: space-evenly;
}
.keypad {
display: flex;
flex-direction: column;
margin: 1.5em;
align-content: space-evenly;
}
.keypadRow {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-around;
align-content: space-evenly;
}
.keypadFeedback {
display: inline-block;
text-align: center;
background-color: lightgray;
background-clip: border-box;
font-size: 2em;
min-height: 2em;
margin-bottom: 1em;
border: 1px solid #000000;
border-radius: 3px;
background: #ffffff;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
</style>

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import {ref, onMounted, reactive } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
import {ref, onMounted, reactive} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
import DownIcon from './icons/DownIcon.vue';
import UpIcon from './icons/UpIcon.vue';
@ -18,13 +18,13 @@ const topicstrs = [ //TODO everything else
]
const subscriptions =
topicstrs.map(topic => {
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
})
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
@ -70,7 +70,6 @@ function publishMQTTMsg(topic: string, msg: string) {
}
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/platno/')
//TODO organize better, binds, etc.
@ -88,30 +87,55 @@ const platnoStatus = ref(platnoState.UNKNOWN)
<template>
<div>
<!-- <h3>{{ props.room }}</h3> -->
<!-- <form> -->
<!-- TODO lepš -->
<h4>platna {{ props.position }}</h4>
<h4>Platno</h4>
<div class="updown">
<button
@click="publishMQTTMsg(publishPrefix + 'goto', 'UP')"><UpIcon/></button>
@click="publishMQTTMsg(publishPrefix + 'goto', 'UP')">
<UpIcon/>
</button>
<button
@click="publishMQTTMsg(publishPrefix + 'goto', 'DOWN')"><DownIcon/></button>
@click="publishMQTTMsg(publishPrefix + 'goto', 'DOWN')">
<DownIcon/>
</button>
</div>
<div v-if="props.ctrlType == 'service'">
<h5>Manual control</h5>
<div class="updown">
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'UP')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')" >
<UpIcon /></button>
@touchstart="publishMQTTMsg(publishPrefix + 'move', 'UP')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')"
@touchend="publishMQTTMsg(publishPrefix + 'move', 'STOP')">
<UpIcon/>
</button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'DOWN')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')" >
<DownIcon /></button>
@touchstart="publishMQTTMsg(publishPrefix + 'move', 'DOWN')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')"
@touchend="publishMQTTMsg(publishPrefix + 'move', 'STOP')">
<DownIcon/>
</button>
</div>
</div>
<!-- </form> -->
</div>
</template>
<style scoped>
button {
padding: 1rem;
margin: 0.1rem;
width: 45%;
}
.updown {
display: flex
}
.updown button {
flex: 1
}
</style>

View file

@ -0,0 +1,87 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import { ref, onMounted, reactive, watch } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
const props = defineProps({
room: String,
})
const topicstrs = [ //TODO everything else
props.room + '/power/projectors/status',
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
const projectorsStatus = ref(false)
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
projectorsStatus.value = msg == '1'
}
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
async function setProjectors() {
let topicPref = props.room + "/power/projectors/set"
let command = '0'
if (!projectorsStatus.value) {
command = '1'
}
publishMQTTMsg(topicPref, command)
//audioStatus.value = command == '1'
}
//TODO organize better, binds, etc.
const roomState = ref(0)
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
</script>
<template>
<div>
<!-- TODO lepš -->
<div>
<h3>Projektorji</h3>
<button style="" @click="setProjectors()">
{{ projectorsStatus ? 'IZKLOP' : 'VKLOP' }}
</button>
</div>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
width: 100%;
}
</style>

View file

@ -0,0 +1,63 @@
<script setup lang="ts">
import { reactive } from "vue";
import { $mqtt } from "vue-paho-mqtt";
const props = defineProps({
room: String,
position: String,
ctrltype: [String, null],
});
const topicPrefix = `${props.room}/projectors/${props.position}`
const status = reactive({
power: false,
shutter: false,
freeze: false,
})
function bindBoolean(name: keyof typeof status) {
$mqtt.subscribe(`${topicPrefix}/status/${name}`, (message) => {
status[name] = message == "1";
console.debug(name, "=", message == "1")
});
}
bindBoolean("power")
bindBoolean("shutter")
bindBoolean("freeze")
function doCommand(command: keyof typeof status, shutter: boolean) {
$mqtt.publish(`${topicPrefix}/set/${command}`, shutter ? "1" : "0", 'Fnr');
console.debug("SET SHUTTER", shutter)
}
</script>
<template>
<div class="btns">
<button @click="doCommand('shutter', !status.shutter)" :class="{ disabled: !status.power, currentlyActive: status.power && status.shutter }">
SHUTTER
</button>
<button @click="doCommand('freeze', !status.freeze)" :class="{ disabled: !status.power, currentlyActive: status.power && status.freeze }">
FREEZE
</button>
</div>
</template>
<style scoped>
.disabled {
opacity: 0.5;
pointer-events: none;
}
.btns {
margin-top: 1rem;
display: flex;
justify-content: center;
gap: .5em;
}
button {
padding: 1rem;
min-width: 40%;
}
</style>

View file

@ -7,15 +7,18 @@ import { $mqtt } from 'vue-paho-mqtt'
const props = defineProps({
room: String,
position: String,
ctrlType: [String, null]
ctrltype: [String, null]
})
const topicstrs = [ //TODO everything else
props.room + '/projectors/' + props.position + '/status/power',
props.room + '/projectors/' + props.position + '/status/shutter',
props.room + '/projectors/' + props.position + '/status/freeze',
props.room + '/projectors/' + props.position + '/status',
]
const isUnreachable = ref(false)
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
@ -27,16 +30,20 @@ const subscriptions =
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
if (topic.includes('status')) {
if (topic.includes('status') && !topic.endsWith('status')) {
//console.log(topic.split('/'))
let typ = topic.split('/')[4]
handleProjectorStatus(typ, msg)
} else {
isUnreachable.value = msg.length > 1
}
}
function handleProjectorStatus(typ: string, msg: string) {
console.log('handling status')
//console.log(projStatus)
console.debug(props.room, projStatus.power, projStatus.shutter)
if (typ == 'power') { projStatus.power = msg == '1' }
else if (typ == 'shutter') { projStatus.shutter = msg == '1' }
else if (typ == 'freeze') { projStatus.freeze = msg == '1' }
@ -51,13 +58,13 @@ onMounted(() => {
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
$mqtt.publish(topic, msg, 'Fnr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/command/')
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/set/')
//TODO organize better, binds, etc.
@ -71,21 +78,22 @@ const projStatus = reactive({
<template>
<div>
<span v-if="isUnreachable">CONNECTION FAILED</span>
<!-- TODO lepš -->
<div>
<h5>Power: {{ (projStatus.power ? "ON" : "OFF") }}</h5>
<button @click="publishMQTTMsg(publishPrefix + 'power', (!projStatus.power ? '1' : '0'))">
<h4>Power</h4>
<button @click="publishMQTTMsg(publishPrefix + 'power', (!projStatus.power ? '1' : '0'))" :class="{currentlyActive: projStatus.power}">
Turn {{ projStatus.power ? 'OFF' : 'ON' }}</button>
</div>
<div :class="{ disabled: !projStatus.power }">
<div>
<h5>Shutter: {{ (projStatus.shutter ? "ON" : "OFF") }}</h5>
<button @click="publishMQTTMsg(publishPrefix + 'shutter', (!projStatus.shutter ? '1' : '0'))">
<h4>Shutter</h4>
<button @click="publishMQTTMsg(publishPrefix + 'shutter', (!projStatus.shutter ? '1' : '0'))" :class="{currentlyActive: projStatus.shutter}">
Turn {{ projStatus.shutter ? 'OFF' : 'ON' }}</button>
</div>
<div>
<h5>Freeze image: {{ (projStatus.freeze ? "ON" : "OFF") }}</h5>
<button @click="publishMQTTMsg(publishPrefix + 'freeze', (!projStatus.freeze ? '1' : '0'))">
<h4>Freeze image</h4>
<button @click="publishMQTTMsg(publishPrefix + 'freeze', (!projStatus.freeze ? '1' : '0'))" :class="{currentlyActive: projStatus.freeze}">
Turn {{ projStatus.freeze ? 'OFF' : 'ON' }}</button>
</div>
</div>
@ -94,7 +102,7 @@ const projStatus = reactive({
<style scoped>
.disabled {
opacity: .8;
opacity: .5;
pointer-events: none;
}
button {

View file

@ -0,0 +1,83 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import { ref, onMounted, reactive, watch } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
const props = defineProps({
room: String,
})
const topicstrs = [ //TODO everything else
props.room + '',
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
const audioStatus = ref(false)
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
audioStatus.value = msg == '1'
}
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
async function resetAll() {
let topicPref = props.room + "/power/"
publishMQTTMsg(topicPref + 'audio/set', '0')
publishMQTTMsg(topicPref + 'projectors/set', '1')
publishMQTTMsg(topicPref + 'master/set', '1')
publishMQTTMsg(props.room + "/projectors/main/lift/move/up", '1')
publishMQTTMsg(props.room + "/projectors/side/lift/move/up", '1')
//TODO NOT HARDCODE THISEFEWGJREWGREW
publishMQTTMsg(props.room + "/projectors/main/platno/goto", 'UP')
publishMQTTMsg(props.room + "/projectors/side/platno/goto", 'UP')
//audioStatus.value = command == '1'
}
</script>
<template>
<div>
<!-- TODO lepš -->
<div>
<h3>Reset sist.</h3>
<button style="" @click="resetAll()">
RESET
</button>
</div>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
width: 100%;
}
</style>

View file

@ -0,0 +1,53 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import {ref, onMounted, reactive } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
import DownIcon from '../icons/DownIcon.vue';
import UpIcon from '../icons/UpIcon.vue';
import AudioControlModule from "@/components/AudioControlModule.vue";
const props = defineProps({
room: String,
position: String
})
const topicstrs = [ //TODO everything else
props.room + '/power/audio/set'
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
})
})
</script>
<template>
<div class="page">
<div style="display:flex; gap: 1rem">
<AudioControlModule :room="room" />
</div>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
margin: 0.1rem;
width: 45%;
}
.currentlySelectedPreset {
background-color: orange;
}
</style>

View file

@ -0,0 +1,237 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import {ref, onMounted, reactive} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
import DownIcon from '../icons/DownIcon.vue';
import UpIcon from '../icons/UpIcon.vue';
import LightControl from '../LightControl.vue';
const props = defineProps({
room: String,
position: String
})
const topicstrs = [ //TODO everything else
props.room + '/shades/status',
props.room + '/lucke/preset/current',
]
const lastPreset = ref("-1")
const platnoStatus = ref("UNKNOWN")
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
if (topic.includes('status')) {
//console.log(topic.split('/'))
let typ = topic.split('/')[4]
handlePlatnoStatus(msg)
} else if (topic.includes('current')) {
lastPreset.value = msg
}
}
enum firankState {
UP,
DOWN,
MOVING,
STOPPED
}
const firankStatus = ref(firankState.STOPPED)
function handlePlatnoStatus(msg: string) {
console.log('handling status')
//console.log(projStatus)
platnoStatus.value = msg
let newState: firankState
switch (msg) {
case "UP":
newState = firankState.UP
break
case "DOWN":
newState = firankState.DOWN
break
case "MOVING":
newState = firankState.MOVING
break
default:
newState = firankState.STOPPED
break
}
firankStatus.value = newState
}
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
const publishPrefix = ref(props.room + '/')
// TODO: unhardcode
const showCustom = true;
// TODO: un-hard-code this
const lights = [
{
id: 1,
name: "Tabla",
dimmable: true,
}, {
id: 5,
name: "Začetek",
dimmable: true,
}, {
id: 2,
name: "Sredina",
dimmable: true,
}, {
id: 3,
name: "Vrh",
dimmable: true,
}, {
id: 4,
name: "Vhod 1",
dimmable: false,
}, {
id: 7,
name: "Vhod 2",
dimmable: false,
// }, {
// id: 6,
// name: "Reflektorji 2 (ne dela)",
// dimmable: false,
}, {
id: 8,
name: "Stopnice",
dimmable: false,
}
]
</script>
<template>
<div class="page">
<div class="razsvetljava">
<div class="lightButtons" style="display: flex; flex-direction: column; align-items: stretch">
<div class="row">
<button :class="{currentlyActive: (lastPreset == '4')}"
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '4')">IZKLOP
</button>
<button :class="{currentlyActive: (lastPreset == '3')}"
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '3')">PRIPRAVA
</button>
<div class="editablePreset">
<button :class="{currentlyActive: (lastPreset == '5')}"
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '5')"
v-if="showCustom">
CUSTOM 5
</button>
<button class="saveButton" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/save', '5')">💾</button>
</div>
</div>
<div class="row">
<button :class="{currentlyActive: (lastPreset == '2')}"
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '2')">PREDAVANJE
</button>
<button :class="{currentlyActive: (lastPreset == '1')}"
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '1')">PROJEKCIJA
</button>
<div class="editablePreset">
<button :class="{currentlyActive: (lastPreset == '6')}"
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '6')"
v-if="showCustom">
CUSTOM 6
</button>
<button class="saveButton" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/save', '6')">💾</button>
</div>
</div>
</div>
</div>
<div style="justify-content: center">
<LightControl v-for="light in lights" :key="light.id" v-bind="light" :room="room" />
</div>
<div style="justify-content: center" class="sencila">
<h3>Senčila</h3>
<button
:class="{currentlyActive: (platnoStatus == 'MOVING_UP')}"
@mousedown="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_UP')"
@touchstart="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_UP')"
@mouseup="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')"
@touchend="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')">
<UpIcon/>
</button>
<button
:class="{currentlyActive: (platnoStatus == 'MOVING_DOWN')}"
@touchstart="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_DOWN')"
@mousedown="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_DOWN')"
@mouseup="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')"
@touchend="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')">
<DownIcon/>
</button>
</div>
</div>
</template>
<style scoped>
.sencila button {
min-width: 3em;
}
button {
padding: 1rem;
margin: 0.3rem;
}
.row {
display: flex;
}
.row > * {
flex:1
}
.editablePreset {
display: flex
}
.lightButtons button {
width: fit-content;
flex: 1;
height: 3.5em;
}
button.saveButton {
flex: 0;
padding: .5rem;
margin-left: -10px;
}
</style>

View file

@ -0,0 +1,36 @@
<script setup lang="ts">
import LectureModule from '../LectureModule.vue';
import AudioControlModule from '../AudioControlModule.vue';
const props = defineProps({
room: String
})
</script>
<template>
<div class="page">
<div style="display: flex; gap: 1rem; margin:inherit">
<div style="width: 50%;">
<h3>Glavni projektor</h3>
<LectureModule :room="props.room" position="main"/>
</div>
<div style="width: 50%;">
<h3>Stranski projektor</h3>
<LectureModule :room="props.room" position="side"/>
</div>
</div>
<div style="text-align: center">
<AudioControlModule :room="props.room" :big="true"/>
</div>
</div>
</template>
<style scoped>
.page {
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-around;
}
</style>

View file

@ -0,0 +1,74 @@
<script setup lang="ts">
import {ref, onMounted, reactive, watch} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
import Platno from '../Platno.vue'
import Lift from '../Lift.vue';
import Projektor from '../Projektor.vue';
import ProjectorPowerModule from "@/components/ProjectorPowerModule.vue";
import AudioControlModule from "@/components/AudioControlModule.vue";
import MasterPowerControlModule from "@/components/MasterPowerControlModule.vue";
import ResetButton from "@/components/ResetButton.vue";
import Numpad from "@/components/Numpad.vue";
const props = defineProps({
room: String,
currentlyActive: Boolean,
})
const _glavni_position = ref('main')
const _stranski_position = ref('side')
const _ctrl_type = ref('service')
const showServis = ref(false)
watch(props, (ca) => {
if (!props.currentlyActive) {
showServis.value = false
}
}, {
immediate: true,
deep: true,
})
</script>
<template>
<div>
<div v-show="!showServis" style="display: flex">
<Numpad @submit-passcode="(b: boolean) => showServis = b"/>
</div>
<div v-show="showServis" style="display: flex; gap: 1rem">
<div>
<Projektor :room="props.room" :position="_glavni_position" :ctrlType="_ctrl_type"/>
<Lift :room="props.room" :position="_glavni_position"/>
<Platno :room="props.room" :position="_glavni_position" :ctrlType="_ctrl_type"/>
</div>
<div>
<Projektor :room="props.room" :position="_stranski_position" :ctrlType="_ctrl_type"/>
<Lift :room="props.room" :position="_stranski_position"/>
<Platno :room="props.room" :position="_stranski_position" :ctrlType="_ctrl_type"/>
</div>
<div>
<h5>POWER CONTROL</h5>
<div>
<ProjectorPowerModule :room="props.room"/>
<AudioControlModule :room="props.room"/>
<MasterPowerControlModule :room="props.room"/>
</div>
<div></div>
<ResetButton :room="props.room"/>
<button onclick="window.location.reload()">RELOAD PAGE</button>
</div>
</div>
</div>
</template>
<style scoped>
</style>
<!-- scaling UIja, buttons as big as can, text as big (within reason), clear feedback, mqtt defaults to host font family noto sans, sans serif, chrome disable -->
<!-- naj se odziva ono sam na platno status fuj fej -->

View file

@ -9,27 +9,34 @@ import Platno from '../Platno.vue'
const props = defineProps({
room: String,
})
const _glavni_position = ref('glavni')
const _stranski_position = ref('stranski')
const _glavni_position = ref('main')
const _stranski_position = ref('side')
const _test = ref('test')
</script>
<template>
<div style="display: flex; gap: 1rem">
<div>
<h4>Glavni</h4>
<div class="page">
<div class="col">
<h3>Glavni</h3>
<Projektor :room="props.room" :position="_glavni_position" :ctrl-type="_test" />
<Platno :room="props.room" :position="_glavni_position" />
<Platno :room="props.room" :position="_glavni_position" :ctrl-type="_test" />
</div>
<div>
<h4>Stranski</h4>
<Projektor :room="props.room" :position="_stranski_position" />
<Platno :room="props.room" :position="_stranski_position" />
<div class="col">
<h3>Stranski</h3>
<Projektor :room="props.room" :position="_stranski_position" :ctrltype="_test"/>
<Platno :room="props.room" :position="_stranski_position" :ctrl-type="_test"/>
</div>
</div>
</template>
<style scoped>
.page {
display: flex;
flex-direction: row;
gap: 1rem;
}
.col {
flex: 1;
}
</style>

View file

@ -0,0 +1,39 @@
<script setup lang="ts">
const props = defineProps({
selected: Boolean,
});
</script>
<template>
<div class="tab" :class="{ selected: props.selected }">
<slot />
</div>
</template>
<style scoped>
.tab {
background-color: var(--color-brand-ul-medium-grey);
border: none;
font-weight: bold;
border: 1px solid #000000;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
border-left: none;
background: lightgray;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
.tab.selected {
background-color: var(--color-brand-ul-red);
}
.vertical-tabs .tab {
width: 100%;
padding: 1rem;
text-align: right;
}
.vertical-tabs .tab:not(:last-child) {
margin-bottom: 0.3rem;
}
</style>

View file

@ -0,0 +1,16 @@
<template>
<div class="vertical-tabs">
<slot />
</div>
</template>
<style scoped>
.vertical-tabs .tab {
width: 100%;
padding: 1rem 2rem;
}
.vertical-tabs .tab:not(:last-child) {
margin-bottom: 1rem;
}
</style>

View file

@ -10,20 +10,21 @@ createApp(App)
createPahoMqttPlugin({
PluginOptions: {
autoConnect: true,
showNotifications: true,
showNotifications: false,
},
MqttOptions: {
//host: import.meta.env.VITE_MQTT_HOST,
//host: "localhost",
host: urlParams.get('mqtt') || 'localhost',
host: urlParams.get('mqtt') || window.location.hostname,
port: 8080,
useSSL: false,
//port: parseInt(import.meta.env.VITE_MQTT_PORT),
//useSSL: ["1", "true", "True"].includes(import.meta.env.VITE_MQTT_SSL),
clientId: `vju-${Math.random() * 9999}`,
//mainTopic: '',
enableMainTopic: false
enableMainTopic: false,
},
})
)

View file

@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar"]
}

View file

@ -1,33 +0,0 @@
# vju_display
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -1,103 +0,0 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import MainPage from './components/pages/MainPage.vue';
import VideoPage from './components/pages/VideoPage.vue';
import VerticalTabs from './components/tabs/VerticalTabs.vue';
import Tab from './components/tabs/Tab.vue';
import LightingPage from './components/pages/LightingPage.vue';
import ServisPage from './components/pages/ServisPage.vue';
let urlParams = new URLSearchParams(window.location.search);
const currentRoom = ref(urlParams.get('room') || 'none') // if no param specified
// should also check if valid room parameter ampak se ne mudi
// TODO does this make sense al se naj naprej defaulta kr na p01?
const pageNum = ref(0)
const srvcUnlocked = ref(false)
const showPinPopup = ref(false)
function openPinDiag() {
}
function closePinDiag() {
}
function unlockServicePage() {
showPinPopup.value = true
while (true) {
if ('' == '') {
srvcUnlocked.value = true
closePinDiag()
break
}
}
}
function lockServicePage() {
srvcUnlocked.value = false
}
</script>
<template>
<div v-if="currentRoom == 'none'">
<h1>Incorrect or missing room parameter!</h1>
</div>
<div v-else id="wrapper">
<header class="sidebar">
<img class="logo" src="https://fri.uni-lj.si/sites/all/themes/fri_theme/images/fri_logo.png" />
<h1>{{ currentRoom.toUpperCase() }}</h1>
<VerticalTabs id="nav">
<Tab @click="pageNum=0; lockServicePage()" :selected="pageNum==0">Priprava</Tab>
<Tab @click="pageNum=1; lockServicePage()" :selected="pageNum==1">Video</Tab>
<Tab @click="pageNum=2; lockServicePage()" :selected="pageNum==2">Audio</Tab>
<Tab @click="pageNum=3; lockServicePage()" :selected="pageNum==3">Lučke</Tab>
<Tab @click="pageNum=4; unlockServicePage()" :selected="pageNum==4">Servis</Tab>
</VerticalTabs>
<large style="display: flex;">turbo odličen super mega kontrol panel</large>
</header>
<main>
<MainPage v-if="pageNum == 0" :room="currentRoom" />
<VideoPage v-else-if="pageNum == 1" :room="currentRoom" />
<LightingPage v-else-if="pageNum == 3" :room="currentRoom" />
<ServisPage v-else-if="pageNum == 4 && srvcUnlocked" :room="currentRoom" />
</main>
</div>
</template>
<style scoped>
#wrapper {
width: 100%;
height: 100%;
display: flex;
}
main {
flex: 1;
padding: 1rem;
}
h1 {
text-align: center;
}
.sidebar {
max-width: 20vw;
}
.logo {
max-width: 100%;
padding: .4rem;
}
</style>

View file

@ -1,32 +0,0 @@
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
:root {
--color-text: #000;
--color-background: #EEE;
--color-brand-ul-red: #e03127;
--color-brand-ul-light-grey: #E6E7E8;
--color-brand-ul-medium-grey: #A7A8AA;
--color-brand-ul-dark-grey: #58595b;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family: sans-serif;
font-size: 17px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View file

@ -1,37 +0,0 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
}
button {
border: none;
background-color: #aaa;
cursor: pointer;
}
button:hover, button:focus, button:active {
background-color: #ccc;
}

View file

@ -1,140 +0,0 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import { ref, onMounted, reactive, watch } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
const props = defineProps({
room: String,
position: String,
})
const topicstrs = [ //TODO everything else
props.room + '/projectors/' + props.position + '/status/power',
props.room + '/projectors/' + props.position + '/platno/status',
props.room + '/projectors/' + props.position + '/lift/status'
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
let typ: string = ''
if (topic.includes('power')) {
typ = topic.split('/')[4]
} else if (topic.includes('platno')) {
typ = topic.split('/')[3]
} else if (topic.includes('lift')) {
typ = topic.split('/')[3]
} else { return }
handleIncStatus(typ, msg)
}
function handleIncStatus(typ: string, msg: string) {
console.log('handling status')
//console.log(projStatus)
if (typ == 'power') { roomStatus.proj_power = msg == '1' }
else if (typ == 'platno') { roomStatus.platno_state = msg }
else if (typ == 'lift') { roomStatus.lift_state = msg }
}
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
async function setLecture(pos?: String) {
if (!pos) { return }
let topicPref = props.room + "/projektorji/" + props.position + "/"
let command = '0'
if (roomState.value == 0) {
command = '1'
} else if (roomState.value == 1) {
command = '0'
} else { return }
publishMQTTMsg((topicPref + 'set/power'), command)
publishMQTTMsg((topicPref + 'platno/goto'), command == '1' ? 'DOWN' : 'UP')
publishMQTTMsg((topicPref + 'lift/move/' + (command == '1' ? 'down' : 'up') ), command)
await sleep(500)
publishMQTTMsg((topicPref + 'platno/goto'), 'STOP')
publishMQTTMsg((topicPref + 'lift/move/' + (command == '1' ? 'down' : 'up') ), '0')
}
//TODO organize better, binds, etc.
const roomStatus = reactive({
proj_power: false,
lift_state: 'UNKNOWN',
platno_state: 'UNKNOWN',
})
const roomState = ref(0)
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
watch (roomStatus, (_, _newState) => {
if (!_newState) { return }
let allOn = _newState.proj_power && _newState.platno_state == 'DOWN' && _newState.lift_state == 'DOWN'
let allOff = !_newState.proj_power && _newState.platno_state == 'UP' && _newState.lift_state == 'UP'
if (allOn) {
roomState.value = 1
} else if (allOff) {
roomState.value = 0
} else {
roomState.value = 2
}
}, {immediate: true})
</script>
<template>
<div>
<!-- TODO lepš -->
<div>
<h3 v-if="roomState == 1">AKTIVNO</h3>
<h3 v-else-if="roomState == 0">V PRIPRAVLJENOSTI</h3>
<h3 v-else-if="roomState == 2">POČAKAJTE</h3>
<h3 v-else="roomState == 1">NAPAKA</h3>
<button style="" @click="setLecture(props.position)" :disabled="roomState == 2">
{{ roomState == 1 ? 'UGASNI' : (roomState == 0 ? 'PRIŽGI' : '...') }}
</button>
</div>
<h6>Projektor status: {{ roomStatus.proj_power ? "ON" : "OFF" }}</h6>
<h6>Platno pozicija: {{ roomStatus.platno_state }}</h6>
<h6>Dvigalo pozicija: {{ roomStatus.lift_state }}</h6>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
width: 100%;
}
</style>

View file

@ -1,125 +0,0 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import {ref, onMounted, reactive } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
import DownIcon from './icons/DownIcon.vue';
import UpIcon from './icons/UpIcon.vue';
const props = defineProps({
room: String,
position: String
})
const topicstrs = [ //TODO everything else
props.room + '/projectors/' + props.position + 'lift/status',
props.room + '/projectors/' + props.position + 'lift/status',
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
if (topic.includes('status')) {
//console.log(topic.split('/'))
let typ = topic.split('/')[4]
// handlePlatnoStatus(msg)
}
}
// enum firankState {
// UP,
// DOWN,
// MOVING,
// STOPPED
// }
// const firankStatus = ref(firankState.STOPPED)
// function handlePlatnoStatus(msg: string) {
// console.log('handling status')
// //console.log(projStatus)
// let newState: firankState
// switch (msg) {
// case "UP":
// newState = firankState.UP
// break
// case "DOWN":
// newState = firankState.DOWN
// break
// case "MOVING":
// newState = firankState.MOVING
// break
// default:
// newState = firankState.STOPPED
// break
// }
// firankStatus.value = newState
// }
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/lift/')
</script>
<!--
TODO: NE HARDCODANO
-->
<template>
<div style="display:flex; gap: 1rem">
<div>
<h4>Lifti {{ props.position }}</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/up', '0')" >
<UpIcon /></button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/down', '0')" >
<DownIcon /></button>
</div><div>
<h4>Servis lifti {{ props.position }}</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/service_up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/service_up', '0')" >
<UpIcon /></button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/service_down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/service_down', '0')" >
<DownIcon /></button>
</div>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
margin: 0.1rem;
width: 45%;
}
</style>

View file

@ -1,113 +0,0 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import {ref, onMounted, reactive } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
import DownIcon from '../icons/DownIcon.vue';
import UpIcon from '../icons/UpIcon.vue';
const props = defineProps({
room: String,
position: String
})
const topicstrs = [ //TODO everything else
props.room + '/projectors/' + props.position + 'platno/status',
props.room + '/projectors/' + props.position + 'platno/status',
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
if (topic.includes('status')) {
//console.log(topic.split('/'))
let typ = topic.split('/')[4]
handlePlatnoStatus(msg)
}
}
enum firankState {
UP,
DOWN,
MOVING,
STOPPED
}
const firankStatus = ref(firankState.STOPPED)
function handlePlatnoStatus(msg: string) {
console.log('handling status')
//console.log(projStatus)
let newState: firankState
switch (msg) {
case "UP":
newState = firankState.UP
break
case "DOWN":
newState = firankState.DOWN
break
case "MOVING":
newState = firankState.MOVING
break
default:
newState = firankState.STOPPED
break
}
firankStatus.value = newState
}
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
//msg = msg.toString()
console.log('Sending to [', topic, '] with message [', msg, ']')
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
const publishPrefix = ref(props.room + '/firanki/')
</script>
<template>
<div style="display: flex; gap: 1rem">
</div>
<div style="display:flex; gap: 1rem">
<div>
<h4>Firanki</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'MOVE_UP')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')" >
<UpIcon /></button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'MOVE_DOWN')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')" >
<DownIcon /></button>
</div>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
margin: 0.1rem;
width: 45%;
}
</style>

View file

@ -1,32 +0,0 @@
<script setup lang="ts">
import {ref, onMounted, reactive } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
import Projektor from '../Projektor.vue';
import Platno from '../Platno.vue';
import Lift from '../Lift.vue';
import LectureModule from '../LectureModule.vue';
const props = defineProps({
room: String
})
const _glavni_position = ref('glavni')
const _stranski_position = ref('stranski')
let _glavni = ref('glavni')
let _stranski = ref('stranski')
</script>
<template>
<div style="display:flex; gap: 1rem">
<div style="width: 50%;">
<h4>Glavni:</h4>
<LectureModule :room="props.room" :position="_glavni" />
</div>
<div style="width: 50%;">
<h4>Stranski:</h4>
<LectureModule :room="props.room" :position="_stranski" />
</div>
</div>
</template>

View file

@ -1,37 +0,0 @@
<script setup lang="ts">
//import HelloWorld from './components/HelloWorld.vue'
//import TheWelcome from './components/TheWelcome.vue'
import {ref, onMounted, reactive } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
// import Projektor from '../Projektor.vue'
// import Platno from '../Platno.vue'
import Lift from '../Lift.vue';
import Projektor from '../Projektor.vue';
const props = defineProps({
room: String,
})
const _glavni_position = ref('glavni')
const _stranski_position = ref('stranski')
const _ctrl_type = ref('service')
</script>
<template>
<div style="display: flex; gap: 1rem">
<div>
<Projektor :room="props.room" :position="_glavni_position" :ctrlType="_ctrl_type" />
<Lift :room="props.room" :position="_glavni_position" />
<Platno :room="props.room" :position="_glavni_position" :ctrlType="_ctrl_type"/>
</div>
<div>
<Projektor :room="props.room" :position="_stranski_position" :ctrlType="_ctrl_type"/>
<Lift :room="props.room" :position="_stranski_position" />
<Platno :room="props.room" :position="_stranski_position" :ctrlType="_ctrl_type" />
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -1,37 +0,0 @@
<script setup lang="ts">
const props = defineProps({
selected: Boolean
})
</script>
<template>
<div class="tab" :class="{selected: props.selected}">
<slot/>
</div>
</template>
<style scoped>
.tab {
background-color: var(--color-brand-ul-medium-grey);
border: none;
font-weight: bold;
}
.tab:hover {
cursor: pointer;
}
.tab.selected {
background-color: var(--color-brand-ul-red);
}
.vertical-tabs .tab {
width: 100%;
padding: 1rem;
text-align: right;
}
.vertical-tabs .tab:not(:last-child) {
margin-bottom: .3rem;
}
</style>

View file

@ -1,27 +0,0 @@
<template>
<div class="vertical-tabs">
<slot />
</div>
</template>
<style scoped>
.tab:hover {
cursor: pointer;
}
.vertical-tabs .tab {
width: 100%;
padding: 1rem 2rem;
}
.vertical-tabs .tab:not(:last-child) {
margin-bottom: 1rem;
}
</style>

View file

@ -1,4 +0,0 @@
#!/bin/sh
poetry run python3 ~/unit_generator.py

View file

@ -1,5 +0,0 @@
#!/bin/sh
poetry install
cp ./generator_wrapper.sh /usr/lib/systemd/systemd-generators

View file

@ -1,3 +0,0 @@
# interfaces(5) file used by ifup(8) and ifdown(8)
# Include files from /etc/network/interfaces.d:
source /etc/network/interfaces.d/*

View file

@ -1,11 +0,0 @@
[Unit]
Description=Sets up MQTT topics
After=multi-user.target
[Service]
ExecStart =/usr/bin/python3 /home/rpi/mqtt_init_topics.py
Type=simple
[Install]
WantedBy=multi-user.target

154
poetry.lock generated
View file

@ -1,154 +0,0 @@
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
[[package]]
name = "aiomqtt"
version = "2.3.0"
description = "The idiomatic asyncio MQTT client, wrapped around paho-mqtt"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "aiomqtt-2.3.0-py3-none-any.whl", hash = "sha256:127926717bd6b012d1630f9087f24552eb9c4af58205bc2964f09d6e304f7e63"},
{file = "aiomqtt-2.3.0.tar.gz", hash = "sha256:312feebe20bc76dc7c20916663011f3bd37aa6f42f9f687a19a1c58308d80d47"},
]
[package.dependencies]
paho-mqtt = ">=2.1.0,<3.0.0"
[[package]]
name = "aioserial"
version = "1.3.1"
description = "An asynchronous serial port library of Python"
optional = false
python-versions = ">=3.6,<4.0"
files = [
{file = "aioserial-1.3.1.tar.gz", hash = "sha256:702bf03b0eb84b8ef2d8dac5cb925e1e685dce98f77b125569bc6fd2b3b58228"},
]
[package.dependencies]
pyserial = "*"
[[package]]
name = "colorzero"
version = "2.0"
description = "Yet another Python color library"
optional = false
python-versions = "*"
files = [
{file = "colorzero-2.0-py2.py3-none-any.whl", hash = "sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8"},
{file = "colorzero-2.0.tar.gz", hash = "sha256:e7d5a5c26cd0dc37b164ebefc609f388de24f8593b659191e12d85f8f9d5eb58"},
]
[package.dependencies]
setuptools = "*"
[package.extras]
doc = ["pkginfo", "sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "gpiozero"
version = "2.0.1"
description = "A simple interface to GPIO devices with Raspberry Pi"
optional = false
python-versions = ">=3.9"
files = [
{file = "gpiozero-2.0.1-py3-none-any.whl", hash = "sha256:8f621de357171d574c0b7ea0e358cb66e560818a47b0eeedf41ce1cdbd20c70b"},
{file = "gpiozero-2.0.1.tar.gz", hash = "sha256:d4ea1952689ec7e331f9d4ebc9adb15f1d01c2c9dcfabb72e752c9869ab7e97e"},
]
[package.dependencies]
colorzero = "*"
[package.extras]
doc = ["sphinx (>=4.0)", "sphinx-rtd-theme (>=1.0)"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "paho-mqtt"
version = "2.1.0"
description = "MQTT version 5.0/3.1.1 client class"
optional = false
python-versions = ">=3.7"
files = [
{file = "paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"},
{file = "paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834"},
]
[package.extras]
proxy = ["pysocks"]
[[package]]
name = "pyserial"
version = "3.5"
description = "Python Serial Port Extension"
optional = false
python-versions = "*"
files = [
{file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"},
{file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"},
]
[package.extras]
cp2110 = ["hidapi"]
[[package]]
name = "setuptools"
version = "75.6.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
files = [
{file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"},
{file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"},
]
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"]
core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"]
[[package]]
name = "smbus2"
version = "0.5.0"
description = "smbus2 is a drop-in replacement for smbus-cffi/smbus-python in pure Python"
optional = false
python-versions = "*"
files = [
{file = "smbus2-0.5.0-py2.py3-none-any.whl", hash = "sha256:1a15c3b9fa69357beb038cc0b5d37939702f8bfde1ddc89ca9f17d8461dbe949"},
{file = "smbus2-0.5.0.tar.gz", hash = "sha256:4a5946fd82277870c2878befdb1a29bb28d15cda14ea4d8d2d54cf3d4bdcb035"},
]
[package.extras]
docs = ["sphinx (>=1.5.3)"]
qa = ["flake8"]
[[package]]
name = "telnetlib3"
version = "2.0.4"
description = "Python 3 asyncio Telnet server and client Protocol library"
optional = false
python-versions = ">=3.7"
files = [
{file = "telnetlib3-2.0.4-py2.py3-none-any.whl", hash = "sha256:b3c0f984a7fb1b6ee16e6fdaa410c56389b0dc492174a99c6661b1ba4c9d457d"},
{file = "telnetlib3-2.0.4.tar.gz", hash = "sha256:dbcbc16456a0e03a62431be7cfefff00515ab2f4ce2afbaf0d3a0e51a98c948d"},
]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "5e30fcfae3a69f8475da0f4ba655b5194a80b2a2185c652cbce2c72fa83aa752"

View file

@ -1,13 +0,0 @@
[Unit]
Description=Control for projector motors
After=multi-user.target
[Service]
ExecStart=/usr/bin/python3 /home/rpi/projector_motors.py
Restart=always
Type=simple
User=kat
Group=kat
[Install]
WantedBy=multi-user.target

View file

@ -1,105 +0,0 @@
#import gpiozero.pins.mock
#from gpiozero import *
#from grove.factory import Factory
#from grove.grove_relay import GroveRelay
import aiomqtt
import asyncio
#from i2cpy import I2C
from smbus2 import SMBus
#import i2cpy
#i2cset -y 1 0x11 0x11 0x42
#set i2c address from 0x11 to 0x42
# ONLY FOR TESTING ON NON RPI
#Device.pin_factory = gpiozero.pins.mock.MockFactory()
#relays = [LED(17), LED(27), LED(22), LED(23), LED(5), LED(6), LED(24), LED(25)]
relayBoardMain = 0x42
relayBoardSide = 0x43
""" 0 1 2 3 """
relayMasks = [0b0001, 0b0010, 0b0100, 0b1000] #probably ne rabim
bus = SMBus(1)
relMapping = {
'service_down': 0,
'service_up': 1,
'down': 2,
'up': 3
}
currentState = {
'glavni': 0b0000,
'stranski': 0b0000
}
async def msgRelayBoard(projSelect, command, state: bool):
#i2cAddr = relayBoardMain if projSelect == 'glavni' else relayBoardSide
#TODO this is not optimal, check for more crap
# register 0x10 za releje
I2CAddr = 0x42 #glavni
if projSelect == 'stranski':
I2CAddr += 0x1
maskShift = relMapping[command]
mask = (1 << maskShift)
if state:
currentState[projSelect] = currentState[projSelect] | mask
else:
currentState[projSelect] = currentState[projSelect] & ~mask
bus.write_byte_data(I2CAddr, 0x10, currentState[projSelect])
print('testirovano jako')
"""
SrvDwn SrvUp OpDwn OpUp
MAIN: 0x42 0001 0010 0100 1000
SIDE: 0x43 0001 0010 0100 1000
"""
#old board
"""
MAIN: SrvDwn SrvUp OpDwn OpUp
0 1 2 3
SIDE: 4 5 6 7
"""
#dej like bolš to podukumentiraj or smth
async def task_command2relays(controlClient: aiomqtt.Client):
#relayCtrl = lambda cmd, relay: relays[relay].on() if cmd == 1 else relays[relay].off()
#relayCtrl = lambda cmd, relay: [relays[r].on() if cmd == 1 and r == relay else relays[r].off for r in range(len(relays))]
relayCtrl = lambda x, y: print(x, y)
await controlClient.subscribe("p01/projectors/+/lift/#")
msgs = controlClient.messages
async for mesg in msgs:
mesg: aiomqtt.Message
if mesg.topic.matches('p01/projectors/+/lift/move/#'):
msgTopicArr = mesg.topic.value.split('/')
state = mesg.payload.decode()
print("Received: " + str(msgTopicArr) + " payload: [" + state + "]")
#testCase = (msgTopicArr[2], msgTopicArr[4])
projSel = msgTopicArr[2]
if projSel != 'glavni' and projSel != 'stranski':
continue
command = msgTopicArr[5]
await msgRelayBoard(projSel, command, state == '1')
#print("Pushing \'off\' to other relays to prevent conflicts")
await asyncio.sleep(0.01)
async def main():
async with aiomqtt.Client('localhost', 1883) as client:
task_control = asyncio.create_task(task_command2relays(client))
await asyncio.gather(task_control)
if __name__ == '__main__':
asyncio.run(main())

View file

@ -1,19 +0,0 @@
import toml
tomlConf = toml.load('./config.toml')
print(tomlConf)
barcos = {k: v for k, v in tomlConf["barco"].items()}
print()
print(barcos)
newBarcos = {k: v for k,v in barcos["novi"].items()}
print(newBarcos)
for pos in newBarcos.keys():
atrs = newBarcos[pos]
print(atrs)
ip = atrs['ip']
# etc
# launch scripty thingy with atrs[IP], itd

View file

@ -1,15 +0,0 @@
[Unit]
Description=TSE serial control
After=multi-user.target
[Service]
ExecStart=/usr/bin/python3 /home/rpi/tse_serial_controler.py /home/rpi/tse_serial_interpreter.py
Type=simple
Restart=always
User=kat
Group=kat
[Install]
WantedBy=multi-user.target

View file

@ -1,4 +0,0 @@
import asyncio
import serial
import aioserial
import aiomqtt

Some files were not shown because too many files have changed in this diff Show more