initial commit
This commit is contained in:
commit
ae9041e769
294
AI.cfg
Normal file
294
AI.cfg
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
[net]
|
||||||
|
# Testing
|
||||||
|
#batch=1
|
||||||
|
#subdivisions=1
|
||||||
|
# Training
|
||||||
|
batch=64
|
||||||
|
subdivisions=1
|
||||||
|
width=416
|
||||||
|
height=416
|
||||||
|
channels=3
|
||||||
|
momentum=0.9
|
||||||
|
decay=0.0005
|
||||||
|
angle=0
|
||||||
|
saturation = 1.5
|
||||||
|
exposure = 1.5
|
||||||
|
hue=.1
|
||||||
|
|
||||||
|
learning_rate=0.00261
|
||||||
|
burn_in=1000
|
||||||
|
|
||||||
|
max_batches = 2000200
|
||||||
|
policy=steps
|
||||||
|
steps=1600000,1800000
|
||||||
|
scales=.1,.1
|
||||||
|
|
||||||
|
|
||||||
|
#weights_reject_freq=1001
|
||||||
|
#ema_alpha=0.9998
|
||||||
|
#equidistant_point=1000
|
||||||
|
#num_sigmas_reject_badlabels=3
|
||||||
|
#badlabels_rejection_percentage=0.2
|
||||||
|
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=32
|
||||||
|
size=3
|
||||||
|
stride=2
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=64
|
||||||
|
size=3
|
||||||
|
stride=2
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=64
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers=-1
|
||||||
|
groups=2
|
||||||
|
group_id=1
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=32
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=32
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -1,-2
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=64
|
||||||
|
size=1
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -6,-1
|
||||||
|
|
||||||
|
[maxpool]
|
||||||
|
size=2
|
||||||
|
stride=2
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=128
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers=-1
|
||||||
|
groups=2
|
||||||
|
group_id=1
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=64
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=64
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -1,-2
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=128
|
||||||
|
size=1
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -6,-1
|
||||||
|
|
||||||
|
[maxpool]
|
||||||
|
size=2
|
||||||
|
stride=2
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=256
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers=-1
|
||||||
|
groups=2
|
||||||
|
group_id=1
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=128
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=128
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -1,-2
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=256
|
||||||
|
size=1
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -6,-1
|
||||||
|
|
||||||
|
[maxpool]
|
||||||
|
size=2
|
||||||
|
stride=2
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=512
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
##################################
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=256
|
||||||
|
size=1
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=512
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
size=1
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
filters=255
|
||||||
|
activation=linear
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[yolo]
|
||||||
|
mask = 3,4,5
|
||||||
|
anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319
|
||||||
|
classes=80
|
||||||
|
num=6
|
||||||
|
jitter=.3
|
||||||
|
scale_x_y = 1.05
|
||||||
|
cls_normalizer=1.0
|
||||||
|
iou_normalizer=0.07
|
||||||
|
iou_loss=ciou
|
||||||
|
ignore_thresh = .7
|
||||||
|
truth_thresh = 1
|
||||||
|
random=0
|
||||||
|
resize=1.5
|
||||||
|
nms_kind=greedynms
|
||||||
|
beta_nms=0.6
|
||||||
|
#new_coords=1
|
||||||
|
#scale_x_y = 2.0
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -4
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=128
|
||||||
|
size=1
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[upsample]
|
||||||
|
stride=2
|
||||||
|
|
||||||
|
[route]
|
||||||
|
layers = -1, 23
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
batch_normalize=1
|
||||||
|
filters=256
|
||||||
|
size=3
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
activation=leaky
|
||||||
|
|
||||||
|
[convolutional]
|
||||||
|
size=1
|
||||||
|
stride=1
|
||||||
|
pad=1
|
||||||
|
filters=255
|
||||||
|
activation=linear
|
||||||
|
|
||||||
|
[yolo]
|
||||||
|
mask = 1,2,3
|
||||||
|
anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319
|
||||||
|
classes=80
|
||||||
|
num=6
|
||||||
|
jitter=.3
|
||||||
|
scale_x_y = 1.05
|
||||||
|
cls_normalizer=1.0
|
||||||
|
iou_normalizer=0.07
|
||||||
|
iou_loss=ciou
|
||||||
|
ignore_thresh = .7
|
||||||
|
truth_thresh = 1
|
||||||
|
random=0
|
||||||
|
resize=1.5
|
||||||
|
nms_kind=greedynms
|
||||||
|
beta_nms=0.6
|
||||||
|
#new_coords=1
|
||||||
|
#scale_x_y = 2.0
|
BIN
AI.weights
Normal file
BIN
AI.weights
Normal file
Binary file not shown.
151
README.md
Normal file
151
README.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# work-soc-streamer
|
||||||
|
|
||||||
|
## Описание содержания файлов
|
||||||
|
|
||||||
|
В этом разделе описано краткое содержание файлов проекта, которые были изменены разработчиком (Мария Сухоносова).
|
||||||
|
Описание может содержать неточности.
|
||||||
|
|
||||||
|
### Файл /streamer_ulils.py
|
||||||
|
|
||||||
|
Это самописная библиотека, содержит класс SocketBlocksWrapper - обертку над сокетом для чтения/записи объектов или сырых блоков данных в сокет.
|
||||||
|
Используется остальными компонентами проекта.
|
||||||
|
|
||||||
|
### Файл /server.py
|
||||||
|
|
||||||
|
Это имполняемый файл сервера, то есть машины, которая ожидает подключений плат и компьютеров.
|
||||||
|
Задача сервера - прослушивать порт. На любом входящем соединении требовать авторизацию и после успешной авторизации создавать некое подобие локальной сети между нужными соединениями. Принцп действия схож с принципом действия свича с поддержкой VLAN с разницей в том, что сервер сначала требует авторизацию.
|
||||||
|
|
||||||
|
Требует наличия файла /streamer_ulils.py рядом во время исполнения.ы
|
||||||
|
|
||||||
|
### Файл /client.py
|
||||||
|
|
||||||
|
Это исполняемый файл клиента, то есть программы, которая создает окно, показывает картинку и реагирует на действия пользователя.
|
||||||
|
|
||||||
|
Требует наличия файла /streamer_ulils.py рядом во время исполнения.
|
||||||
|
|
||||||
|
В программе отдельно создается поток, работающий с сокетом и потом, работающий с ГУИ.
|
||||||
|
|
||||||
|
Пользователь (например) нажимает кнопку в ГУИ, дальше формируется сообщение с типом 'command' и отправляется в сокет путем неблокирующей записи, если сокет существует и открыт.
|
||||||
|
|
||||||
|
Поток, работающий с сокетом пытается установить соединение с сервером.
|
||||||
|
После успешной попытки происходит авторизация (сообщения типа 'auth').
|
||||||
|
После успешной авторизации клиент ожидает сообщений с типом 'video'.
|
||||||
|
Сообщение с этим типом интерпретируются как сообщения с картинкой. После чего картинка восстанавливается из сообщения и обновляется в потоке ГУИ.
|
||||||
|
|
||||||
|
В случае неудачной попытки подключения будет предпринята еще одна попытка подключения, и так до закрытия программы.
|
||||||
|
|
||||||
|
### Файл /board.py
|
||||||
|
|
||||||
|
Это основной исполняемый файл для компьютера с камерой. За основу был взят Final-Ochka-v1.2-beta.py
|
||||||
|
В него был встроен один класс в начале скрипта, был модифицирован алгоритм отправки фреймов и обработки классов.
|
||||||
|
|
||||||
|
Скрипт так же как и скрипт для клиента запускает поток, работающий с соединением. В данном случае поток слушает все сообщения с типом 'command'. Сразу после принятия сообщения такого типа вызывается обработчик команд, который уже выполняет команду.
|
||||||
|
|
||||||
|
### Файл /board-config.json
|
||||||
|
|
||||||
|
Файл содержит json структуру и хранит в себе настройки подключения к серверу.
|
||||||
|
|
||||||
|
### Файл /client-config.json
|
||||||
|
|
||||||
|
Файл содержит json структуру и хранит в себе настройки подключения и авторизации клиента на сервере.
|
||||||
|
|
||||||
|
### Файл /classes.json
|
||||||
|
|
||||||
|
Файл имеет структуру json и содержит массив классов в формате
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"class": "nameOfClass",
|
||||||
|
"label": "Название класса",
|
||||||
|
"color": [<R>, <G>, <B>]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Поле class содержит имя класса. Поле label содержит название класса, которое будет отображено на клиенте. Поле label пока не используется.
|
||||||
|
|
||||||
|
### Файл /boards.sqlite3
|
||||||
|
|
||||||
|
Файл базы данных, в которой хранятся все наименования плат и пароль для подключения к каждой плате.
|
||||||
|
Содержит одну единственную таблицу, созданную таким кодом:
|
||||||
|
```sqlite
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
board_name TEXT NOT NULL PRIMARY KEY,
|
||||||
|
password TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## КАК ЗАПУСКАТЬ
|
||||||
|
|
||||||
|
Всего в этом проекте 3 исполняемых файла: board.py, computer.py, server.py.
|
||||||
|
Не так важно в каком порядке их запускать, однако если есть желание увидеть картинку, то нужно запустить все 3.
|
||||||
|
|
||||||
|
При копировании проекта по разным машинам следует убедиться, что вы взяли все ресурсы для конкретного файла.
|
||||||
|
|
||||||
|
### Board (плата с камерой)
|
||||||
|
|
||||||
|
Команда запуска:
|
||||||
|
```bash
|
||||||
|
python board.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Используемые файлы:
|
||||||
|
* board.py (исполняемый файл)
|
||||||
|
* board-config.json
|
||||||
|
* classes.json
|
||||||
|
* Nab.wav
|
||||||
|
* Pot.wav
|
||||||
|
* Zah.wav
|
||||||
|
* AI.cfg
|
||||||
|
* AI.weights
|
||||||
|
* streamer_utils.py
|
||||||
|
|
||||||
|
В конфигурационном файле прописываются:
|
||||||
|
|
||||||
|
### Server (комп, к которому все подключатся)
|
||||||
|
|
||||||
|
Команда запуска
|
||||||
|
```bash
|
||||||
|
python server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Используемые файлы:
|
||||||
|
* server.py (исполняемый файл)
|
||||||
|
* streamer_utils.py
|
||||||
|
* boards.sqlite3
|
||||||
|
|
||||||
|
### Client (комп, который должен показывать картинку)
|
||||||
|
|
||||||
|
Команда запуска
|
||||||
|
```bash
|
||||||
|
python client.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Используемые файлы:
|
||||||
|
* client.py (исполняемый файл)
|
||||||
|
* classes.json
|
||||||
|
* client-config.json
|
||||||
|
* streamer_utils.py
|
||||||
|
* logo.png
|
||||||
|
|
||||||
|
В конфигурационном файле прописываются: адрес сервера, порт, имя платы для подключения, пароль
|
||||||
|
|
||||||
|
## Как добавить плату
|
||||||
|
|
||||||
|
Нужно отредактировать базу данных, то есть файл boards.sqlite3.
|
||||||
|
|
||||||
|
Открыть базу можно любой утилитой, которая поддерживает SQLite3.
|
||||||
|
Самый простой метод - выполнить в терминале
|
||||||
|
```bash
|
||||||
|
sqlite3 boards.sqlite3
|
||||||
|
```
|
||||||
|
|
||||||
|
Посмотреть все платы и пароли к ним
|
||||||
|
```sqlite
|
||||||
|
SELECT board_name, password FROM users;
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавление/удаление/обновление информации делается нативным образом для этой базы данных, то есть через запросы.
|
||||||
|
|
||||||
|
База данных позволяет на лету менять данные. Изменения применяются сразу и только для новых подключений.
|
||||||
|
Уже установленные подключения не будут разорваны.
|
||||||
|
|
BIN
__pycache__/streamer_utils.cpython-38.pyc
Normal file
BIN
__pycache__/streamer_utils.cpython-38.pyc
Normal file
Binary file not shown.
5
board-config.json
Normal file
5
board-config.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "test-board",
|
||||||
|
"server-address": "192.168.2.91",
|
||||||
|
"server-port": 40100
|
||||||
|
}
|
337
board.py
Normal file
337
board.py
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
# import packages
|
||||||
|
from openal import *
|
||||||
|
from imutils.video import VideoStream
|
||||||
|
import itertools
|
||||||
|
import imutils
|
||||||
|
import time
|
||||||
|
import cv2
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
import spidev
|
||||||
|
|
||||||
|
# Client
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from threading import Thread
|
||||||
|
from streamer_utils import SocketBlocksWrapper, read_json_config
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
spi = spidev.SpiDev()
|
||||||
|
spi.open(1, 0)
|
||||||
|
|
||||||
|
spi.bits_per_word = 8
|
||||||
|
spi.max_speed_hz = 500000
|
||||||
|
|
||||||
|
X = bool(0) # иниц-я глоб. переменной
|
||||||
|
X_New = bool(0)
|
||||||
|
X_pred = bool(0) # иниц-я глоб. переменной
|
||||||
|
startTime = float(time.time() - 10) # иниц-я глоб. переменной
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG = read_json_config('board-config.json')
|
||||||
|
CLASSES = read_json_config('classes.json')
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionDaemon(Thread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(daemon=True)
|
||||||
|
self._sock = None
|
||||||
|
self._message_handler = None
|
||||||
|
|
||||||
|
def set_message_handler(self, handler: callable):
|
||||||
|
self._message_handler = handler
|
||||||
|
|
||||||
|
def __do_call_message_handler(self, res):
|
||||||
|
if self._message_handler is not None:
|
||||||
|
try:
|
||||||
|
self._message_handler(res)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def __do_session(self):
|
||||||
|
try:
|
||||||
|
with SocketBlocksWrapper.connect(CONFIG['server-address'], CONFIG['server-port']) as sock:
|
||||||
|
print("ConnectionDaemon: open connection")
|
||||||
|
self._sock = sock
|
||||||
|
self._sock.write_object({'type': 'auth', 'client-type': 'board', 'name': CONFIG['name']})
|
||||||
|
res = self._sock.read_object()
|
||||||
|
if res is None:
|
||||||
|
return
|
||||||
|
print(res)
|
||||||
|
if 'status' in res:
|
||||||
|
if res['status'] == 'success':
|
||||||
|
while True:
|
||||||
|
res = self._sock.read_object()
|
||||||
|
if res is None:
|
||||||
|
break
|
||||||
|
self.__do_call_message_handler(res)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
print("ConnectionDaemon: start session...")
|
||||||
|
self.__do_session()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
def send_frame(self, fr):
|
||||||
|
if self._sock is not None:
|
||||||
|
try:
|
||||||
|
to_send = {
|
||||||
|
'type': 'video',
|
||||||
|
'data': None,
|
||||||
|
"selected-class": selected_class_id
|
||||||
|
}
|
||||||
|
if fr is not None:
|
||||||
|
fr = imutils.resize(fr, width=640, height=360)
|
||||||
|
buffer = cv2.imencode('.jpg', fr, [int(cv2.IMWRITE_JPEG_QUALITY), 60])[1]
|
||||||
|
data_encode = np.array(buffer)
|
||||||
|
to_send["data"] = data_encode.tobytes()
|
||||||
|
self._sock.write_object(to_send)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def send_image(self, img: Image):
|
||||||
|
if self._sock is not None:
|
||||||
|
try:
|
||||||
|
out = io.BytesIO()
|
||||||
|
img.save(out, format="JPEG")
|
||||||
|
self._sock.write_object({
|
||||||
|
'type': 'video',
|
||||||
|
'data': out.getvalue(),
|
||||||
|
"selected-class": selected_class_id
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
# камера не движется
|
||||||
|
Left = bool(0)
|
||||||
|
Right = bool(0)
|
||||||
|
Up = bool(0)
|
||||||
|
Down = bool(0)
|
||||||
|
|
||||||
|
# -2 = нейронка отключена, -1 = включены все классы, остальное - id класса из списка CLASSES
|
||||||
|
selected_class_id = -2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# функция, которая вызывается при получении команды
|
||||||
|
def message_handler(msg):
|
||||||
|
global selected_class_id
|
||||||
|
global Left
|
||||||
|
global Right
|
||||||
|
global Up
|
||||||
|
global Down
|
||||||
|
print(msg)
|
||||||
|
if msg["type"] == "command":
|
||||||
|
# отлично, наше сообщение
|
||||||
|
act = msg["data"]["action"]
|
||||||
|
if act == "left":
|
||||||
|
Left = 1
|
||||||
|
if act == "right":
|
||||||
|
Right = 1
|
||||||
|
if act == "up":
|
||||||
|
Up = 1
|
||||||
|
if act == "down":
|
||||||
|
Down = 1
|
||||||
|
if act == "start":
|
||||||
|
selected_class_id = -1
|
||||||
|
elif act == "stop":
|
||||||
|
selected_class_id = -2
|
||||||
|
elif act == "set-class":
|
||||||
|
if selected_class_id < -1:
|
||||||
|
print("message_handler: WARMING: set class-id while board is stop")
|
||||||
|
else:
|
||||||
|
cl = msg["data"]["class"]
|
||||||
|
selected_class_id = -1 # если не найдем, будут выбраны все классы
|
||||||
|
for i in range(0, len(CLASSES)):
|
||||||
|
if CLASSES[i]["class"] == cl:
|
||||||
|
selected_class_id = i
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
print("============ Initialize connection daemon ============")
|
||||||
|
connection_daemon = ConnectionDaemon()
|
||||||
|
connection_daemon.set_message_handler(message_handler)
|
||||||
|
connection_daemon.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def notify():
|
||||||
|
global startTime
|
||||||
|
endTime = time.time()
|
||||||
|
if endTime - startTime > 1.5: # прошло 1.5 секунды
|
||||||
|
# if 1>0: #режим прерывания сообщений
|
||||||
|
global X
|
||||||
|
global X_New
|
||||||
|
global X_pred
|
||||||
|
if X == 0 and X_pred == 1: # поменялось на 0
|
||||||
|
source = oalOpen("Pot.wav") # Потерян
|
||||||
|
source.play() # воспр. 1 раз
|
||||||
|
startTime = time.time() # отсчёт времени
|
||||||
|
if X==1 and X_pred==1 and X_New==0 and (endTime - startTime > 6):
|
||||||
|
source = oalOpen("Nab.wav") #Потерян
|
||||||
|
source.play() #воспр. 1 раз
|
||||||
|
startTime = time.time() #отсчёт времени
|
||||||
|
if X==1 and X_pred==1 and X_New==1:
|
||||||
|
source = oalOpen("New.wav") #new object
|
||||||
|
source.play() #воспр. 1 раз
|
||||||
|
startTime = time.time() #отсчёт времени
|
||||||
|
elif X == 1 and X_pred == 0: # поменялось на 1
|
||||||
|
source = oalOpen("Zah.wav") # Захвачен
|
||||||
|
source.play() # воспр. 1 раз
|
||||||
|
startTime = time.time() # отсчёт времени
|
||||||
|
X_pred = X # обновляем предыдущее значение
|
||||||
|
|
||||||
|
|
||||||
|
print("[INFO] loading model...")
|
||||||
|
net = cv2.dnn_DetectionModel('AI.cfg', 'AI.weights')
|
||||||
|
|
||||||
|
#net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)
|
||||||
|
#net.setPreferableTarget(cv2.dnn.DNN_TARGET_MYRIAD)
|
||||||
|
|
||||||
|
picSize_X = 640
|
||||||
|
picSize_Y = 480
|
||||||
|
net.setInputSize(128, 128)
|
||||||
|
net.setInputScale(1.0 / 255)
|
||||||
|
net.setInputSwapRB(True)
|
||||||
|
|
||||||
|
print("[INFO] starting video stream...")
|
||||||
|
vs = VideoStream(src=0).start()
|
||||||
|
# warm up the camera for a couple of seconds
|
||||||
|
time.sleep(2.0)
|
||||||
|
|
||||||
|
MAX_sX = 0
|
||||||
|
MAX_sY = 0
|
||||||
|
MAX_eX = 0
|
||||||
|
MAX_eY = 0
|
||||||
|
|
||||||
|
centr_X = 0
|
||||||
|
centr_Y = 0
|
||||||
|
|
||||||
|
pred_centr_X = 0
|
||||||
|
pred_centr_Y = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if selected_class_id >= 0:
|
||||||
|
t0 = time.time()
|
||||||
|
frame = vs.read()
|
||||||
|
#frame = imutils.resize(frame, width=1280, height=720)
|
||||||
|
#(h, w) = frame.shape[:2]
|
||||||
|
|
||||||
|
S_MAX = 0
|
||||||
|
X = 0
|
||||||
|
X_New = 0
|
||||||
|
# находим объекты и возвращаем их параметры
|
||||||
|
classes, confidences, boxes = net.detect(frame, confThreshold=0.18, nmsThreshold=0.5)
|
||||||
|
# создаём рамки и надписи
|
||||||
|
for classId, confidence, box in zip(list(itertools.chain(classes)), list(itertools.chain(confidences)), boxes):
|
||||||
|
# if classId == 39: # вот так делать не стоит, работать такое точно не будет
|
||||||
|
if selected_class_id == -1 or classId == selected_class_id:
|
||||||
|
X = 1
|
||||||
|
|
||||||
|
label = f"{CLASSES[classId]['class']}"
|
||||||
|
label = '%s: %.2f' % (label, confidence)
|
||||||
|
color = CLASSES[classId]["color"]
|
||||||
|
|
||||||
|
labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
||||||
|
left, top, width, heigth = box
|
||||||
|
S = width * heigth
|
||||||
|
print ('S =', S, 'pics')
|
||||||
|
if S>S_MAX:
|
||||||
|
S_MAX = S
|
||||||
|
MAX_sX = left
|
||||||
|
MAX_sY = top
|
||||||
|
MAX_eX = left + width
|
||||||
|
MAX_eY = top + heigth
|
||||||
|
MAX_label = label
|
||||||
|
print("Object detected: ", label)
|
||||||
|
|
||||||
|
if (X == 1):
|
||||||
|
# Draw a rectangle across the boundary of the object
|
||||||
|
cv2.rectangle(frame, (MAX_sX, MAX_sY), (MAX_eX, MAX_eY), color, 2)
|
||||||
|
y = MAX_sY - 15 if MAX_sY - 15 > 15 else MAX_sY + 15
|
||||||
|
# Put a text outside the rectangular detection
|
||||||
|
# Choose the font of your choice: FONT_HERSHEY_SIMPLEX, FONT_HERSHEY_PL>
|
||||||
|
cv2.putText(frame, MAX_label, (MAX_sX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
|
||||||
|
|
||||||
|
centr_X = (MAX_sX+MAX_eX)/2
|
||||||
|
centr_Y = (MAX_sY+MAX_eY)/2
|
||||||
|
|
||||||
|
if (abs(centr_X-pred_centr_X) > picSize_X/4 or abs(centr_Y-pred_centr_Y) > picSize_Y/4):
|
||||||
|
X_New = 1
|
||||||
|
|
||||||
|
if (X == 1 and Left == 0 and Right == 0 and Up == 0 and Down == 0):
|
||||||
|
if (centr_X > (picSize_X/2+picSize_X/10) and centr_Y < (picSize_Y/2+picSize_Y/10) and centr_Y > (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00000111] #Вправо
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y < (picSize_Y/2+picSize_Y/10) and centr_Y > (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00000110] #Влево
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_Y > (picSize_Y/2+picSize_Y/10) and centr_X < (picSize_X/2+picSize_X/10) and centr_X > (picSize_X/2-picSize_X/10)):
|
||||||
|
txData = [0b00001101] #Вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_Y < (picSize_Y/2-picSize_Y/10) and centr_X < (picSize_X/2+picSize_X/10) and centr_X > (picSize_X/2-picSize_X/10)):
|
||||||
|
txData = [0b00001001] #Вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y < (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00001010] #Влево/вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X > (picSize_X/2+picSize_X/10) and centr_Y < (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00001011] #Вправо/вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y > (picSize_Y/2+picSize_Y/10)):
|
||||||
|
txData = [0b00001110] #Влево/вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X > (picSize_X/2+picSize_X/10) and centr_Y > (picSize_Y/2+picSize_Y/10)):
|
||||||
|
txData = [0b00001111] #Вправо/вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
else:
|
||||||
|
txData = [0b00000101] #Центр
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 0 and Right == 1 and Up == 0 and Down == 0):
|
||||||
|
txData = [0b00000111] #Вправо
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 1 and Right == 0 and Up == 0 and Down == 0):
|
||||||
|
txData = [0b00000110] #Влево
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 0 and Right == 0 and Up == 1 and Down == 0):
|
||||||
|
txData = [0b00001001] #Вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 0 and Right == 0 and Up == 0 and Down == 1):
|
||||||
|
txData = [0b00001101] #Вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
|
||||||
|
pred_centr_X = centr_X
|
||||||
|
pred_centr_Y = centr_Y
|
||||||
|
|
||||||
|
# обнуление
|
||||||
|
Left = 0
|
||||||
|
Right = 0
|
||||||
|
Up = 0
|
||||||
|
Down = 0
|
||||||
|
My_FPS = 1 / (time.time() - t0)
|
||||||
|
FPS_label = 'FPS=%2.f' % My_FPS
|
||||||
|
labelSize, baseLine = cv2.getTextSize(FPS_label, cv2.FONT_HERSHEY_SIMPLEX, 1.5, 1)
|
||||||
|
cv2.rectangle(frame, (4, 4), (4 + labelSize[0], 4 + labelSize[1] + baseLine), (255, 0, 155), cv2.FILLED)
|
||||||
|
cv2.putText(frame, FPS_label, (4, 4 + labelSize[1]), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 0))
|
||||||
|
notify()
|
||||||
|
|
||||||
|
# отправка фрейма на сервер
|
||||||
|
connection_daemon.send_frame(frame)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# отправка раз в секунду пустого фрейма
|
||||||
|
connection_daemon.send_frame(None)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
spi.close()
|
||||||
|
# Destroy windows and cleanup
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
# Stop the video stream
|
||||||
|
vs.stop()
|
350
board5.py
Normal file
350
board5.py
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
# import packages
|
||||||
|
from openal import *
|
||||||
|
from imutils.video import VideoStream
|
||||||
|
import itertools
|
||||||
|
import imutils
|
||||||
|
import time
|
||||||
|
import cv2
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
import spidev
|
||||||
|
|
||||||
|
# Client
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from threading import Thread
|
||||||
|
from streamer_utils import SocketBlocksWrapper, read_json_config
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
spi = spidev.SpiDev()
|
||||||
|
spi.open(1, 0)
|
||||||
|
|
||||||
|
spi.bits_per_word = 8
|
||||||
|
spi.max_speed_hz = 500000
|
||||||
|
|
||||||
|
X = bool(0) # иниц-я глоб. переменной
|
||||||
|
X_New = bool(0)
|
||||||
|
X_pred = bool(0) # иниц-я глоб. переменной
|
||||||
|
startTime = float(time.time() - 10) # иниц-я глоб. переменной
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG = read_json_config('board-config.json')
|
||||||
|
CLASSES = read_json_config('classes.json')
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionDaemon(Thread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(daemon=True)
|
||||||
|
self._sock = None
|
||||||
|
self._message_handler = None
|
||||||
|
|
||||||
|
def set_message_handler(self, handler: callable):
|
||||||
|
self._message_handler = handler
|
||||||
|
|
||||||
|
def __do_call_message_handler(self, res):
|
||||||
|
if self._message_handler is not None:
|
||||||
|
try:
|
||||||
|
self._message_handler(res)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def __do_session(self):
|
||||||
|
try:
|
||||||
|
with SocketBlocksWrapper.connect(CONFIG['server-address'], CONFIG['server-port']) as sock:
|
||||||
|
print("ConnectionDaemon: open connection")
|
||||||
|
self._sock = sock
|
||||||
|
self._sock.write_object({'type': 'auth', 'client-type': 'board', 'name': CONFIG['name']})
|
||||||
|
res = self._sock.read_object()
|
||||||
|
if res is None:
|
||||||
|
return
|
||||||
|
print(res)
|
||||||
|
if 'status' in res:
|
||||||
|
if res['status'] == 'success':
|
||||||
|
while True:
|
||||||
|
res = self._sock.read_object()
|
||||||
|
if res is None:
|
||||||
|
break
|
||||||
|
self.__do_call_message_handler(res)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
print("ConnectionDaemon: start session...")
|
||||||
|
self.__do_session()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
def send_frame(self, fr):
|
||||||
|
if self._sock is not None:
|
||||||
|
try:
|
||||||
|
to_send = {
|
||||||
|
'type': 'video',
|
||||||
|
'data': None,
|
||||||
|
"selected-class": selected_class_id
|
||||||
|
}
|
||||||
|
if fr is not None:
|
||||||
|
fr = imutils.resize(fr, width=640, height=360)
|
||||||
|
buffer = cv2.imencode('.jpg', fr, [int(cv2.IMWRITE_JPEG_QUALITY), 60])[1]
|
||||||
|
data_encode = np.array(buffer)
|
||||||
|
to_send["data"] = data_encode.tobytes()
|
||||||
|
self._sock.write_object(to_send)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def send_image(self, img: Image):
|
||||||
|
if self._sock is not None:
|
||||||
|
try:
|
||||||
|
out = io.BytesIO()
|
||||||
|
img.save(out, format="JPEG")
|
||||||
|
self._sock.write_object({
|
||||||
|
'type': 'video',
|
||||||
|
'data': out.getvalue(),
|
||||||
|
"selected-class": selected_class_id
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
# камера не движется
|
||||||
|
Left = bool(0)
|
||||||
|
Right = bool(0)
|
||||||
|
Up = bool(0)
|
||||||
|
Down = bool(0)
|
||||||
|
lazer = bool(0)
|
||||||
|
|
||||||
|
# -2 = нейронка отключена, -1 = включены все классы, остальное - id класса из списка CLASSES
|
||||||
|
selected_class_id = -2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# функция, которая вызывается при получении команды
|
||||||
|
def message_handler(msg):
|
||||||
|
global selected_class_id
|
||||||
|
global Left
|
||||||
|
global Right
|
||||||
|
global Up
|
||||||
|
global Down
|
||||||
|
global lazer
|
||||||
|
print(msg)
|
||||||
|
if msg["type"] == "command":
|
||||||
|
# отлично, наше сообщение
|
||||||
|
act = msg["data"]["action"]
|
||||||
|
if act == "lazerOn":
|
||||||
|
lazer = 1
|
||||||
|
if act == "lazerOff":
|
||||||
|
lazer = 0
|
||||||
|
if act == "left":
|
||||||
|
Left = 1
|
||||||
|
if act == "right":
|
||||||
|
Right = 1
|
||||||
|
if act == "up":
|
||||||
|
Up = 1
|
||||||
|
if act == "down":
|
||||||
|
Down = 1
|
||||||
|
if act == "start":
|
||||||
|
selected_class_id = -1
|
||||||
|
elif act == "stop":
|
||||||
|
selected_class_id = -2
|
||||||
|
elif act == "set-class":
|
||||||
|
if selected_class_id < -1:
|
||||||
|
print("message_handler: WARMING: set class-id while board is stop")
|
||||||
|
else:
|
||||||
|
cl = msg["data"]["class"]
|
||||||
|
selected_class_id = -1 # если не найдем, будут выбраны все классы
|
||||||
|
for i in range(0, len(CLASSES)):
|
||||||
|
if CLASSES[i]["class"] == cl:
|
||||||
|
selected_class_id = i
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
print("============ Initialize connection daemon ============")
|
||||||
|
connection_daemon = ConnectionDaemon()
|
||||||
|
connection_daemon.set_message_handler(message_handler)
|
||||||
|
connection_daemon.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def notify():
|
||||||
|
global startTime
|
||||||
|
endTime = time.time()
|
||||||
|
if endTime - startTime > 1.5: # прошло 1.5 секунды
|
||||||
|
# if 1>0: #режим прерывания сообщений
|
||||||
|
global X
|
||||||
|
global X_New
|
||||||
|
global X_pred
|
||||||
|
if X == 0 and X_pred == 1: # поменялось на 0
|
||||||
|
source = oalOpen("Pot.wav") # Потерян
|
||||||
|
source.play() # воспр. 1 раз
|
||||||
|
startTime = time.time() # отсчёт времени
|
||||||
|
if X==1 and X_pred==1 and X_New==0 and (endTime - startTime > 6):
|
||||||
|
source = oalOpen("Nab.wav") #Потерян
|
||||||
|
source.play() #воспр. 1 раз
|
||||||
|
startTime = time.time() #отсчёт времени
|
||||||
|
if X==1 and X_pred==1 and X_New==1:
|
||||||
|
source = oalOpen("New.wav") #new object
|
||||||
|
source.play() #воспр. 1 раз
|
||||||
|
startTime = time.time() #отсчёт времени
|
||||||
|
elif X == 1 and X_pred == 0: # поменялось на 1
|
||||||
|
source = oalOpen("Zah.wav") # Захвачен
|
||||||
|
source.play() # воспр. 1 раз
|
||||||
|
startTime = time.time() # отсчёт времени
|
||||||
|
X_pred = X # обновляем предыдущее значение
|
||||||
|
|
||||||
|
|
||||||
|
print("[INFO] loading model...")
|
||||||
|
net = cv2.dnn_DetectionModel('AI.cfg', 'AI.weights')
|
||||||
|
|
||||||
|
#net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)
|
||||||
|
#net.setPreferableTarget(cv2.dnn.DNN_TARGET_MYRIAD)
|
||||||
|
|
||||||
|
picSize_X = 640
|
||||||
|
picSize_Y = 480
|
||||||
|
net.setInputSize(128, 128)
|
||||||
|
net.setInputScale(1.0 / 255)
|
||||||
|
net.setInputSwapRB(True)
|
||||||
|
|
||||||
|
print("[INFO] starting video stream...")
|
||||||
|
vs = VideoStream(src=0).start()
|
||||||
|
# warm up the camera for a couple of seconds
|
||||||
|
time.sleep(2.0)
|
||||||
|
|
||||||
|
MAX_sX = 0
|
||||||
|
MAX_sY = 0
|
||||||
|
MAX_eX = 0
|
||||||
|
MAX_eY = 0
|
||||||
|
|
||||||
|
centr_X = 0
|
||||||
|
centr_Y = 0
|
||||||
|
|
||||||
|
pred_centr_X = 0
|
||||||
|
pred_centr_Y = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if lazer == 1:
|
||||||
|
txData = [0b11000000] #Вкл
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif lazer == 0:
|
||||||
|
txData = [0b10000000] #Выкл
|
||||||
|
spi.xfer(txData)
|
||||||
|
if selected_class_id >= -1:
|
||||||
|
t0 = time.time()
|
||||||
|
frame = vs.read()
|
||||||
|
#frame = imutils.resize(frame, width=1280, height=720)
|
||||||
|
#(h, w) = frame.shape[:2]
|
||||||
|
|
||||||
|
S_MAX = 0
|
||||||
|
X = 0
|
||||||
|
X_New = 0
|
||||||
|
# находим объекты и возвращаем их параметры
|
||||||
|
classes, confidences, boxes = net.detect(frame, confThreshold=0.18, nmsThreshold=0.5)
|
||||||
|
# создаём рамки и надписи
|
||||||
|
for classId, confidence, box in zip(list(itertools.chain(classes)), list(itertools.chain(confidences)), boxes):
|
||||||
|
# if classId == 39: # вот так делать не стоит, работать такое точно не будет
|
||||||
|
if selected_class_id == -1 or classId == selected_class_id:
|
||||||
|
X = 1
|
||||||
|
|
||||||
|
label = f"{CLASSES[classId]['class']}"
|
||||||
|
label = '%s: %.2f' % (label, confidence)
|
||||||
|
color = CLASSES[classId]["color"]
|
||||||
|
|
||||||
|
labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
||||||
|
left, top, width, heigth = box
|
||||||
|
S = width * heigth
|
||||||
|
print ('S =', S, 'pics')
|
||||||
|
if S>S_MAX:
|
||||||
|
S_MAX = S
|
||||||
|
MAX_sX = left
|
||||||
|
MAX_sY = top
|
||||||
|
MAX_eX = left + width
|
||||||
|
MAX_eY = top + heigth
|
||||||
|
MAX_label = label
|
||||||
|
print("Object detected: ", label)
|
||||||
|
|
||||||
|
# TODO этот кусок кода перенести чуть выше и сделать так, чтобы он корректно отрисовывал все найденые объекты, а не только один как сейчас
|
||||||
|
if (X == 1):
|
||||||
|
# Draw a rectangle across the boundary of the object
|
||||||
|
cv2.rectangle(frame, (MAX_sX, MAX_sY), (MAX_eX, MAX_eY), color, 2)
|
||||||
|
y = MAX_sY - 15 if MAX_sY - 15 > 15 else MAX_sY + 15
|
||||||
|
# Put a text outside the rectangular detection
|
||||||
|
# Choose the font of your choice: FONT_HERSHEY_SIMPLEX, FONT_HERSHEY_PL>
|
||||||
|
cv2.putText(frame, MAX_label, (MAX_sX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
|
||||||
|
|
||||||
|
centr_X = (MAX_sX+MAX_eX)/2
|
||||||
|
centr_Y = (MAX_sY+MAX_eY)/2
|
||||||
|
|
||||||
|
if (abs(centr_X-pred_centr_X) > picSize_X/4 or abs(centr_Y-pred_centr_Y) > picSize_Y/4):
|
||||||
|
X_New = 1
|
||||||
|
|
||||||
|
if (X == 1 and Left == 0 and Right == 0 and Up == 0 and Down == 0):
|
||||||
|
if (centr_X > (picSize_X/2+picSize_X/10) and centr_Y < (picSize_Y/2+picSize_Y/10) and centr_Y > (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00000111] #Вправо
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y < (picSize_Y/2+picSize_Y/10) and centr_Y > (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00000110] #Влево
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_Y > (picSize_Y/2+picSize_Y/10) and centr_X < (picSize_X/2+picSize_X/10) and centr_X > (picSize_X/2-picSize_X/10)):
|
||||||
|
txData = [0b00001101] #Вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_Y < (picSize_Y/2-picSize_Y/10) and centr_X < (picSize_X/2+picSize_X/10) and centr_X > (picSize_X/2-picSize_X/10)):
|
||||||
|
txData = [0b00001001] #Вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y < (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00001010] #Влево/вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X > (picSize_X/2+picSize_X/10) and centr_Y < (picSize_Y/2-picSize_Y/10)):
|
||||||
|
txData = [0b00001011] #Вправо/вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y > (picSize_Y/2+picSize_Y/10)):
|
||||||
|
txData = [0b00001110] #Влево/вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (centr_X > (picSize_X/2+picSize_X/10) and centr_Y > (picSize_Y/2+picSize_Y/10)):
|
||||||
|
txData = [0b00001111] #Вправо/вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
else:
|
||||||
|
txData = [0b00000101] #Центр
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 0 and Right == 1 and Up == 0 and Down == 0):
|
||||||
|
txData = [0b00000111] #Вправо
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 1 and Right == 0 and Up == 0 and Down == 0):
|
||||||
|
txData = [0b00000110] #Влево
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 0 and Right == 0 and Up == 1 and Down == 0):
|
||||||
|
txData = [0b00001001] #Вверх
|
||||||
|
spi.xfer(txData)
|
||||||
|
elif (Left == 0 and Right == 0 and Up == 0 and Down == 1):
|
||||||
|
txData = [0b00001101] #Вниз
|
||||||
|
spi.xfer(txData)
|
||||||
|
|
||||||
|
pred_centr_X = centr_X
|
||||||
|
pred_centr_Y = centr_Y
|
||||||
|
|
||||||
|
# обнуление
|
||||||
|
Left = 0
|
||||||
|
Right = 0
|
||||||
|
Up = 0
|
||||||
|
Down = 0
|
||||||
|
My_FPS = 1 / (time.time() - t0)
|
||||||
|
FPS_label = 'FPS=%2.f' % My_FPS
|
||||||
|
labelSize, baseLine = cv2.getTextSize(FPS_label, cv2.FONT_HERSHEY_SIMPLEX, 1.5, 1)
|
||||||
|
cv2.rectangle(frame, (4, 4), (4 + labelSize[0], 4 + labelSize[1] + baseLine), (255, 0, 155), cv2.FILLED)
|
||||||
|
cv2.putText(frame, FPS_label, (4, 4 + labelSize[1]), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 0))
|
||||||
|
notify()
|
||||||
|
|
||||||
|
# отправка фрейма на сервер
|
||||||
|
connection_daemon.send_frame(frame)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# отправка раз в секунду пустого фрейма, типа нет видео
|
||||||
|
connection_daemon.send_frame(None)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
spi.close()
|
||||||
|
# Destroy windows and cleanup
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
# Stop the video stream
|
||||||
|
vs.stop()
|
BIN
boards.sqlite3
Normal file
BIN
boards.sqlite3
Normal file
Binary file not shown.
722
classes.json
Normal file
722
classes.json
Normal file
@ -0,0 +1,722 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"class": "person",
|
||||||
|
"label": "человек",
|
||||||
|
"color": [
|
||||||
|
8,
|
||||||
|
191,
|
||||||
|
82
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bicycle",
|
||||||
|
"label": "велосипед",
|
||||||
|
"color": [
|
||||||
|
213,
|
||||||
|
80,
|
||||||
|
133
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "car",
|
||||||
|
"label": "машина",
|
||||||
|
"color": [
|
||||||
|
63,
|
||||||
|
126,
|
||||||
|
162
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "motorbike",
|
||||||
|
"label": "motorbike",
|
||||||
|
"color": [
|
||||||
|
183,
|
||||||
|
90,
|
||||||
|
201
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "aeroplane",
|
||||||
|
"label": "aeroplane",
|
||||||
|
"color": [
|
||||||
|
6,
|
||||||
|
14,
|
||||||
|
236
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bus",
|
||||||
|
"label": "автобус",
|
||||||
|
"color": [
|
||||||
|
213,
|
||||||
|
87,
|
||||||
|
227
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "train",
|
||||||
|
"label": "поезд",
|
||||||
|
"color": [
|
||||||
|
193,
|
||||||
|
123,
|
||||||
|
6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "truck",
|
||||||
|
"label": "truck",
|
||||||
|
"color": [
|
||||||
|
116,
|
||||||
|
122,
|
||||||
|
201
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "boat",
|
||||||
|
"label": "лодка",
|
||||||
|
"color": [
|
||||||
|
194,
|
||||||
|
224,
|
||||||
|
88
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "traffic light",
|
||||||
|
"label": "traffic light",
|
||||||
|
"color": [
|
||||||
|
46,
|
||||||
|
164,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "fire hydrant",
|
||||||
|
"label": "fire hydrant",
|
||||||
|
"color": [
|
||||||
|
181,
|
||||||
|
19,
|
||||||
|
144
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "stop sign",
|
||||||
|
"label": "stop sign",
|
||||||
|
"color": [
|
||||||
|
17,
|
||||||
|
245,
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "parking meter",
|
||||||
|
"label": "parking meter",
|
||||||
|
"color": [
|
||||||
|
185,
|
||||||
|
144,
|
||||||
|
158
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bench",
|
||||||
|
"label": "bench",
|
||||||
|
"color": [
|
||||||
|
93,
|
||||||
|
179,
|
||||||
|
129
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bird",
|
||||||
|
"label": "птица",
|
||||||
|
"color": [
|
||||||
|
97,
|
||||||
|
55,
|
||||||
|
110
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "cat",
|
||||||
|
"label": "кошка",
|
||||||
|
"color": [
|
||||||
|
151,
|
||||||
|
228,
|
||||||
|
14
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "dog",
|
||||||
|
"label": "собака",
|
||||||
|
"color": [
|
||||||
|
105,
|
||||||
|
169,
|
||||||
|
98
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "horse",
|
||||||
|
"label": "лошадь",
|
||||||
|
"color": [
|
||||||
|
29,
|
||||||
|
183,
|
||||||
|
166
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "sheep",
|
||||||
|
"label": "овца",
|
||||||
|
"color": [
|
||||||
|
43,
|
||||||
|
245,
|
||||||
|
65
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "cow",
|
||||||
|
"label": "короча",
|
||||||
|
"color": [
|
||||||
|
33,
|
||||||
|
34,
|
||||||
|
24
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "elephant",
|
||||||
|
"label": "слон",
|
||||||
|
"color": [
|
||||||
|
191,
|
||||||
|
42,
|
||||||
|
137
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bear",
|
||||||
|
"label": "мишка",
|
||||||
|
"color": [
|
||||||
|
151,
|
||||||
|
21,
|
||||||
|
242
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "zebra",
|
||||||
|
"label": "зебра",
|
||||||
|
"color": [
|
||||||
|
211,
|
||||||
|
10,
|
||||||
|
237
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "giraffe",
|
||||||
|
"label": "giraffe",
|
||||||
|
"color": [
|
||||||
|
94,
|
||||||
|
156,
|
||||||
|
124
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "backpack",
|
||||||
|
"label": "backpack",
|
||||||
|
"color": [
|
||||||
|
255,
|
||||||
|
194,
|
||||||
|
176
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "umbrella",
|
||||||
|
"label": "umbrella",
|
||||||
|
"color": [
|
||||||
|
173,
|
||||||
|
196,
|
||||||
|
240
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "handbag",
|
||||||
|
"label": "handbag",
|
||||||
|
"color": [
|
||||||
|
3,
|
||||||
|
157,
|
||||||
|
60
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "tie",
|
||||||
|
"label": "tie",
|
||||||
|
"color": [
|
||||||
|
47,
|
||||||
|
19,
|
||||||
|
83
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "suitcase",
|
||||||
|
"label": "suitcase",
|
||||||
|
"color": [
|
||||||
|
84,
|
||||||
|
62,
|
||||||
|
207
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "frisbee",
|
||||||
|
"label": "frisbee",
|
||||||
|
"color": [
|
||||||
|
140,
|
||||||
|
135,
|
||||||
|
50
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "skis",
|
||||||
|
"label": "skis",
|
||||||
|
"color": [
|
||||||
|
37,
|
||||||
|
133,
|
||||||
|
177
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "snowboard",
|
||||||
|
"label": "snowboard",
|
||||||
|
"color": [
|
||||||
|
88,
|
||||||
|
128,
|
||||||
|
229
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "sports ball",
|
||||||
|
"label": "sports ball",
|
||||||
|
"color": [
|
||||||
|
39,
|
||||||
|
30,
|
||||||
|
120
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "kite",
|
||||||
|
"label": "kite",
|
||||||
|
"color": [
|
||||||
|
104,
|
||||||
|
15,
|
||||||
|
104
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "baseball bat",
|
||||||
|
"label": "baseball bat",
|
||||||
|
"color": [
|
||||||
|
136,
|
||||||
|
0,
|
||||||
|
226
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "baseball glove",
|
||||||
|
"label": "baseball glove",
|
||||||
|
"color": [
|
||||||
|
129,
|
||||||
|
16,
|
||||||
|
120
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "skateboard",
|
||||||
|
"label": "skateboard",
|
||||||
|
"color": [
|
||||||
|
245,
|
||||||
|
31,
|
||||||
|
8
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "surfboard",
|
||||||
|
"label": "surfboard",
|
||||||
|
"color": [
|
||||||
|
15,
|
||||||
|
23,
|
||||||
|
32
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "tennis racket",
|
||||||
|
"label": "tennis racket",
|
||||||
|
"color": [
|
||||||
|
191,
|
||||||
|
175,
|
||||||
|
44
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bottle",
|
||||||
|
"label": "бутылка",
|
||||||
|
"color": [
|
||||||
|
130,
|
||||||
|
81,
|
||||||
|
23
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "wine glass",
|
||||||
|
"label": "wine glass",
|
||||||
|
"color": [
|
||||||
|
52,
|
||||||
|
204,
|
||||||
|
75
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "cup",
|
||||||
|
"label": "cup",
|
||||||
|
"color": [
|
||||||
|
217,
|
||||||
|
4,
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "fork",
|
||||||
|
"label": "fork",
|
||||||
|
"color": [
|
||||||
|
22,
|
||||||
|
155,
|
||||||
|
17
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "knife",
|
||||||
|
"label": "knife",
|
||||||
|
"color": [
|
||||||
|
195,
|
||||||
|
230,
|
||||||
|
217
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "spoon",
|
||||||
|
"label": "spoon",
|
||||||
|
"color": [
|
||||||
|
196,
|
||||||
|
155,
|
||||||
|
208
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bowl",
|
||||||
|
"label": "bowl",
|
||||||
|
"color": [
|
||||||
|
53,
|
||||||
|
79,
|
||||||
|
142
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "banana",
|
||||||
|
"label": "banana",
|
||||||
|
"color": [
|
||||||
|
151,
|
||||||
|
207,
|
||||||
|
131
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "apple",
|
||||||
|
"label": "apple",
|
||||||
|
"color": [
|
||||||
|
199,
|
||||||
|
225,
|
||||||
|
68
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "sandwich",
|
||||||
|
"label": "sandwich",
|
||||||
|
"color": [
|
||||||
|
193,
|
||||||
|
158,
|
||||||
|
167
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "orange",
|
||||||
|
"label": "orange",
|
||||||
|
"color": [
|
||||||
|
74,
|
||||||
|
189,
|
||||||
|
95
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "broccoli",
|
||||||
|
"label": "broccoli",
|
||||||
|
"color": [
|
||||||
|
48,
|
||||||
|
234,
|
||||||
|
238
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "carrot",
|
||||||
|
"label": "carrot",
|
||||||
|
"color": [
|
||||||
|
225,
|
||||||
|
113,
|
||||||
|
215
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "hot dog",
|
||||||
|
"label": "hot dog",
|
||||||
|
"color": [
|
||||||
|
68,
|
||||||
|
168,
|
||||||
|
87
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "pizza",
|
||||||
|
"label": "pizza",
|
||||||
|
"color": [
|
||||||
|
163,
|
||||||
|
151,
|
||||||
|
216
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "donut",
|
||||||
|
"label": "donut",
|
||||||
|
"color": [
|
||||||
|
211,
|
||||||
|
179,
|
||||||
|
218
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "cake",
|
||||||
|
"label": "cake",
|
||||||
|
"color": [
|
||||||
|
45,
|
||||||
|
98,
|
||||||
|
135
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "chair",
|
||||||
|
"label": "chair",
|
||||||
|
"color": [
|
||||||
|
11,
|
||||||
|
22,
|
||||||
|
204
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "sofa",
|
||||||
|
"label": "sofa",
|
||||||
|
"color": [
|
||||||
|
187,
|
||||||
|
207,
|
||||||
|
214
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "pottedplant",
|
||||||
|
"label": "pottedplant",
|
||||||
|
"color": [
|
||||||
|
88,
|
||||||
|
7,
|
||||||
|
174
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "bed",
|
||||||
|
"label": "bed",
|
||||||
|
"color": [
|
||||||
|
4,
|
||||||
|
180,
|
||||||
|
42
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "diningtable",
|
||||||
|
"label": "diningtable",
|
||||||
|
"color": [
|
||||||
|
39,
|
||||||
|
112,
|
||||||
|
122
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "toilet",
|
||||||
|
"label": "toilet",
|
||||||
|
"color": [
|
||||||
|
99,
|
||||||
|
23,
|
||||||
|
252
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "tvmonitor",
|
||||||
|
"label": "tvmonitor",
|
||||||
|
"color": [
|
||||||
|
147,
|
||||||
|
33,
|
||||||
|
230
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "laptop",
|
||||||
|
"label": "laptop",
|
||||||
|
"color": [
|
||||||
|
240,
|
||||||
|
9,
|
||||||
|
130
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "mouse",
|
||||||
|
"label": "mouse",
|
||||||
|
"color": [
|
||||||
|
83,
|
||||||
|
215,
|
||||||
|
128
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "remote",
|
||||||
|
"label": "remote",
|
||||||
|
"color": [
|
||||||
|
112,
|
||||||
|
73,
|
||||||
|
202
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "keyboard",
|
||||||
|
"label": "keyboard",
|
||||||
|
"color": [
|
||||||
|
222,
|
||||||
|
219,
|
||||||
|
122
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "cell phone",
|
||||||
|
"label": "cell phone",
|
||||||
|
"color": [
|
||||||
|
95,
|
||||||
|
241,
|
||||||
|
55
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "microwave",
|
||||||
|
"label": "microwave",
|
||||||
|
"color": [
|
||||||
|
1,
|
||||||
|
207,
|
||||||
|
104
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "oven",
|
||||||
|
"label": "oven",
|
||||||
|
"color": [
|
||||||
|
68,
|
||||||
|
0,
|
||||||
|
254
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "toaster",
|
||||||
|
"label": "toaster",
|
||||||
|
"color": [
|
||||||
|
69,
|
||||||
|
118,
|
||||||
|
241
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "sink",
|
||||||
|
"label": "sink",
|
||||||
|
"color": [
|
||||||
|
147,
|
||||||
|
186,
|
||||||
|
199
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "refrigerator",
|
||||||
|
"label": "refrigerator",
|
||||||
|
"color": [
|
||||||
|
140,
|
||||||
|
150,
|
||||||
|
173
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "book",
|
||||||
|
"label": "book",
|
||||||
|
"color": [
|
||||||
|
151,
|
||||||
|
28,
|
||||||
|
129
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "clock",
|
||||||
|
"label": "clock",
|
||||||
|
"color": [
|
||||||
|
87,
|
||||||
|
114,
|
||||||
|
56
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "vase",
|
||||||
|
"label": "vase",
|
||||||
|
"color": [
|
||||||
|
228,
|
||||||
|
65,
|
||||||
|
145
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "scissors",
|
||||||
|
"label": "scissors",
|
||||||
|
"color": [
|
||||||
|
106,
|
||||||
|
245,
|
||||||
|
80
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "teddy bear",
|
||||||
|
"label": "teddy bear",
|
||||||
|
"color": [
|
||||||
|
56,
|
||||||
|
115,
|
||||||
|
221
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "hair drier",
|
||||||
|
"label": "hair drier",
|
||||||
|
"color": [
|
||||||
|
213,
|
||||||
|
201,
|
||||||
|
111
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "toothbrush",
|
||||||
|
"label": "toothbrush",
|
||||||
|
"color": [
|
||||||
|
125,
|
||||||
|
106,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
4
client-config.json
Normal file
4
client-config.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"server-address": "192.168.2.91",
|
||||||
|
"server-port": 40100
|
||||||
|
}
|
383
client.py
Normal file
383
client.py
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
#!/bin/python
|
||||||
|
|
||||||
|
import io
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from threading import Thread, Event, Lock
|
||||||
|
import gi
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from streamer_utils import SocketBlocksWrapper, read_json_config
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk, GLib, GdkPixbuf, Gdk
|
||||||
|
|
||||||
|
CLASSES = read_json_config('classes.json')
|
||||||
|
CONFIG = read_json_config('client-config.json')
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionDaemon:
|
||||||
|
def __init__(self, address, port, object_receive_callback: callable):
|
||||||
|
self._address = address
|
||||||
|
self._port = port
|
||||||
|
self._object_receive_callback = object_receive_callback
|
||||||
|
|
||||||
|
self._sock = None
|
||||||
|
self._lock = Lock()
|
||||||
|
|
||||||
|
self.__login = None
|
||||||
|
self.__password = None
|
||||||
|
|
||||||
|
# объект события, только для ожидания авторизации
|
||||||
|
self.__auth_event = Event()
|
||||||
|
self.__auth_callback = None
|
||||||
|
|
||||||
|
self.__auth_callback_is_done = False
|
||||||
|
self.__auth_success = False
|
||||||
|
|
||||||
|
self._conn_thread = Thread(target=self.__run, daemon=True)
|
||||||
|
self._conn_thread.start()
|
||||||
|
|
||||||
|
def auth(self, login, password, callback: callable):
|
||||||
|
with self._lock:
|
||||||
|
self.__login = login
|
||||||
|
self.__password = password
|
||||||
|
self.__auth_callback = callback
|
||||||
|
self.__auth_event.set()
|
||||||
|
|
||||||
|
def __do_session(self, login, password, auth_callback: callable):
|
||||||
|
try:
|
||||||
|
with SocketBlocksWrapper.connect(CONFIG["server-address"], CONFIG["server-port"]) as sock:
|
||||||
|
with self._lock:
|
||||||
|
self._sock = sock
|
||||||
|
sock.write_object(
|
||||||
|
{
|
||||||
|
'type': 'auth',
|
||||||
|
'client-type': 'client',
|
||||||
|
'target': login,
|
||||||
|
'password': password
|
||||||
|
})
|
||||||
|
res = sock.read_object()
|
||||||
|
print(res)
|
||||||
|
if 'status' in res:
|
||||||
|
if res['status'] == 'success':
|
||||||
|
self.__auth_success = True
|
||||||
|
if auth_callback is not None:
|
||||||
|
auth_callback(True, "success")
|
||||||
|
self.__auth_callback_is_done = True
|
||||||
|
while True:
|
||||||
|
res = sock.read_object()
|
||||||
|
if res is None:
|
||||||
|
break
|
||||||
|
with self._lock:
|
||||||
|
callback = self._object_receive_callback
|
||||||
|
callback(res)
|
||||||
|
else:
|
||||||
|
self.__auth_callback_is_done = True
|
||||||
|
if auth_callback is not None:
|
||||||
|
auth_callback(False, res['description'])
|
||||||
|
else:
|
||||||
|
raise Exception("'status' is not defined in response")
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
with self._lock:
|
||||||
|
self._sock = None
|
||||||
|
|
||||||
|
def __run(self):
|
||||||
|
# нужно ждать данных авторизации
|
||||||
|
while True:
|
||||||
|
if not self.__auth_success:
|
||||||
|
self.__auth_event.wait()
|
||||||
|
with self._lock:
|
||||||
|
login, password, callback = self.__login, self.__password, self.__auth_callback
|
||||||
|
self.__auth_callback_is_done = False
|
||||||
|
self.__auth_event.clear()
|
||||||
|
|
||||||
|
if login is None or password is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# делаем сессию
|
||||||
|
self.__do_session(login, password, callback)
|
||||||
|
|
||||||
|
if not self.__auth_callback_is_done and callback is not None and callable(callback):
|
||||||
|
callback(False, "Ошибка ввода-вывода")
|
||||||
|
|
||||||
|
if not self.__auth_success:
|
||||||
|
with self._lock:
|
||||||
|
if not self.__auth_event.is_set():
|
||||||
|
self.__login = None
|
||||||
|
self.__password = None
|
||||||
|
else:
|
||||||
|
time.sleep(5)
|
||||||
|
print("Try to reconnect...")
|
||||||
|
|
||||||
|
def send_object(self, obj: dict):
|
||||||
|
try:
|
||||||
|
self._sock.write_object(obj)
|
||||||
|
except Exception as e:
|
||||||
|
# traceback.print_exc()
|
||||||
|
print(f"Failed to send command {obj} ({e})")
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(Gtk.Window):
|
||||||
|
def __create_main_widget(self):
|
||||||
|
# основная "коробка"
|
||||||
|
root_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
root_box.connect("key-press-event", self.on_key_press)
|
||||||
|
|
||||||
|
self.image = Gtk.Image()
|
||||||
|
self.image.set_vexpand(True)
|
||||||
|
self.image.set_can_focus(True)
|
||||||
|
|
||||||
|
no_connection_label = Gtk.Label(label="Нет подключения к плате")
|
||||||
|
no_connection_label.set_vexpand(True)
|
||||||
|
no_connection_label.set_hexpand(True)
|
||||||
|
|
||||||
|
no_video_label = Gtk.Label(label="Плата подключена\nОжидание действия пользователя")
|
||||||
|
no_video_label.set_vexpand(True)
|
||||||
|
no_video_label.set_hexpand(True)
|
||||||
|
|
||||||
|
self._image_stack = Gtk.Stack()
|
||||||
|
self._image_stack.set_transition_type(Gtk.StackTransitionType.SLIDE_DOWN)
|
||||||
|
self._image_stack.add_named(no_connection_label, "no-connection")
|
||||||
|
self._image_stack.add_named(no_video_label, "no-video")
|
||||||
|
self._image_stack.add_named(self.image, "video")
|
||||||
|
|
||||||
|
root_box.pack_start(self._image_stack, expand=True, fill=True, padding=0)
|
||||||
|
|
||||||
|
# нижняя сторона окна, панелька с управлением
|
||||||
|
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
|
||||||
|
toolbar.set_halign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
button_start = Gtk.Button(label="Старт")
|
||||||
|
button_start.connect("clicked", lambda widget: self.__send_action('start'))
|
||||||
|
|
||||||
|
button_stop = Gtk.Button(label="Стоп")
|
||||||
|
button_stop.connect("clicked", lambda widget: self.__send_action('stop'))
|
||||||
|
|
||||||
|
button_lazer_start = Gtk.Button(label="Старт лазер")
|
||||||
|
button_lazer_start.connect("clicked", lambda widget: self.__send_action('lazerOn'))
|
||||||
|
|
||||||
|
button_lazer_stop = Gtk.Button(label="Стоп лазер")
|
||||||
|
button_lazer_stop.connect("clicked", lambda widget: self.__send_action('lazerOff'))
|
||||||
|
|
||||||
|
self.select_class = Gtk.ComboBoxText()
|
||||||
|
self.select_class.connect("changed", self.on_class_select)
|
||||||
|
|
||||||
|
sorted_list = [c for c in CLASSES]
|
||||||
|
self.select_class.append_text("<__ (все) __>")
|
||||||
|
for c in sorted_list:
|
||||||
|
self.select_class.append_text(c["label"])
|
||||||
|
|
||||||
|
toolbar.pack_start(button_start, expand=False, fill=False, padding=0)
|
||||||
|
toolbar.pack_start(button_stop, expand=False, fill=False, padding=0)
|
||||||
|
toolbar.pack_start(self.select_class, expand=False, fill=False, padding=0)
|
||||||
|
toolbar.pack_start(button_lazer_start, expand=False, fill=False, padding=0)
|
||||||
|
toolbar.pack_start(button_lazer_stop, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
|
root_box.pack_start(toolbar, expand=False, fill=True, padding=0)
|
||||||
|
|
||||||
|
return root_box
|
||||||
|
|
||||||
|
def __connection_auth_handler(self, result, message):
|
||||||
|
def gui_function():
|
||||||
|
self._spinner.stop()
|
||||||
|
self.__password_entry.set_editable(True)
|
||||||
|
self.__login_entry.set_editable(True)
|
||||||
|
self.__button_auth.set_sensitive(True)
|
||||||
|
if result:
|
||||||
|
self.root.set_visible_child_name("main")
|
||||||
|
else:
|
||||||
|
dialog = Gtk.MessageDialog(
|
||||||
|
transient_for=self,
|
||||||
|
flags=0,
|
||||||
|
message_type=Gtk.MessageType.ERROR,
|
||||||
|
buttons=Gtk.ButtonsType.OK,
|
||||||
|
text=message,
|
||||||
|
title="Ошибка аутентификации"
|
||||||
|
)
|
||||||
|
dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
GLib.idle_add(lambda: gui_function())
|
||||||
|
|
||||||
|
def __auth_handler(self, widget):
|
||||||
|
self._spinner.start()
|
||||||
|
self.__password_entry.set_editable(False)
|
||||||
|
self.__login_entry.set_editable(False)
|
||||||
|
self.__button_auth.set_sensitive(False)
|
||||||
|
self._daemon.auth(self.__login_entry.get_text(),
|
||||||
|
self.__password_entry.get_text(),
|
||||||
|
self.__connection_auth_handler)
|
||||||
|
|
||||||
|
def __create_auth_widget(self):
|
||||||
|
hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
hbox.set_halign(Gtk.Align.CENTER)
|
||||||
|
hbox.set_valign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
logo = Gtk.Image.new_from_file("logo.png")
|
||||||
|
logo_label = Gtk.Label(label="Ведапроект")
|
||||||
|
self.__button_auth = Gtk.Button(label="Вход")
|
||||||
|
self.__button_auth.connect("clicked", self.__auth_handler)
|
||||||
|
|
||||||
|
label_login = Gtk.Label(label="Логин")
|
||||||
|
self.__login_entry = Gtk.Entry(can_default=True)
|
||||||
|
|
||||||
|
label_password = Gtk.Label(label="Пароль")
|
||||||
|
self.__password_entry = Gtk.Entry()
|
||||||
|
self.__password_entry.set_visibility(False)
|
||||||
|
|
||||||
|
self.__login_entry.connect("activate", lambda widget: self.__password_entry.grab_focus())
|
||||||
|
self.__password_entry.connect("activate", self.__auth_handler)
|
||||||
|
|
||||||
|
hbox.pack_start(logo, expand=False, fill=False, padding=0)
|
||||||
|
hbox.pack_start(logo_label, expand=False, fill=False, padding=10)
|
||||||
|
|
||||||
|
hbox.pack_start(label_login, expand=False, fill=False, padding=10)
|
||||||
|
hbox.pack_start(self.__login_entry, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
|
hbox.pack_start(label_password, expand=False, fill=False, padding=10)
|
||||||
|
hbox.pack_start(self.__password_entry, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
|
hbox.pack_start(self.__button_auth, expand=False, fill=False, padding=10)
|
||||||
|
|
||||||
|
# спинер, хрень которая будет показывать что идет процесс подключения
|
||||||
|
self._spinner = Gtk.Spinner()
|
||||||
|
hbox.pack_start(self._spinner, True, True, 0)
|
||||||
|
|
||||||
|
return hbox
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self._remote_selected_class = False
|
||||||
|
|
||||||
|
self._visible_image_child = "no-connection"
|
||||||
|
|
||||||
|
self._daemon = ConnectionDaemon(CONFIG["server-address"], CONFIG["server-port"], self.__on_object_received)
|
||||||
|
|
||||||
|
self.set_default_size(600, 400)
|
||||||
|
self.set_title("Ведапроект")
|
||||||
|
|
||||||
|
# основная "коробка"
|
||||||
|
self.root = Gtk.Stack()
|
||||||
|
self.root.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
|
||||||
|
self.root.add_named(self.__create_auth_widget(), "auth")
|
||||||
|
self.root.add_named(self.__create_main_widget(), "main")
|
||||||
|
self.add(self.root)
|
||||||
|
|
||||||
|
# ---------------------- Handlers --------------------------
|
||||||
|
|
||||||
|
def _send_command(self, cmd):
|
||||||
|
self._daemon.send_object({'type': 'command', 'data': cmd})
|
||||||
|
|
||||||
|
def __send_action(self, act):
|
||||||
|
self._send_command({'action': act})
|
||||||
|
|
||||||
|
def __get_selected_class(self):
|
||||||
|
label = self.select_class.get_active_text()
|
||||||
|
for i in range(0, len(CLASSES)):
|
||||||
|
if CLASSES[i]["label"] == label:
|
||||||
|
# теперь метка "не выбрано" имеет индекс 0, поэтому все остальные смещены на +1
|
||||||
|
return i
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def on_class_select(self, widget):
|
||||||
|
c = self.__get_selected_class()
|
||||||
|
if self._remote_selected_class:
|
||||||
|
self._remote_selected_class = False
|
||||||
|
else:
|
||||||
|
if c is None:
|
||||||
|
cl = "__all__"
|
||||||
|
else:
|
||||||
|
cl = CLASSES[c]["class"]
|
||||||
|
print(f'select class: id={c}, {cl}')
|
||||||
|
self._send_command({'action': 'set-class', 'class': cl})
|
||||||
|
|
||||||
|
def on_key_press(self, window, event_key: Gdk.EventKey):
|
||||||
|
commands = {
|
||||||
|
"up": [Gdk.KEY_w, Gdk.KEY_W], # Gdk.KEY_Up
|
||||||
|
"left": [Gdk.KEY_a, Gdk.KEY_A], # Gdk.KEY_Left
|
||||||
|
"down": [Gdk.KEY_s, Gdk.KEY_S], # Gdk.KEY_Down
|
||||||
|
"right": [Gdk.KEY_d, Gdk.KEY_D], # Gdk.KEY_Right
|
||||||
|
"start": Gdk.KEY_z,
|
||||||
|
"stop": Gdk.KEY_x,
|
||||||
|
}
|
||||||
|
keyval = event_key.get_keyval()
|
||||||
|
action = None
|
||||||
|
if keyval[0]:
|
||||||
|
keyval = keyval[1]
|
||||||
|
# прошерстим команды
|
||||||
|
for key in commands:
|
||||||
|
if type(commands[key]) == list:
|
||||||
|
if keyval in commands[key]:
|
||||||
|
action = key
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if commands[key] == keyval:
|
||||||
|
action = key
|
||||||
|
break
|
||||||
|
|
||||||
|
if action is not None:
|
||||||
|
self._send_command({'action': action})
|
||||||
|
|
||||||
|
def __update_image(self, image):
|
||||||
|
need_frame = "no-video"
|
||||||
|
if image is not None:
|
||||||
|
need_frame = "video"
|
||||||
|
data = image.tobytes()
|
||||||
|
w, h = image.size
|
||||||
|
data = GLib.Bytes.new(data)
|
||||||
|
pix = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB, False, 8, w, h, w * 3)
|
||||||
|
self.image.set_from_pixbuf(pix)
|
||||||
|
|
||||||
|
if self._visible_image_child is not need_frame:
|
||||||
|
self._have_image = need_frame
|
||||||
|
self._image_stack.set_visible_child_name(need_frame)
|
||||||
|
|
||||||
|
# print(f"{datetime.datetime.now()} MyWindow: image received")
|
||||||
|
|
||||||
|
def __update_selected_class(self, cl):
|
||||||
|
c = self.__get_selected_class()
|
||||||
|
|
||||||
|
if c is None:
|
||||||
|
c = 0
|
||||||
|
else:
|
||||||
|
c += 1
|
||||||
|
|
||||||
|
if cl < -1:
|
||||||
|
cl = 0
|
||||||
|
else:
|
||||||
|
cl += 1
|
||||||
|
|
||||||
|
if c != cl:
|
||||||
|
if cl != c:
|
||||||
|
self._remote_selected_class = True
|
||||||
|
# то же самое, из-за метки все классы уехали на +1
|
||||||
|
self.select_class.set_active(cl)
|
||||||
|
|
||||||
|
def __on_object_received(self, res):
|
||||||
|
if res["type"] == "video":
|
||||||
|
if res["data"] is None:
|
||||||
|
GLib.idle_add(lambda: self.__update_image(None))
|
||||||
|
else:
|
||||||
|
data = io.BytesIO(res["data"])
|
||||||
|
img = Image.open(data)
|
||||||
|
GLib.idle_add(lambda: self.__update_image(img))
|
||||||
|
|
||||||
|
if 'selected-class' in res:
|
||||||
|
GLib.idle_add(lambda: self.__update_selected_class(res['selected-class']))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
""" Run the main application"""
|
||||||
|
win = MainWindow()
|
||||||
|
win.connect("destroy", Gtk.main_quit)
|
||||||
|
win.show_all()
|
||||||
|
Gtk.main()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
337
host_test.py
Normal file
337
host_test.py
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
# import packages
|
||||||
|
from openal import *
|
||||||
|
from imutils.video import VideoStream
|
||||||
|
import itertools
|
||||||
|
import imutils
|
||||||
|
import time
|
||||||
|
import cv2
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
import spidev
|
||||||
|
|
||||||
|
# Client
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from threading import Thread
|
||||||
|
from streamer_utils import SocketBlocksWrapper, read_json_config
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
#spi = spidev.SpiDev()
|
||||||
|
#spi.open(1, 0)
|
||||||
|
|
||||||
|
#spi.bits_per_word = 8
|
||||||
|
#spi.max_speed_hz = 500000
|
||||||
|
|
||||||
|
X = bool(0) # иниц-я глоб. переменной
|
||||||
|
X_New = bool(0)
|
||||||
|
X_pred = bool(0) # иниц-я глоб. переменной
|
||||||
|
startTime = float(time.time() - 10) # иниц-я глоб. переменной
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG = read_json_config('board-config.json')
|
||||||
|
CLASSES = read_json_config('classes.json')
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionDaemon(Thread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(daemon=True)
|
||||||
|
self._sock = None
|
||||||
|
self._message_handler = None
|
||||||
|
|
||||||
|
def set_message_handler(self, handler: callable):
|
||||||
|
self._message_handler = handler
|
||||||
|
|
||||||
|
def __do_call_message_handler(self, res):
|
||||||
|
if self._message_handler is not None:
|
||||||
|
try:
|
||||||
|
self._message_handler(res)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def __do_session(self):
|
||||||
|
try:
|
||||||
|
with SocketBlocksWrapper.connect(CONFIG['server-address'], CONFIG['server-port']) as sock:
|
||||||
|
print("ConnectionDaemon: open connection")
|
||||||
|
self._sock = sock
|
||||||
|
self._sock.write_object({'type': 'auth', 'client-type': 'board', 'name': CONFIG['name']})
|
||||||
|
res = self._sock.read_object()
|
||||||
|
if res is None:
|
||||||
|
return
|
||||||
|
print(res)
|
||||||
|
if 'status' in res:
|
||||||
|
if res['status'] == 'success':
|
||||||
|
while True:
|
||||||
|
res = self._sock.read_object()
|
||||||
|
if res is None:
|
||||||
|
break
|
||||||
|
self.__do_call_message_handler(res)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
print("ConnectionDaemon: start session...")
|
||||||
|
self.__do_session()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
def send_frame(self, fr):
|
||||||
|
if self._sock is not None:
|
||||||
|
try:
|
||||||
|
to_send = {
|
||||||
|
'type': 'video',
|
||||||
|
'data': None,
|
||||||
|
"selected-class": selected_class_id
|
||||||
|
}
|
||||||
|
if fr is not None:
|
||||||
|
fr = imutils.resize(fr, width=640, height=360)
|
||||||
|
buffer = cv2.imencode('.jpg', fr, [int(cv2.IMWRITE_JPEG_QUALITY), 60])[1]
|
||||||
|
data_encode = np.array(buffer)
|
||||||
|
to_send["data"] = data_encode.tobytes()
|
||||||
|
self._sock.write_object(to_send)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def send_image(self, img: Image):
|
||||||
|
if self._sock is not None:
|
||||||
|
try:
|
||||||
|
out = io.BytesIO()
|
||||||
|
img.save(out, format="JPEG")
|
||||||
|
self._sock.write_object({
|
||||||
|
'type': 'video',
|
||||||
|
'data': out.getvalue(),
|
||||||
|
"selected-class": selected_class_id
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
# камера не движется
|
||||||
|
Left = bool(0)
|
||||||
|
Right = bool(0)
|
||||||
|
Up = bool(0)
|
||||||
|
Down = bool(0)
|
||||||
|
|
||||||
|
# -2 = нейронка отключена, -1 = включены все классы, остальное - id класса из списка CLASSES
|
||||||
|
selected_class_id = -2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# функция, которая вызывается при получении команды
|
||||||
|
def message_handler(msg):
|
||||||
|
global selected_class_id
|
||||||
|
global Left
|
||||||
|
global Right
|
||||||
|
global Up
|
||||||
|
global Down
|
||||||
|
print(msg)
|
||||||
|
if msg["type"] == "command":
|
||||||
|
# отлично, наше сообщение
|
||||||
|
act = msg["data"]["action"]
|
||||||
|
if act == "left":
|
||||||
|
Left = 1
|
||||||
|
if act == "right":
|
||||||
|
Right = 1
|
||||||
|
if act == "up":
|
||||||
|
Up = 1
|
||||||
|
if act == "down":
|
||||||
|
Down = 1
|
||||||
|
if act == "start":
|
||||||
|
selected_class_id = -1
|
||||||
|
elif act == "stop":
|
||||||
|
selected_class_id = -2
|
||||||
|
elif act == "set-class":
|
||||||
|
if selected_class_id < -1:
|
||||||
|
print("message_handler: WARMING: set class-id while board is stop")
|
||||||
|
else:
|
||||||
|
cl = msg["data"]["class"]
|
||||||
|
selected_class_id = -1 # если не найдем, будут выбраны все классы
|
||||||
|
for i in range(0, len(CLASSES)):
|
||||||
|
if CLASSES[i]["class"] == cl:
|
||||||
|
selected_class_id = i
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
print("============ Initialize connection daemon ============")
|
||||||
|
connection_daemon = ConnectionDaemon()
|
||||||
|
connection_daemon.set_message_handler(message_handler)
|
||||||
|
connection_daemon.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def notify():
|
||||||
|
global startTime
|
||||||
|
endTime = time.time()
|
||||||
|
if endTime - startTime > 1.5: # прошло 1.5 секунды
|
||||||
|
# if 1>0: #режим прерывания сообщений
|
||||||
|
global X
|
||||||
|
global X_New
|
||||||
|
global X_pred
|
||||||
|
if X == 0 and X_pred == 1: # поменялось на 0
|
||||||
|
source = oalOpen("Pot.wav") # Потерян
|
||||||
|
source.play() # воспр. 1 раз
|
||||||
|
startTime = time.time() # отсчёт времени
|
||||||
|
if X==1 and X_pred==1 and X_New==0 and (endTime - startTime > 6):
|
||||||
|
source = oalOpen("Nab.wav") #Потерян
|
||||||
|
source.play() #воспр. 1 раз
|
||||||
|
startTime = time.time() #отсчёт времени
|
||||||
|
if X==1 and X_pred==1 and X_New==1:
|
||||||
|
source = oalOpen("New.wav") #new object
|
||||||
|
source.play() #воспр. 1 раз
|
||||||
|
startTime = time.time() #отсчёт времени
|
||||||
|
elif X == 1 and X_pred == 0: # поменялось на 1
|
||||||
|
source = oalOpen("Zah.wav") # Захвачен
|
||||||
|
source.play() # воспр. 1 раз
|
||||||
|
startTime = time.time() # отсчёт времени
|
||||||
|
X_pred = X # обновляем предыдущее значение
|
||||||
|
|
||||||
|
|
||||||
|
print("[INFO] loading model...")
|
||||||
|
net = cv2.dnn_DetectionModel('AI.cfg', 'AI.weights')
|
||||||
|
|
||||||
|
#net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)
|
||||||
|
#net.setPreferableTarget(cv2.dnn.DNN_TARGET_MYRIAD)
|
||||||
|
|
||||||
|
picSize_X = 640
|
||||||
|
picSize_Y = 480
|
||||||
|
net.setInputSize(128, 128)
|
||||||
|
net.setInputScale(1.0 / 255)
|
||||||
|
net.setInputSwapRB(True)
|
||||||
|
|
||||||
|
print("[INFO] starting video stream...")
|
||||||
|
vs = VideoStream(src=0).start()
|
||||||
|
# warm up the camera for a couple of seconds
|
||||||
|
time.sleep(2.0)
|
||||||
|
|
||||||
|
MAX_sX = 0
|
||||||
|
MAX_sY = 0
|
||||||
|
MAX_eX = 0
|
||||||
|
MAX_eY = 0
|
||||||
|
|
||||||
|
centr_X = 0
|
||||||
|
centr_Y = 0
|
||||||
|
|
||||||
|
pred_centr_X = 0
|
||||||
|
pred_centr_Y = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if selected_class_id >= 0:
|
||||||
|
t0 = time.time()
|
||||||
|
frame = vs.read()
|
||||||
|
#frame = imutils.resize(frame, width=1280, height=720)
|
||||||
|
#(h, w) = frame.shape[:2]
|
||||||
|
|
||||||
|
S_MAX = 0
|
||||||
|
X = 0
|
||||||
|
X_New = 0
|
||||||
|
# находим объекты и возвращаем их параметры
|
||||||
|
classes, confidences, boxes = net.detect(frame, confThreshold=0.18, nmsThreshold=0.5)
|
||||||
|
# создаём рамки и надписи
|
||||||
|
for classId, confidence, box in zip(list(itertools.chain(classes)), list(itertools.chain(confidences)), boxes):
|
||||||
|
# if classId == 39: # вот так делать не стоит, работать такое точно не будет
|
||||||
|
if selected_class_id == -1 or classId == selected_class_id:
|
||||||
|
X = 1
|
||||||
|
|
||||||
|
label = f"{CLASSES[classId]['class']}"
|
||||||
|
label = '%s: %.2f' % (label, confidence)
|
||||||
|
color = CLASSES[classId]["color"]
|
||||||
|
|
||||||
|
labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
||||||
|
left, top, width, heigth = box
|
||||||
|
S = width * heigth
|
||||||
|
print ('S =', S, 'pics')
|
||||||
|
if S>S_MAX:
|
||||||
|
S_MAX = S
|
||||||
|
MAX_sX = left
|
||||||
|
MAX_sY = top
|
||||||
|
MAX_eX = left + width
|
||||||
|
MAX_eY = top + heigth
|
||||||
|
MAX_label = label
|
||||||
|
print("Object detected: ", label)
|
||||||
|
|
||||||
|
if (X == 1):
|
||||||
|
# Draw a rectangle across the boundary of the object
|
||||||
|
cv2.rectangle(frame, (MAX_sX, MAX_sY), (MAX_eX, MAX_eY), color, 2)
|
||||||
|
y = MAX_sY - 15 if MAX_sY - 15 > 15 else MAX_sY + 15
|
||||||
|
# Put a text outside the rectangular detection
|
||||||
|
# Choose the font of your choice: FONT_HERSHEY_SIMPLEX, FONT_HERSHEY_PL>
|
||||||
|
cv2.putText(frame, MAX_label, (MAX_sX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
|
||||||
|
|
||||||
|
centr_X = (MAX_sX+MAX_eX)/2
|
||||||
|
centr_Y = (MAX_sY+MAX_eY)/2
|
||||||
|
|
||||||
|
if (abs(centr_X-pred_centr_X) > picSize_X/4 or abs(centr_Y-pred_centr_Y) > picSize_Y/4):
|
||||||
|
X_New = 1
|
||||||
|
|
||||||
|
# if (X == 1 and Left == 0 and Right == 0 and Up == 0 and Down == 0):
|
||||||
|
# if (centr_X > (picSize_X/2+picSize_X/10) and centr_Y < (picSize_Y/2+picSize_Y/10) and centr_Y > (picSize_Y/2-picSize_Y/10)):
|
||||||
|
# txData = [0b00000111] #Вправо
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y < (picSize_Y/2+picSize_Y/10) and centr_Y > (picSize_Y/2-picSize_Y/10)):
|
||||||
|
# txData = [0b00000110] #Влево
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (centr_Y > (picSize_Y/2+picSize_Y/10) and centr_X < (picSize_X/2+picSize_X/10) and centr_X > (picSize_X/2-picSize_X/10)):
|
||||||
|
# txData = [0b00001101] #Вверх
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (centr_Y < (picSize_Y/2-picSize_Y/10) and centr_X < (picSize_X/2+picSize_X/10) and centr_X > (picSize_X/2-picSize_X/10)):
|
||||||
|
# txData = [0b00001001] #Вниз
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y < (picSize_Y/2-picSize_Y/10)):
|
||||||
|
# txData = [0b00001010] #Влево/вниз
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (centr_X > (picSize_X/2+picSize_X/10) and centr_Y < (picSize_Y/2-picSize_Y/10)):
|
||||||
|
# txData = [0b00001011] #Вправо/вниз
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (centr_X < (picSize_X/2-picSize_X/10) and centr_Y > (picSize_Y/2+picSize_Y/10)):
|
||||||
|
# txData = [0b00001110] #Влево/вверх
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (centr_X > (picSize_X/2+picSize_X/10) and centr_Y > (picSize_Y/2+picSize_Y/10)):
|
||||||
|
# txData = [0b00001111] #Вправо/вверх
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# else:
|
||||||
|
# txData = [0b00000101] #Центр
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (Left == 0 and Right == 1 and Up == 0 and Down == 0):
|
||||||
|
# txData = [0b00000111] #Вправо
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (Left == 1 and Right == 0 and Up == 0 and Down == 0):
|
||||||
|
# txData = [0b00000110] #Влево
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (Left == 0 and Right == 0 and Up == 1 and Down == 0):
|
||||||
|
# txData = [0b00001001] #Вверх
|
||||||
|
# spi.xfer(txData)
|
||||||
|
# elif (Left == 0 and Right == 0 and Up == 0 and Down == 1):
|
||||||
|
# txData = [0b00001101] #Вниз
|
||||||
|
# spi.xfer(txData)
|
||||||
|
|
||||||
|
pred_centr_X = centr_X
|
||||||
|
pred_centr_Y = centr_Y
|
||||||
|
|
||||||
|
# обнуление
|
||||||
|
Left = 0
|
||||||
|
Right = 0
|
||||||
|
Up = 0
|
||||||
|
Down = 0
|
||||||
|
My_FPS = 1 / (time.time() - t0)
|
||||||
|
FPS_label = 'FPS=%2.f' % My_FPS
|
||||||
|
labelSize, baseLine = cv2.getTextSize(FPS_label, cv2.FONT_HERSHEY_SIMPLEX, 1.5, 1)
|
||||||
|
cv2.rectangle(frame, (4, 4), (4 + labelSize[0], 4 + labelSize[1] + baseLine), (255, 0, 155), cv2.FILLED)
|
||||||
|
cv2.putText(frame, FPS_label, (4, 4 + labelSize[1]), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 0))
|
||||||
|
notify()
|
||||||
|
|
||||||
|
# отправка фрейма на сервер
|
||||||
|
connection_daemon.send_frame(frame)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# отправка раз в секунду пустого фрейма
|
||||||
|
connection_daemon.send_frame(None)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
spi.close()
|
||||||
|
# Destroy windows and cleanup
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
# Stop the video stream
|
||||||
|
vs.stop()
|
222
server.py
Normal file
222
server.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#!/bin/python
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import socket
|
||||||
|
from threading import Thread, Lock
|
||||||
|
from streamer_utils import SocketBlocksWrapper
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
db_connection = sqlite3.connect("boards.sqlite3", check_same_thread=False)
|
||||||
|
|
||||||
|
LISTEN = ('', 40100)
|
||||||
|
MAX_ATTEMPTS = 3
|
||||||
|
BLOCK_TIME_MINUTES = 3
|
||||||
|
|
||||||
|
__log_lock = Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _log(message, owner="__base__"):
|
||||||
|
with __log_lock:
|
||||||
|
print(f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] {owner}: {message}")
|
||||||
|
|
||||||
|
|
||||||
|
class ServerWorker(Thread):
|
||||||
|
def __init__(self, factory, conn, addr):
|
||||||
|
super().__init__(daemon=True)
|
||||||
|
self.conn = conn
|
||||||
|
self._log_name = f"Worker-{addr}"
|
||||||
|
self.factory = factory
|
||||||
|
self._client_type = None
|
||||||
|
self._client_name = None
|
||||||
|
self._cursor = db_connection.cursor()
|
||||||
|
|
||||||
|
def _auth_board(self, obj):
|
||||||
|
if 'name' not in obj:
|
||||||
|
return True, 'missing field "name"!'
|
||||||
|
|
||||||
|
self._cursor.execute("SELECT board_name, password FROM users WHERE board_name = ?;", (obj['name'], ))
|
||||||
|
res = self._cursor.fetchone()
|
||||||
|
if res is None:
|
||||||
|
return True, 'this board is not registered!'
|
||||||
|
|
||||||
|
self._client_name = obj['name']
|
||||||
|
self._client_type = "board"
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def _auth_client(self, obj):
|
||||||
|
if 'target' not in obj:
|
||||||
|
return True, 'missing field "target"!'
|
||||||
|
if 'password' not in obj:
|
||||||
|
return True, 'missing field "password"!'
|
||||||
|
|
||||||
|
self._cursor.execute("SELECT board_name, password, blocked_until, wrong_attempts FROM users "
|
||||||
|
"WHERE board_name = ?;",
|
||||||
|
(obj['target'], ))
|
||||||
|
res = self._cursor.fetchone()
|
||||||
|
|
||||||
|
# сначала проверка на то, что плата существует
|
||||||
|
if res is None:
|
||||||
|
return True, 'Аккаунт не найден!'
|
||||||
|
|
||||||
|
# теперь на то, что аккаунт не заблокирован
|
||||||
|
now = datetime.now()
|
||||||
|
delta = res[2] - int(round(now.timestamp()))
|
||||||
|
print(delta)
|
||||||
|
if delta >= 0:
|
||||||
|
return True, f'Доступ отклонен: аккаунт заблокирован! Разблокировка через {delta} секунд'
|
||||||
|
|
||||||
|
# проверка пароля
|
||||||
|
if res[1] != obj['password']:
|
||||||
|
if res[3] >= MAX_ATTEMPTS - 1:
|
||||||
|
dt = now + timedelta(minutes=BLOCK_TIME_MINUTES)
|
||||||
|
t = int(round(dt.timestamp()))
|
||||||
|
self._cursor.execute("UPDATE users SET wrong_attempts = 0, blocked_until = ? WHERE board_name = ?;",
|
||||||
|
(t, res[0]))
|
||||||
|
db_connection.commit()
|
||||||
|
return True, f'Доступ отклонен: аккаунт заблокирован! Разблокировка через {BLOCK_TIME_MINUTES} минут(ы)'
|
||||||
|
else:
|
||||||
|
self._cursor.execute("UPDATE users SET wrong_attempts = wrong_attempts + 1 WHERE board_name = ?;",
|
||||||
|
(res[0], ))
|
||||||
|
db_connection.commit()
|
||||||
|
return True, 'Доступ отклонен: неверный пароль!'
|
||||||
|
|
||||||
|
# обновление неудачных попыток в случае если пароль верный
|
||||||
|
if res[3] != 0:
|
||||||
|
self._cursor.execute("UPDATE users SET wrong_attempts = 0 WHERE board_name = ?;",
|
||||||
|
(res[0], ))
|
||||||
|
db_connection.commit()
|
||||||
|
|
||||||
|
self._client_name = obj['target']
|
||||||
|
self._client_type = "client"
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def _auth(self):
|
||||||
|
_log("Wait for auth...", self._log_name)
|
||||||
|
err = True
|
||||||
|
|
||||||
|
obj = self.conn.read_object()
|
||||||
|
if type(obj) != dict:
|
||||||
|
description = "invalid object type!"
|
||||||
|
elif 'type' not in obj:
|
||||||
|
description = 'missing field "type"!'
|
||||||
|
elif obj['type'] != 'auth':
|
||||||
|
description = 'field "type" must have value "auth"!'
|
||||||
|
elif 'client-type' not in obj:
|
||||||
|
description = 'missing field "client-type"!'
|
||||||
|
elif obj['client-type'] != 'board' and obj['client-type'] != 'client':
|
||||||
|
description = f'unsupported client type: "{obj["client-type"]}"'
|
||||||
|
else:
|
||||||
|
if obj['client-type'] == 'board':
|
||||||
|
err, description = self._auth_board(obj)
|
||||||
|
else:
|
||||||
|
err, description = self._auth_client(obj)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
'type': 'auth-response'
|
||||||
|
}
|
||||||
|
|
||||||
|
if err:
|
||||||
|
response["status"] = "failed"
|
||||||
|
response["description"] = description
|
||||||
|
else:
|
||||||
|
response["status"] = "success"
|
||||||
|
|
||||||
|
_log(f"auth {response['status']}! request={obj}, response={response}", owner=self._log_name)
|
||||||
|
|
||||||
|
self.conn.write_object(response)
|
||||||
|
return not err
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
with self.conn:
|
||||||
|
if self._auth():
|
||||||
|
while True:
|
||||||
|
recv = self.conn.read_object()
|
||||||
|
if recv is None:
|
||||||
|
break
|
||||||
|
# _log(f"received {recv['type']} frame", self._log_name)
|
||||||
|
self.factory.route_packet(self, recv)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.factory.remove_connection(self)
|
||||||
|
|
||||||
|
_log(f"Close connection!", self._log_name)
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return self._client_name
|
||||||
|
|
||||||
|
def get_log_name(self):
|
||||||
|
return self._log_name
|
||||||
|
|
||||||
|
def send_packet(self, packet):
|
||||||
|
self.conn.write_object(packet)
|
||||||
|
# _log("send routed packet", self._log_name)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerWorkerFactory:
|
||||||
|
def __init__(self):
|
||||||
|
self._lock = Lock()
|
||||||
|
self._connections = []
|
||||||
|
|
||||||
|
def add_connection(self, conn, addr):
|
||||||
|
with self._lock:
|
||||||
|
worker = ServerWorker(self, conn, addr)
|
||||||
|
worker.start()
|
||||||
|
self._connections.append(worker)
|
||||||
|
|
||||||
|
def remove_connection(self, conn: ServerWorker):
|
||||||
|
with self._lock:
|
||||||
|
if conn in self._connections:
|
||||||
|
_log(f"remove connection {conn.get_log_name()}", "ServerWorkerFactory")
|
||||||
|
self._connections.remove(conn)
|
||||||
|
|
||||||
|
def route_packet(self, owner: ServerWorker, data):
|
||||||
|
connections = None
|
||||||
|
with self._lock:
|
||||||
|
if owner in self._connections:
|
||||||
|
connections = self._connections.copy()
|
||||||
|
|
||||||
|
if connections is not None:
|
||||||
|
name = owner.get_name()
|
||||||
|
for c in connections:
|
||||||
|
if c == owner:
|
||||||
|
continue
|
||||||
|
if c.get_name() == name:
|
||||||
|
c.send_packet(data)
|
||||||
|
|
||||||
|
|
||||||
|
def server_listener():
|
||||||
|
_log("============ SERVER ============")
|
||||||
|
_log("Creating table...")
|
||||||
|
cur = db_connection.cursor()
|
||||||
|
cur.execute("""CREATE TABLE IF NOT EXISTS users (
|
||||||
|
board_name TEXT NOT NULL PRIMARY KEY,
|
||||||
|
password TEXT DEFAULT '' NOT NULL,
|
||||||
|
blocked_until INT DEFAULT 0 NOT NULL,
|
||||||
|
wrong_attempts INT DEFAULT 0 NOT NULL
|
||||||
|
);""")
|
||||||
|
db_connection.commit()
|
||||||
|
|
||||||
|
with socket.socket() as sock:
|
||||||
|
sock.bind(LISTEN)
|
||||||
|
_log(f"socket bind success", "ServerTask")
|
||||||
|
sock.listen(8)
|
||||||
|
|
||||||
|
_log(f"socket listen...", "ServerTask")
|
||||||
|
worker = ServerWorkerFactory()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
connection, addr = sock.accept()
|
||||||
|
_log(f"connected from {addr}", "ServerTask")
|
||||||
|
worker.add_connection(SocketBlocksWrapper(connection), addr)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
server_listener()
|
||||||
|
|
||||||
|
|
||||||
|
|
70
streamer_utils.py
Normal file
70
streamer_utils.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import pickle
|
||||||
|
import socket
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def read_json_config(filename: str):
|
||||||
|
with open(filename, 'r') as file:
|
||||||
|
return json.loads(file.read())
|
||||||
|
|
||||||
|
|
||||||
|
TCP_CHUNK_SIZE = 16384
|
||||||
|
|
||||||
|
|
||||||
|
class SocketBlocksWrapper:
|
||||||
|
def __init__(self, s, address: str = None, port: int = None):
|
||||||
|
self.sock = s
|
||||||
|
|
||||||
|
def get_socket(self):
|
||||||
|
return self.sock
|
||||||
|
|
||||||
|
def _read_block(self):
|
||||||
|
arr = self.sock.recv(8)
|
||||||
|
if len(arr) < 8:
|
||||||
|
return None
|
||||||
|
data_size = int.from_bytes(arr, 'little')
|
||||||
|
|
||||||
|
# далее нужно читать данные чанками, потому что в противном случае будет говно
|
||||||
|
received_size = 0
|
||||||
|
data = bytearray()
|
||||||
|
while received_size < data_size:
|
||||||
|
to_recv = data_size - received_size
|
||||||
|
if to_recv > TCP_CHUNK_SIZE:
|
||||||
|
to_recv = TCP_CHUNK_SIZE
|
||||||
|
r = self.sock.recv(to_recv)
|
||||||
|
if len(r) == 0:
|
||||||
|
continue
|
||||||
|
received_size += len(r)
|
||||||
|
data += r
|
||||||
|
return data
|
||||||
|
|
||||||
|
def read_object(self):
|
||||||
|
raw = self._read_block()
|
||||||
|
if raw is None:
|
||||||
|
return None
|
||||||
|
return pickle.loads(raw)
|
||||||
|
|
||||||
|
def _write_block(self, data: bytes):
|
||||||
|
data_size = len(data).to_bytes(8, 'little')
|
||||||
|
to_send = bytearray(data_size)
|
||||||
|
to_send += data
|
||||||
|
# print(f"libstreamer: write {len(to_send)} bytes")
|
||||||
|
self.sock.send(to_send)
|
||||||
|
|
||||||
|
def write_object(self, obj):
|
||||||
|
to_send = pickle.dumps(obj)
|
||||||
|
self._write_block(to_send)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.sock.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def connect(host: str, port: int):
|
||||||
|
sock = socket.socket()
|
||||||
|
sock.connect((host, port))
|
||||||
|
return SocketBlocksWrapper(sock)
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user