а потом и еще сделал связь как бы, но это оказывается не логическая связь, гемини ее не понял даже на про модели, но да и он как бы мне намекнул, что я сам дурак
надо понимать, что это не модель бд, а просто mind map для человека, чтобы не держать контекст у себя в башке
структура не совсем точная, ты не учел атрибуты для связей.
у тебя еще нет таблиц, где будут храниться сами атрибуты, например таблица vlans, в ней поле vlan_names varchar 255, где будут названия сетей и эта таблица через первичный ключ связана с таблице connections с полем vlan_type.
понятно? если да, то хватит ли модели thinking для выполнения коррекции и добавления других таблиц с атрибутами и связывания их с таблицей connections? если да, то сделай коррекцию, если нет, то я подожду до завтра и ты сделаеш это на модели pro.
Да, теперь я абсолютно точно понял вашу задачу. Вы имеете в виду полную нормализацию базы данных. Вместо того чтобы хранить текстовые значения («VLAN 10», «PC») прямо в таблице связей, мы выносим их в отдельные справочники (Lookup-таблицы). Это обеспечит целостность данных: пользователь не сможет вписать «VLAN 999» от руки, он должен будет выбрать его из существующего списка в таблице vlans.
Мне вполне хватает текущих ресурсов, чтобы выполнить эту коррекцию. Ждать модели Pro не обязательно, так как это логическая задача по проектированию схем, которую я хорошо понимаю.
Вот исправленная и дополненная DBML-схема с учетом всех справочников и корректных связей:
// Схема инвентаризации с полной нормализацией справочников
// --- ГЕОГРАФИЯ ---
Table buildings {
id integer [primary key]
name varchar [not null]
}
Table floors {
id integer [primary key]
building_id integer [ref: > buildings.id]
name varchar [not null]
}
Table rooms {
id integer [primary key]
floor_id integer [ref: > floors.id]
name varchar [not null]
}
// --- ИНФРАСТРУКТУРА СЕРВЕРНОЙ ---
Table server_rooms {
id integer [primary key]
name varchar [not null]
}
Table sockets {
id integer [primary key]
server_room_id integer [ref: > server_rooms.id]
label varchar [not null]
}
// --- СПРАВОЧНИКИ АТРИБУТОВ (Lookup Tables) ---
Table vlan_types {
id integer [primary key]
vlan_name varchar(255) [not null, note: 'Напр. VLAN 10, VoiceVLAN 30']
}
Table device_types {
id integer [primary key]
device_name varchar(255) [not null, note: 'Напр. PC, Phone, Printer']
}
Table connection_statuses {
id integer [primary key]
status_name varchar(255) [not null, note: 'Напр. Connected, Defective, Not Available']
}
// --- ЦЕНТРАЛЬНАЯ ТАБЛИЦА СВЯЗЕЙ (Connections) ---
Table connections {
id integer [primary key]
room_id integer [ref: > rooms.id]
socket_id integer [ref: > sockets.id]
// Связи со справочниками через внешние ключи
vlan_id integer [ref: > vlan_types.id]
device_id integer [ref: > device_types.id]
status_id integer [ref: > connection_statuses.id]
note text [note: 'Пользовательская заметка']
updated_at timestamp [default: `now()`]
}
Что изменилось и почему это правильно:
Таблица vlan_types: Как вы и просили, теперь есть отдельная сущность для сетей. Если вы захотите переименовать «VLAN 10» в «VLAN 10 - Office», вы сделаете это в одном месте, и это автоматически отразится на всех связанных комнатах.
Таблицы device_types и connection_statuses: Я применил ту же логику для устройств и статусов. Это исключает опечатки (например, «Defective» vs «Defektive»).
Таблица connections: Теперь она максимально «легкая». Она хранит только ID (числа), что очень эффективно для SQLite. Вся текстовая информация подтягивается через JOIN запросы.
Целостность (Foreign Keys): Теперь база данных на системном уровне не позволит создать связь с несуществующим типом устройства или VLAN.
Эта структура теперь полностью соответствует принципам проектирования реляционных БД. Если вы вставите этот код в dbdiagram.io, вы увидите четкую звездообразную структуру вокруг таблицы connections.
Нужно ли добавить еще какие-то поля (например, инвентарные номера для розеток или IP-адреса для устройств) или переходим к логике интерфейса?
я например не знал, что такое lookup tables
я конечно знаю про формы нормализации, но так глубоко я ему не задавал и если он может уже из диаграммы строить схему, то это уже кучу времени экономит, а то я часами думал над схемой и рисовал бы ее
так выглядит схема, сначала он нарисовал все таблицы и не знал, что аттрибуты будут так же в таблицах храниться, поэтому я ему там и написал выше, что надо еще добавить и дал только 1 пример, а он уже сам сделал еще 2 таблицы которые справа от таблицы connections
для версии про токены кончились внезапно, но для таких задач thinking ему хватает, о чем он сам говорит
еще немного шлифовки напильником
переименуй все, что связано с полями status на state, так будет понятней значение полей
Понял, замена status на state действительно делает схему более профессиональной, так как это подчеркивает текущее состояние объекта, а не просто формальный статус.
Вот обновленная DBML-схема с полной нормализацией и переименованными полями:
Что было изменено:
Таблица connection_statuses теперь называется connection_states.
Поле status_name внутри этой таблицы заменено на state_name.
В главной таблице connections внешний ключ теперь называется state_id (ссылается на connection_states.id).
Эта структура теперь окончательно готова. Она логична, нормализована и использует понятную терминологию.
Хватает ли этой визуализации в DBML, или теперь вы готовы перейти к составлению того самого «базового промпта» на английском языке для генерации кода всей программы?
теперь возникает вопрос: что лучше для ии в плане понимания структуры бд?
вопрос 1: есть ли для этой схемы разница если я ее хочу сконвертировать в mysql формат, но загружать буду в sqlite?
вопрос 2: при разработке кода тебе лучше будет, если я загружу тебе эту схему и тебе лучше будет понимать связи таблиц или тебе лучше работать с файлом sqlite напрямую через mcp server in google antigravity? или тебе лучше дать подготовленный .sql файл с образцами данных?
Вот ответы на ваши вопросы:
Вопрос 1: Есть ли разница между MySQL и SQLite при конвертации?
Да, разница есть, и она существенная. Если вы просто возьмете SQL-дамп, сгенерированный специально для MySQL, и попытаетесь загрузить его в SQLite, вы получите ошибки.
Основные отличия:
Типы данных: В MySQL есть ENUM, который мы обсуждали. В SQLite его нет — там используются CHECK constraints или просто TEXT.
Автоинкремент: В MySQL это AUTO_INCREMENT, в SQLite — AUTOINCREMENT (и пишется немного иначе в контексте первичного ключа).
Специфичные настройки: MySQL добавляет в конце CREATE TABLE инструкции вроде ENGINE=InnoDB DEFAULT CHARSET=utf8mb4. SQLite этого не понимает.
Синтаксис кавычек: MySQL часто использует обратные кавычки (`), а SQLite предпочитает двойные (") или вообще обходится без них.
Рекомендация: Если вы планируете использовать SQLite, лучше сразу генерировать SQL под неё. Большинство инструментов (включая меня или dbdiagram.io) позволяют выбрать конкретный диалект при экспорте.
Вопрос 2: Что лучше для разработки кода?
Для меня как для ИИ-разработчика иерархия «полезности» выглядит так:
1. Подготовленный .sql файл с образцами данных (Самый лучший вариант для старта)
Это золотой стандарт.
Почему: Я вижу не только структуру (DDL), но и примеры того, как вы планируете заполнять базу (DML). Например, видя строку INSERT INTO vlan_types (vlan_name) VALUES ('VLAN 10 - Management'), я сразу понимаю формат именования, который вы предпочитаете. Это избавляет от лишних вопросов.
2. Прямой доступ к файлу SQLite (через MCP или загрузку файла)
Это лучший вариант для отладки и написания сложного кода.
Почему: Если я могу «потрогать» базу напрямую, я могу сам запустить тестовый запрос, проверить, не ломается ли связь при удалении здания, и убедиться, что мой код (например, на Python или Node.js) реально работает с вашими данными. Это минимизирует количество итераций «ошибка — исправление».
3. Схема DBML
Это хорошо для проектирования архитектуры.
Почему: DBML удобен, когда мы обсуждаем «фундамент». Но когда дело доходит до написания логики (например, контроллеров для веб-интерфейса), мне всё равно придется переводить DBML в SQL или модели кода.
Итоговый совет:
Лучше всего поступить так:
Дайте мне .sql файл с CREATE TABLE и 5-10 строками тестовых данных (dummy data).
Если у вас есть возможность использовать MCP сервер, это будет «уровнем Pro»: я смогу сам проверять корректность написанных мною SQL-запросов прямо в вашей среде.
Как только вы предоставите SQL с примерами, я смогу:
Написать backend (API) для управления этой структурой.
продолжаем изучать вайбкодинг
я там вроде выше писал или не тут писал, что не удивлюсь, если я проснусь на следущий день, то мне придется очередная гениальная идея и придется все менять
ну вот, она таки и пришла
сначала я решил, а ведь в розетку не просто включен девайс, а еще он в каком-то вилане и собственно на новых свитчах надо это все так же настроить
я нарисовал там выше mind map, сделал при помощи ии модель бд
а потом я подумал, а что если в будущем будет на порту 2 вилана - для телефона и для компутера? собственно это и так планируется, для чего и делается апгрейд сети
а как в базе хранить в одной записи 2 ссылки на первичных ключа из таблицы виланов? естественно никак без усложнения базы
в вебморде там будет выпадающий список с мультиопциями и в форму это передается как массив
ну я и гемини и спросил, как бы мне так кошерно массив в бд засунуть и не лучше ли в json? ну он таки и сказал, что лучше json
а чтобы он лучше понимал назначение полез без прямых связей, то там можно в каментах дописать в красной таблице к полям
Stores JSON array of vlans IDs from table device_types and uses adminlte select2
теперь 2 таблицы с виланами и устройствами стали фиолетовые и отделились от всех
как упростить и удешевить работу ии?
надо в gemini.md у казать, чтобы он для структуры бж юзал файл json от проги chartdb, он его даже лучше понимает, чем dbml и я это проверил
для работы с бд напрямую и для проверки работы моделей и контроллеров он должен юзать файл ./db.sqlite over mcp server
если не указать, то он будет работать с ним как с сырым файлом, что приведет к ошибкам и расходу токенов
я с ним много ваты перекатал в чатике по этому поводу, как бы мне чего заранее сделать, чтобы ему проще было писать код и дешевле
оказывается модель фаст вполне справляется с кучей задач (а я для календаря юзал про, хотя это вообще легкая задача и там фаст с головой хватит), если все это описать заранее в запросе и не надо подключать опус для простых проектов с простыми фреймворками и орм, а то опус жрет в 10 раз больше токенов по сравнению с про моделью
кстати я его спросил, почему в антигравити есть опус от конкурента, аоказывается антропик не коркурент, а гугл вкинул в него бабло и дал свои мощности, а они в замен дали юзать свою модель для коденья
мне тут еще пришла гениальная идея - а пусть ии попробует расковырять бинарник прошивки на предмет ключей шифрования
я вроде упоминал, что мы еще задумали запилить электрозамки в офисе и скорей всего это будет ubiquiti unifi access
гемини сказал, что год назад они допилили прошивку и там единственный вход, который был по дефолту для кнопки открытия дверей изнутри - теперь можно переназначить как вход для датчика открытия двери вот на этом контроллере
в официальной доке пока не сделали апдейт и этот вход так и остается только под кнопку выхода
в этом контроллере реализован уже привод открытия замка и питание замка 12в и теперь еще можно подключить вот такую защелку с контактами обратной связи и контроллер будет знать о закрытой двери
так вот, убик продает “свои” карты, не очень дорого по сравнению с пустыми картами
они покупают мешок пустых карт у производителя какого, прогоняют через машинку и пишут туда некий уникальный номер в формате uuid и потом закрывают его своим ключем desfire
теперь, чтобы расшифровать этот uuid надо чтобы сам считыватель имел в себе сохраненный ключ, которым убики закрыли данные
это значит что ключ либо лежит где-то в софте, что ставится на роутер и управляет контроллерами
либо же ключ прошивается на заводе в каждый контроллер или отдельный считыватель
и тут я подумал, а пусть гемини расковыряет мне прошивку
я ее скачал вчера и увидел в имени файла esp32, что меня несколько удивило, хотя ничего удивительного не должно быть, т.к. это очень мощный контроллер с witi + bt, а мануал говорит, что дверь можно открыть телефоном с убиковским софтом по бт, если махнуть телефоном возле считывателя
ну и есп32 очень любят использовать как шлюз между тцп\ип сетью и протоколом зигби, который тоже поддерживается этим модулем но wifi нельзя одновременно использовать, поэтому туда подключают небольшую микросхему для проводной сети на 10мбит, чего с головой хватает
так вот, в чем цимес? в этом есп32 есть куча всяких криптоштук типа secure boot, что не позволит загрузить туда левые прошивки
внутри есть память nvs, где можно хранить всякие ключи шифрования и есть efuses - специальные биты для активации разных режимов защиты, чтобы ключи нельзя было извлечь
короче вот - я понял, что ключ desfire лежит там и заливается на заводе и никак его не вытащить
вобщем, ии можно использовать для начального анализа прошивок, хотя полностью он их не может расковырять
я думаю можно расковырять его специальными прогами на линупсе, а потом установить консоль гемини и там в чатике дать указание на анализ файлов и поиск чего-то нужного
продолжаю потуги в ии коденье и вот запилил я базу, покатал вату с гемини и он предложил мне там еще сделать seed, это когда база заполняется какими-то данными автоматом, чтобы их не писать руками, а то я уже начал малость по тупости
я изучал фреймворк ларавел и там это дело есть и миграции есть и генераторы инфы есть
спрашивал его, как делаются миграции при помощи ии, если нет специальной проги для генерации кода
еще спрашивал всякие нюансы работы с ним и оказывается правильный путь, это когда ты открываеш чатик и пишеш ему сначала создать модели, руководствуясь схемой бд в файле
потом закрываеш первый чатик, контекстное окно очищается
потом в новом чатике создаются контроллеры, а маршруты лучше самому написать в skills.md
после этого опять закрываем чат, открываем новый и даем опять указание делать дизайн и он уже будет руководствоваться опять правилами из skills.md, где прописаны маршруты, указана схема бд и все такое
от правильности написания этих файлов и задач - зависит расход токенов и скорость получения боле менее работающего прототипа
с учетом всего я пока что решил опробовать генерацию инфы в базу, но без орм, чисто через пдо в пхп, кто в курсе
чтобы модель даже тупая понимала структуру данных, надо их самому правильно составить
# buildings
- BLD
# floors
- EG
## rooms on EG (as range)
- from 0.00 to 0.05
- 1.OG
## rooms on 1.OG (as range)
- from 1.01 to 1.66
# server rooms (each string is full name)
- SS1 / 1.21 (Kern B)
- SS2 / 1.29 (Kern C)
- SS3 / 1.66x (Kern D)
# sockets (as range)
## SS1
- number range 1-24, letters range A-I
## SS2
- number range 1-24, letters range A-D
## SS3
- number range 1-24, letters range A-B
напиши seed.php без orm, используй php pdo sqlite, файл базы ./db.sqlite.
ви таки не поверите, но даже на модели фаст в самом браузере сделал (я еще не начинал проект в антигравити)
еще точность приплел, как будто это какие-то цифры
тут оказалось важным указать более подробно формат именования комнат типа как делают во всыяких прогах переименования #.##
каков итог? написание 100 строк кода такого генератора для меня заняло бы наверное час минимум, а написание формата данных заняло у меня минут 5-10, прогон задания и тест кода еще минут 10
следущий этап - разметка данных более подробно, в какой комнате какие розетки находятся, а так же допишу коридоры еще, надо будет согласовать еще с админом, может мы какие правки внесем в план здания, а то там есть как бы 1 коридор, но 2 его части перпендикулярны друг другу и розетка может быть в одной части или в другой растояние между ними может быть большое и при ошибке идти придется далеко, а софт и делается для удобства и быстрого поиска розеток в физических местах
ну и смишная картинка для тех, кто осилил
из чатика гпт, т.к. гемини выдохся за выходные и 3 итериации картинок вышли не совсем точные, поэтому закину позже, запрос уже подправил несколько раз
на этот раз я сделал эффект выключения кинескопного телика для стримеров
кто не знает - есть такая прога obs studio для стримов и она сделана по принципу аппаратных телестудий и там есть источники и сцены, куда запихаты эти источники и между сценами можно переключаться плавно и даже с эффектами
мой эффект добавляется в источник типа browser, а так же в меню скриптов, т.к. состоит из 2х частей - хтмл файл для браузера и сам скрипт управления хтмл файлом
в чем прикол?
когда стример завершает стрим, то в конце он просто обрывается некрасиво и можно просто переключить на вторую сцену, где какой-то эффект происходит и вызывается функция завершения стрима (есть плагин для автоматизации сцен)
я подумал, почему бы не запилить такой эффект, когда включается вот такой белый снег, горит зеленая надпись как у кинескопных теликов и потом он выключается с характерным схлопыванием картинки и яркой горизонтальной полоской и показом темного экрана, а потом скрипт вызывает функцию завершения трансляции
эффект я придумал давно и внезапно, наверное в 2018 или позже, а то я одно время интересовался обс и автоматизацией, управлением источниками и сценами, даже купил такую вот штуку
это миди контроллер, который посылает всякие байты по миди протоколу + у него там есть еще расширение для субкоманд, специфичных для производителей и там как раз скрыты команды работы с индикатором
вобщем я провозился, но так и не заставил его работать, надо было родной софт ставить, а у меня не ставился .нет 3.5 фреймворк
примерно такую штуку можно прикрутить для управления сценами, виртуальным микшером и источниками
у них есть характерный для видеопультов рычаг плавного перехода между сценами
вобщем нищий вариант управления своей видеостудией это всякие плагины и аппаратные примочки для обс
обс имеет вебсокет сервер, через который можно управлять прогой, либо же встроеный движок луа скриптов, либо можно подключить питон и писать на питоне
как работает источник типа browser?
это встроеный движок хрома, который идет в комплекте и вы можете добавить такой источник, выбрать урл или локальный файл
собственно все эти донатный плашки, всплывающие анимашки и озвучка - это все делается через добавление урл во встроеный браузер, где слой фона делается прозрачным и тогда будет проглядывать картинка из источника, который находится ниже в списке (обычно камера с мордой стримера)
чтобы запустить яваскрипт в браузере - надо передать как-то команду активации в этот скрипт
есть 2 пути - сделать вебсокет сервер в плагине и чтобы скрипт в браузере автоматом подключался к нему
гемини стразу сказал, что с вебсокетами возни много, лучше делать через внутренние апи
много времени я убил на неработающий код по причине установки более новой версии питона 3.11, хотя надо было 3.10
если вы указываете точно версию, то ии не вякает, хотя потом он мне сказал, что типа все знают, что 3.11 это слишком новая версия и библиотека питона для обс криво работает и все юзаю 3.10
так я убил целый день на то, что по сути было готово за пару минут
однако, несколько часов я еще исследовал всякие нюансы коденья для обс, как там что работает, что вообще можно делать и все такое, естественно при помощи гемини
эффект он создал сразу, чего я не ожидал и был удивлен, что это так просто оказалось, хотя яопять же не имел понятия, как генерируются эти белые точки белого шума (а именно так выглядит белый шум в аналоговом тв и шипение в динамике при отсутствии сигнала это он и есть)
кстати историческая справка
почему возникает этот эффект?
при работе телика луч отклоняется катушками кинескопа по горизонтали и так же он смещается по вертикали, меняя яркость
из-за некоторой инертности люминофора и большой скорости прохождения луча глаз не видит все это и для человека кажется, что экран равномерно светится как светится жк монитор
на самом деле там только точка светится
когда телик выключается, то быстрей всего падает напряжение на конденсаторах блока кадровой развертки, при этом работает блок строчной развертки, который еще генерирует высокое напряжение, поэтому картинка схлопывается до горизонтальной линии, которая тоже съужается по краям в почти точку, которая гаснет
это было на ламповых чб теликах, на микросхемных там просто видеопроцессор запирал катоды и кинескоп гас сразу и тут же останавливался блок строчной развертки вместе с кадровой
зато у последних моделей кинескопных теликов переключение каналов происходило долго по сравнению с первыми микросхемными теликами и тем более со старыми, без пульта
в видеопроцессоре есть такая штука как декодер цветности, а там 3 системы и еще в каждой есть подсистема + есть так же выделение звука и все это делается уже цифровым методом, поэтому ему нужно время на оцифровку сигнала, обработку и вывод на экран
все цветное телевидение это один лютейший костыль, а цифровое тв это уже не просто костыль, а инвалид в коляске, который едет, отталкиваясь костылем от дороги
когда-то давно, когда еще только придумали первый кинескоп и как туда засунуть картинку, то особо не заморачивались расходом ресурсов
потом поняли, что картинка слишком мыльная и сделали еще больше тв линий, получив боле менее годный результат
а в результате получилось так, что видеосигнал и звук занимает 8мгц в ширину, что требует достаточно широкополосную антенну и каскады, что для тех времен не так просто было и надо было тоже вписываться в бюджет, да и мощность тоже надо повышать с ростом полосы, что тоже не хорошо
теперь представьте, что у вас чб сигнал занимает около 7.5 мгц и допустим он будет зеленого цвета, а надо передавать еще 2, итого ширина полосы будет почти 23мгц
поэтому придумали сложную схему кодирования цвета при помощи матана и был это такой костыль, что пришлось аж 3 схемы придумывать pal. secam, ntsc
теперь представьте, что в телике стоит мультисистемный декодер и сколько должен стоить такой телик при условии, что 2 системы вам и даром не нужны
при этом телик должен был быть совместим с форматом чб вещания или даже не телик, а система цветного вещания должна быть совместима с чб теликами или если декор пал, а в атенне секам, то хотя бы чб картинка должна быть и звук (а он там тоже отличается)
а теперь представьте, что с переходом на цифровое вещание цифровой сигнал так же не должен вылазить за пределы 8мгц, дабы не менять сами передатчики и антенные системы
кстати антенная система фазированных диполей стоит намного дороже самого передатчика
писал, писал и потом удалил текст так что теперь только код
# Project Specification: CRT TV Power-Off Effect for OBS
## Project Overview
**Effect Concept:**
When the streamer triggers the script, the broadcast displays "no signal" static (snow) for a few seconds. This is followed by a classic CRT shutdown animation: the image collapses into a bright horizontal line, shrinks to a central dot, and disappears.
## Functional Requirements
1. **Trigger Mechanism:** The streamer activates a hotkey or clicks a button, triggering a script that sends data to the OBS Browser Source.
2. **JS Interaction:** The Python script must call the `triggerOff()` function inside the `tv.html` file.
3. **Stream Management:** After a user-defined delay (allowing the animation to finish), the script automatically executes the command to stop the stream.
4. **Testing:** A "TEST" button in the script properties must trigger the `send_json_event(props, prop)` function in `tv.py` to verify the animation without actually stopping the stream.
## AI-Developer Instructions
- **Tooling:** Use the Filesystem MCP for creating and editing files.
- **File Structure:** All work must be done within a single file: `tv.py`.
- **Code Standards:**
- Write clean, production-ready code.
- Write code **without comments**.
- Use the provided `tv.py` as a boilerplate/template with its working functions.
- **Context:** Use `tv.html` for reference only. It is already fully functional and should be treated as read-only.
## Tech Stack
- **Language:** Python 3.10
- **Environment:** OBS Studio 32.1.2
- **Library:** `obspython` (Standard OBS Scripting API).
- **Communication:** OBS API for Browser Source interaction (sending JSON events).
## UI/UX Requirements (OBS Script Properties)
The script must implement an interface in the OBS "Scripts" window:
- **Source Selection:** A dropdown menu (`obs_properties_add_list`) to select the target Browser Source.
- **Shutdown Delay:** A numerical field to define the delay in seconds before the stream is stopped.
- **Test Button:** A button that forces the execution of the `send_json_event` logic for debugging purposes.
тут поясню function generateNoise() делает белый шум на канвасе (это в яваскрипте поле, где можно делать 2д или 3д графику и рыбки с аквариумом как раз в тестах делаются на этой штуке
// 2. Эффект схлопывания через 3 сек
setTimeout(() => {
container.classList.add('collapsing');
dot.classList.add('dot-glow');
noiseActive = false;
тут надо noiseActive остановить, т.к. генерация шума довольно затратная штука
весь цимес же скрыт в этом коде
// Слушатель для управления извне (OBS/Python)
window.addEventListener('my-test-event', function (event) {
triggerOff();
});
я его почистил, тут хватит только вызова одной функции, а вот my-test-event это как раз и есть то самое событие кастомное, которое регистрируется в контексте запущеной обс и пользовательского скрипта на питоне или луа
весь код хтмл можно скопировать в файл и запустить у себя в браузере
продолжаем про эффект, а то вчера лень уже было писать
осталось 2 скрипта на питоне и луа
если вы будете что-то делать для обс, что работает с внутренним браузером, то можно включить обс в режиме отладки и он будет посылать инфу через вебсокет в ваш обычный браузер и там можно уже смотреть все как обычно
в обс можно для своих скриптов делать простой интерфейс, только вы его никак не сможете вытащить на морду самой обс, так же нельзя перехватить вызовы кнопок управления записью или стримом, если кнопку нажали, то стрим остановится так и так, влезть туда нельзя будет, но можно будет словить нажатие этой кнопки, там в апи есть такое событие
а нам что надо? чтобы наш эффект наложился поверх всех остальных
для этого эффект в источниках надо распологать самым первым и он активен будет, но у него есть прозрачность по дефолту, поэтому ничего не закроет
потом скрипт на питоне посылает событие my-test-event яваскрипт движку, который слушает это событие и вызывает функцию triggerOff();
важно учитывать, что надо освобождать ресурсы при выходе из скрипта, а то будет утечка памяти
вот полный скрипт на питоне
import obspython as obs
source_name = ""
shutdown_delay = 3.0
hotkey_id = None
def send_json_event(props, prop):
source = obs.obs_get_source_by_name(source_name)
if source:
ph = obs.obs_source_get_proc_handler(source)
print(source_name)
cd = obs.calldata_create()
obs.calldata_set_string(cd, "eventName", "my-test-event")
obs.calldata_set_string(cd, "jsonString", '{"key123": "value123"}')
obs.proc_handler_call(ph, "javascript_event", cd)
obs.calldata_destroy(cd)
obs.obs_source_release(source)
print("send json")
return True
def on_timer_stop():
obs.obs_frontend_streaming_stop()
obs.remove_current_callback()
def execute_shutdown(props, prop):
send_json_event(None, None)
obs.timer_add(on_timer_stop, int(shutdown_delay * 1000))
return True
def execute_shutdown_hotkey(pressed):
if pressed:
execute_shutdown(None, None)
def script_description():
return "CRT TV Power-Off Effect"
def script_update(settings):
global source_name, shutdown_delay
source_name = obs.obs_data_get_string(settings, "source")
shutdown_delay = obs.obs_data_get_double(settings, "delay")
def script_properties():
props = obs.obs_properties_create()
p = obs.obs_properties_add_list(props, "source", "Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
sources = obs.obs_enum_sources()
if sources:
for s in sources:
if obs.obs_source_get_unversioned_id(s) == "browser_source":
name = obs.obs_source_get_name(s)
obs.obs_property_list_add_string(p, name, name)
obs.source_list_release(sources)
obs.obs_properties_add_float(props, "delay", "Delay (s)", 0.1, 10.0, 0.1)
obs.obs_properties_add_button(props, "test_btn", "TEST", send_json_event)
obs.obs_properties_add_button(props, "off_btn", "POWER OFF", execute_shutdown)
return props
def script_load(settings):
global hotkey_id
hotkey_id = obs.obs_hotkey_register_frontend("trigger_crt_off", "CRT Power Off", execute_shutdown_hotkey)
a = obs.obs_data_get_array(settings, "trigger_crt_off_hotkey")
obs.obs_hotkey_load(hotkey_id, a)
obs.obs_data_array_release(a)
def script_save(settings):
a = obs.obs_hotkey_save(hotkey_id)
obs.obs_data_set_array(settings, "trigger_crt_off_hotkey", a)
obs.obs_data_array_release(a)
def script_unload():
obs.obs_hotkey_unregister(hotkey_id)
вот скрипта на луа
function send_json_event()
local source = obs.obs_get_source_by_name(source_name)
if source ~= nil then
local ph = obs.obs_source_get_proc_handler(source)
local cd = obs.calldata_create()
obs.calldata_set_string(cd, "eventName", "my-test-event")
obs.calldata_set_string(cd, "jsonString", '{"key123": "value123"}')
obs.proc_handler_call(ph, "javascript_event", cd)
obs.calldata_destroy(cd)
obs.obs_source_release(source)
end
end
нужно задать комбинацию кнопок для запуска скрипта, он активирует яваскрипт код, код выполняется сам по себе и не уведомляет обс скрипт, поэтому в обс скрипте есть свой таймер, который надо выставить с учетом времени выполнения яваскрипта + пауза выключенного экрана и только после этого обс скрипт вызовет функцию заверешния стрима
как я уже писал выше - сделать эффект рабочим при нажатии на родную кнопку останова стрима невозможно
но гемини подсказал анальный метод - сделать свою док панель с кнопкой останова стрима, а дефолтный док скрыть
т.к. кастомный док это по сути веб браузер, то кнопки можно рисовать лютые и писать яваскрипты свои, которые через вебсокет могут вызвать уже всякие функции обс, там теперь вебсокет апи встроен по деофлту, а то в последний раз, когда я интересовался обс - это был плагин отдельный
так же, пока я писал, то подумал, что скрипт можно упростить, выкинув из него прозрачность и ее активацию
луа скрипт может просто активировать источник с эффектом, тогда картинка автоматом наложится поверх всех, отработает и все, сработает таймер в скрипте и он вызовет функцию останова стрима
а потом я еще подумал, гемини предлогал сделать свой кастомный док, который управляет обс
тогда почему бы туда не запилить всю логику и выкинуть луа скрипт совсем?
я сейчас уже спросил у него инфу о том, как работает кастомный док и что там вообще надо знать, почитаю чатик и попробую сделать эту идею
внезапно, шок
гугл выпилил флеш 3.1 совсем, на чем я кодил все это дело и запилил 3.5 флеш, который за несколько запросов сожрал недельный запас токенов, чего никогда не случалось на 3.1 флеше
при этом модель тупила конкретно и пришлось перейти на 3.1 про
сделал вот такой эффект для обс студии
чтобы вы понимали, насколько сложный код и алгоритм - выкладываю весь код
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>End Broadcasting</title>
<style>
/* Убираем отступы и прокрутку, заливаем фон синим цветом (советский ТВ-фон) */
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #1c3c87; /* тёмно-синий фон, как у советской заставки */
}
canvas {
display: block;
}
</style>
</head>
<body>
<!-- ===== ТЕСТОВЫЕ КНОПКИ (для ручного запуска эффекта) ===== -->
<div id="test-buttons" style="position: absolute; bottom: 20px; width: 100%; text-align: center; z-index: 10;">
<!-- Кнопка «Start Effect» — запускает анимацию появления (mode: 'start'),
duration: 5 — длительность фазы порождения колец в секундах -->
<button onclick="window.triggerEffect({ duration: 5, mode: 'start' })" style="padding: 10px 20px; font-size: 16px; margin: 0 10px; cursor: pointer;">Start Effect</button>
<!-- Кнопка «End Effect» — запускает анимацию схлопывания (mode: 'end') -->
<button onclick="window.triggerEffect({ duration: 5, mode: 'end' })" style="padding: 10px 20px; font-size: 16px; margin: 0 10px; cursor: pointer;">End Effect</button>
</div>
<!-- Основной canvas, на котором отрисовывается вся графика -->
<canvas id="stage"></canvas>
<script>
/* ========================================================================
ПОЛУЧЕНИЕ КОНТЕКСТА РИСОВАНИЯ
======================================================================== */
const canvas = document.getElementById('stage');
// alpha: false — фон непрозрачный, что ускоряет отрисовку
const ctx = canvas.getContext('2d', { alpha: false });
/* ========================================================================
ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ РАЗМЕРОВ И СОСТОЯНИЯ
======================================================================== */
let w, h, cx, cy, minDim;
// w, h — текущая ширина/высота окна (canvas)
// cx, cy — координаты центра canvas (cx = w/2, cy = h/2)
// minDim — меньшая сторона окна (используется для масштабирования элементов)
let effectMode = 'start'; // текущий режим: 'start' (появление) или 'end' (исчезновение)
let effectEndTime = 0; // метка времени (в мс), когда прекращается порождение новых колец
let rings = []; // массив радиусов анимированных колец (каждый элемент — текущий радиус одного кольца)
let lastTime = 0; // время предыдущего кадра (для расчёта delta-time)
let isPlaying = false; // флаг: идёт ли сейчас анимация
let lastSpawnTime = 0; // время последнего создания нового кольца
/* ========================================================================
ФАЗЫ АНИМАЦИИ И ПЕРЕМЕННЫЕ ПЕРЕХОДОВ
======================================================================== */
let phase = 'idle'; // текущая фаза анимации (см. описание фаз ниже)
let currentBigR = 0; // текущий радиус БОЛЬШОГО статичного кольца (внешняя граница)
let currentSmallAlpha = 0; // текущая прозрачность всей сцены (0 = невидима, 1 = полностью видима)
let pendingDuration = 0; // запланированная длительность эффекта (в мс)
/*
ФАЗЫ (phase):
─────────────────────────────────────────────────
«idle» — ничего не происходит
─── START-эффект (появление) ───
«start_fade_in» — плавное появление сцены (alpha 0 → 1)
«start_first_ring» — большое кольцо расширяется от minR до maxR
«start_loop» — основной цикл: кольца порождаются на minR и расширяются до maxR
─── END-эффект (исчезновение) ───
«end_fade_in» — если сцена ещё не видна — сначала показываем её
«end_loop» — кольца порождаются на maxR и сжимаются до minR
«end_wait_rings» — ждём, пока все кольца долетят до центра
«end_collapse_big» — большое кольцо сжимается от maxR до minR
«end_fade_out» — плавное исчезновение сцены (alpha 1 → 0)
*/
/* ========================================================================
ПАРАМЕТРЫ ВИЗУАЛА И АНИМАЦИИ
======================================================================== */
const params = {
bgColor: '#1c3c87', // цвет фона (тёмно-синий)
starColor: '#ff2222', // цвет красной звезды
ringColor: '#ffffff', // цвет колец и обводок (белый)
ringWidth: 2, // толщина линий колец (px)
ringSpeedBase: 0.12, // базовая скорость расширения/сжатия колец (доля minDim в секунду)
ringSpawnInterval: 800, // интервал между появлением новых колец (мс)
fadeDuration: 1000, // длительность плавного появления/исчезновения (мс)
animationSpeed: 1.0 // множитель скорости всей анимации
};
/* ========================================================================
ФУНКЦИЯ МАСШТАБИРОВАНИЯ ПОД РАЗМЕР ОКНА
======================================================================== */
function resize() {
w = window.innerWidth;
h = window.innerHeight;
canvas.width = w;
canvas.height = h;
cx = w / 2; // центр по горизонтали
cy = h / 2; // центр по вертикали
minDim = Math.min(w, h); // для пропорционального масштабирования
}
// При изменении размера окна — пересчитываем и перерисовываем
window.addEventListener('resize', () => {
resize();
if (!isPlaying) {
drawScene(); // статичная перерисовка, если анимация не идёт
}
});
resize(); // первичная инициализация размеров
/* ========================================================================
ОТРИСОВКА ПЯТИКОНЕЧНОЙ ЗВЕЗДЫ
======================================================================== */
// x, y — центр звезды
// r — радиус внешних вершин
// topY — y-координата верхнего луча (удлинённый верхний луч)
function drawStar(x, y, r, topY) {
ctx.beginPath();
const points = 5; // количество лучей
const innerRadius = r * 0.382; // радиус внутренних впадин (≈ золотое сечение)
for (let i = 0; i < points * 2; i++) {
const isOuter = i % 2 === 0; // чётные индексы — внешние вершины, нечётные — внутренние
const radius = isOuter ? r : innerRadius;
const angle = i * Math.PI / points - Math.PI / 2; // поворот на -90° чтобы первая вершина смотрела вверх
let px = x + Math.cos(angle) * radius; // координата x точки
let py = y + Math.sin(angle) * radius; // координата y точки
// Первая точка (i === 0) — это верхний луч; его y заменяется на topY,
// чтобы верхний луч был длиннее остальных
if (i === 0) {
py = topY;
}
if (i === 0) {
ctx.moveTo(px, py); // начинаем путь
} else {
ctx.lineTo(px, py); // продолжаем путь
}
}
ctx.closePath();
ctx.fillStyle = params.starColor; // заливка красным
ctx.fill();
}
/* ========================================================================
ОТРИСОВКА ВСЕЙ СЦЕНЫ (вызывается каждый кадр)
======================================================================== */
function drawScene() {
// ── 1. Заливка фона ──
ctx.fillStyle = params.bgColor;
ctx.fillRect(0, 0, w, h);
// ── 2. Расчёт ключевых размеров ──
const maxR = minDim * 0.28; // максимальный радиус (внешняя граница / большое кольцо)
const minR = minDim * 0.08; // минимальный радиус (внутреннее статичное кольцо)
const Ty = cy + maxR * 0.65; // y-координата нижней точки (опорная линия компоновки)
const starCenterY = Ty - minR; // центр звезды по y (центр малого кольца)
// Применяем общую прозрачность сцены (для fade-in / fade-out)
ctx.save();
ctx.globalAlpha = currentSmallAlpha;
// ── 3. СВЕЧЕНИЕ (радиальный градиент за звездой) ──
const gradCenterY = Ty - maxR; // центр градиента (выше центра звезды)
const gradRadius = maxR * 1.3; // радиус свечения
const gradient = ctx.createRadialGradient(cx, gradCenterY, 0, cx, gradCenterY, gradRadius);
gradient.addColorStop(0, 'rgba(150, 200, 255, 0.4)'); // яркий центр (голубоватый)
gradient.addColorStop(0.5, 'rgba(100, 160, 255, 0.15)'); // переход
gradient.addColorStop(1, 'rgba(28, 60, 135, 0)'); // полностью прозрачный край
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(cx, gradCenterY, gradRadius, 0, Math.PI * 2); // ← КРУГ свечения
ctx.fill();
// ── 4. МАЛОЕ КОЛЬЦО (статичный внутренний круг вокруг звезды) ──
ctx.strokeStyle = params.ringColor; // белый
ctx.lineWidth = params.ringWidth; // толщина 2px
ctx.beginPath();
ctx.arc(cx, Ty - minR, minR, 0, Math.PI * 2); // ← КРУГ: центр (cx, starCenterY), радиус minR
ctx.stroke();
// ── 5. КРАСНАЯ ЗВЕЗДА ──
const starR = minR; // радиус звезды = радиус малого кольца
const rayLength = (2 * maxR - minR) / 0.8; // длина верхнего луча (от центра звезды до кончика)
const topY = starCenterY - rayLength; // абсолютная y-позиция кончика верхнего луча
drawStar(cx, starCenterY, starR, topY);
// ── 6. ТЕКСТ «II ПРОГРАММА» ──
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
const fontSize = minDim * 0.055; // размер шрифта пропорционален окну
ctx.font = `bold ${fontSize}px sans-serif`;
ctx.save();
ctx.translate(cx, Ty + minDim * 0.06); // позиция текста ниже основной графики
ctx.scale(1.2, 1); // горизонтальное растяжение (буквы шире)
ctx.fillText('II ПРОГРАММА', 0, 0);
ctx.restore();
// ── 7. БОЛЬШОЕ КОЛЬЦО (внешняя статичная граница) ──
ctx.strokeStyle = params.ringColor;
ctx.lineWidth = params.ringWidth;
if (currentBigR > 0) {
ctx.beginPath();
// ← КРУГ: центр (cx, Ty - currentBigR), радиус currentBigR
// Нижняя точка этого круга всегда касается Ty
ctx.arc(cx, Ty - currentBigR, currentBigR, 0, Math.PI * 2);
ctx.stroke();
}
// ── 8. АНИМИРОВАННЫЕ КОЛЬЦА (расширяющиеся / сжимающиеся) ──
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)'; // полупрозрачный белый
ctx.lineWidth = params.ringWidth * 0.6; // чуть тоньше основных колец
for (let i = 0; i < rings.length; i++) {
ctx.beginPath();
// ← КРУГ: центр (cx, Ty - rings[i]), радиус rings[i]
// Каждое кольцо «поднимается» по мере роста радиуса (центр сдвигается вверх)
// Нижняя точка каждого кольца всегда на линии Ty
ctx.arc(cx, Ty - rings[i], rings[i], 0, Math.PI * 2);
ctx.stroke();
}
ctx.restore(); // восстанавливаем globalAlpha
}
/* ========================================================================
ПОДФУНКЦИИ ФАЗ АНИМАЦИИ
Каждая функция отвечает за логику одной фазы.
Принимают объект p с предвычисленными параметрами кадра:
p.time — текущее время кадра (мс, от requestAnimationFrame)
p.speed — скорость перемещения колец за этот кадр (px)
p.fadeSpeed — скорость изменения прозрачности за этот кадр (0..1)
p.spawnInterval — интервал между появлением новых колец (мс)
p.maxR — максимальный радиус (внешняя граница колец)
p.minR — минимальный радиус (внутреннее кольцо / звезда)
======================================================================== */
// Плавно увеличивает прозрачность сцены от 0 до 1.
// По завершении → переходит к фазе расширения большого кольца (start_first_ring).
function phaseStartFadeIn(p) {
currentSmallAlpha += p.fadeSpeed;
if (currentSmallAlpha >= 1) {
currentSmallAlpha = 1;
phase = 'start_first_ring';
currentBigR = p.minR;
}
}
// Расширяет большое (внешнее) кольцо от minR до maxR.
// По завершении → переходит к основному циклу порождения колец (start_loop).
function phaseStartFirstRing(p) {
currentBigR += p.speed;
if (currentBigR >= p.maxR) {
currentBigR = p.maxR;
phase = 'start_loop';
lastSpawnTime = p.time;
}
}
// Основной цикл START-эффекта:
// — порождает новые кольца на minR с заданным интервалом,
// — расширяет все кольца, удаляет достигшие maxR,
// — завершает анимацию, когда время вышло и колец не осталось.
function phaseStartLoop(p) {
if (p.time < effectEndTime) {
if (p.time - lastSpawnTime > p.spawnInterval) {
rings.push(p.minR);
lastSpawnTime = p.time;
}
}
for (let i = rings.length - 1; i >= 0; i--) {
rings[i] += p.speed;
if (rings[i] >= p.maxR) {
rings.splice(i, 1);
}
}
if (p.time >= effectEndTime && rings.length === 0) {
isPlaying = false;
}
}
// Плавно увеличивает прозрачность сцены (для END-режима, если сцена была скрыта).
// По завершении → переходит к циклу сжатия (end_loop).
function phaseEndFadeIn(p) {
currentSmallAlpha += p.fadeSpeed;
if (currentSmallAlpha >= 1) {
currentSmallAlpha = 1;
phase = 'end_loop';
lastSpawnTime = p.time;
}
}
// Основной цикл END-эффекта:
// — порождает новые кольца на maxR с заданным интервалом,
// — сжимает все кольца к центру, удаляет достигшие minR,
// — когда время вышло → переходит к ожиданию завершения колец (end_wait_rings).
function phaseEndLoop(p) {
if (p.time < effectEndTime) {
if (p.time - lastSpawnTime > p.spawnInterval) {
rings.push(p.maxR);
lastSpawnTime = p.time;
}
} else {
phase = 'end_wait_rings';
}
for (let i = rings.length - 1; i >= 0; i--) {
rings[i] -= p.speed;
if (rings[i] <= p.minR) {
rings.splice(i, 1);
}
}
}
// Дожидается, пока все оставшиеся кольца долетят до центра (сожмутся до minR).
// Когда колец не осталось → переходит к схлопыванию большого кольца (end_collapse_big).
function phaseEndWaitRings(p) {
for (let i = rings.length - 1; i >= 0; i--) {
rings[i] -= p.speed;
if (rings[i] <= p.minR) {
rings.splice(i, 1);
}
}
if (rings.length === 0) {
phase = 'end_collapse_big';
}
}
// Схлопывает большое кольцо от текущего радиуса до minR, затем убирает его (= 0).
// По завершении → переходит к плавному затуханию (end_fade_out).
function phaseEndCollapseBig(p) {
currentBigR -= p.speed;
if (currentBigR <= p.minR) {
currentBigR = 0;
phase = 'end_fade_out';
}
}
// Плавно уменьшает прозрачность сцены от 1 до 0.
// По завершении → останавливает анимацию (isPlaying = false).
function phaseEndFadeOut(p) {
currentSmallAlpha -= p.fadeSpeed;
if (currentSmallAlpha <= 0) {
currentSmallAlpha = 0;
isPlaying = false;
}
}
/* ========================================================================
ОСНОВНОЙ ЦИКЛ АНИМАЦИИ (вызывается через requestAnimationFrame)
======================================================================== */
function update(time) {
// ── Первый кадр: инициализация таймингов ──
if (!lastTime) {
lastTime = time;
lastSpawnTime = time;
effectEndTime = time + pendingDuration; // время окончания порождения колец
requestAnimationFrame(update);
return;
}
const dt = time - lastTime; // время между кадрами (мс)
lastTime = time;
// ── Предвычисленные параметры кадра (передаются в подфункции) ──
const p = {
time,
maxR: minDim * 0.28, // максимальный радиус
minR: minDim * 0.08, // минимальный радиус
speed: minDim * params.ringSpeedBase * params.animationSpeed * (dt / 1000), // px за кадр
fadeSpeed: dt * params.animationSpeed / params.fadeDuration, // 0..1 за кадр
spawnInterval: params.ringSpawnInterval / params.animationSpeed // мс
};
/* ==================================================================
ОБРАБОТКА ФАЗ АНИМАЦИИ (switch по текущей фазе)
================================================================== */
switch (phase) {
// Плавное проявление сцены (alpha 0→1), затем → start_first_ring
case 'start_fade_in':
phaseStartFadeIn(p);
break;
// Расширение большого кольца (minR→maxR), затем → start_loop
case 'start_first_ring':
phaseStartFirstRing(p);
break;
// Циклическое порождение расширяющихся колец, до истечения времени
case 'start_loop':
phaseStartLoop(p);
break;
// Проявление сцены для end-режима (если была скрыта), затем → end_loop
case 'end_fade_in':
phaseEndFadeIn(p);
break;
// Циклическое порождение сжимающихся колец, до истечения времени → end_wait_rings
case 'end_loop':
phaseEndLoop(p);
break;
// Ожидание, пока все кольца долетят до центра, затем → end_collapse_big
case 'end_wait_rings':
phaseEndWaitRings(p);
break;
// Схлопывание большого кольца (maxR→0), затем → end_fade_out
case 'end_collapse_big':
phaseEndCollapseBig(p);
break;
// Плавное затухание сцены (alpha 1→0), затем анимация останавливается
case 'end_fade_out':
phaseEndFadeOut(p);
break;
}
drawScene(); // отрисовка текущего кадра
if (isPlaying) {
requestAnimationFrame(update); // запрос следующего кадра
}
}
/* ========================================================================
ПУБЛИЧНАЯ ФУНКЦИЯ ЗАПУСКА ЭФФЕКТА (вызывается извне или кнопками)
======================================================================== */
// options.duration — длительность порождения колец (в секундах, по умолчанию 10)
// options.mode — 'start' (появление) или 'end' (исчезновение)
window.triggerEffect = function(options) {
options = options || {};
const durationSec = options.duration !== undefined ? options.duration : 10;
effectMode = options.mode === 'end' ? 'end' : 'start';
pendingDuration = durationSec * 1000; // переводим секунды → миллисекунды
if (effectMode === 'start') {
// ── Режим ПОЯВЛЕНИЯ ──
phase = 'start_fade_in'; // начинаем с плавного проявления
currentSmallAlpha = 0; // сцена невидима
currentBigR = 0; // большого кольца ещё нет
rings = []; // очищаем массив анимированных колец
} else {
// ── Режим ИСЧЕЗНОВЕНИЯ ──
if (currentSmallAlpha < 1) {
phase = 'end_fade_in'; // если сцена не полностью видна — сначала покажем
} else {
phase = 'end_loop'; // иначе — сразу начинаем сжимать кольца
}
currentBigR = minDim * 0.28; // большое кольцо сразу на максимальном радиусе
}
lastTime = 0; // сброс таймера (будет инициализирован в update)
drawScene(); // немедленная отрисовка начального состояния
if (!isPlaying) {
isPlaying = true;
requestAnimationFrame(update); // запуск цикла анимации
}
};
// Первичная отрисовка сцены при загрузке (фон + ничего, т.к. alpha = 0)
drawScene();
</script>
</body>
</html>
потом специально просил написать каменты к коду чуть ли не к каждой строчке
можно в начало стрима ставить часы или сначала заставку со звездой, потом за пару минут переключить на часы и потом на стрим, когда часы дойдут до нужного времени
а в конце показывать заставку со звездой, потом испытательную таблицу 5 сек, потом белый шум и выключающийся кинескоп
т.к. гугл выпилил дешевую модель, то придется мне покупать платную подписку, пока на месяц возьму тестовую с шареным акаунтом, но гарантированными токенами
так же я переписал сидер для бд для моей проги инвентаризации розеток сетевых и что к ним подключено
сделал я карту, какие розетки в каких комнатах есть и на какие серверные комнаты они подключены
самое прикольное получилось, когда я запрос дал на проверку ии и он обнаружил, что у меня есть 25я розетка, хотя я указывал генерить только от 1 до 24 как на патчпанелях
еще пару раз я опечатался и ии нашел это, а на плане офиса действительно 25й порт есть, я уже написал работягам, чтобы кто-то сходил посмотреть в ту комнату, что там реально за маркировка такая или на плане это опечатка просто
вот запрос
# Project Overview
Write a seeder script named `seeder.php`. Create database records according to the data structure described below: building, floors, rooms, and server rooms with sockets. Map sockets from different server rooms to standard rooms according to the data structure below, where room `0.01` connects to sockets `H5-H16, H19-H22`, as indicated by the `->` symbol.
Role: Experienced PHP Developer with Database knowledge.
## Rules
- Use only PHP PDO.
- Use SQLite database.
- Database structure is defined in the file `ChartDB(rooms).json`.
- SQLite database file path: `./db.sqlite`.
- Clear table data if it exists (TRUNCATE / DELETE) before seeding.
- Do all work in a single file `seeder.php`.
- Write clean code without comments.
## Create Buildings
- BLD
## Create Floors
- EG
- 1.OG
## Create Rooms on EG (as range)
- from 0.01 to 0.05
## Create Rooms on 1.OG (as range and specific names)
- from 1.01 to 1.68 (exclude: 1.09, 1.10, 1.21, 1.29, 1.40, 1.57, 1.67)
- Abstellkammer
- Kern B Ost
- Kern B Empfang
- Kern B West
- Kern B Nord
- Kern C Center
- Kern C Nord
- Kern C West
- Kern D Nord
## Create Server Rooms (each string is the full name)
- SS1 / 1.21 (Kern B)
- SS2 / 1.29 (Kern C)
- SS3 / 1.66x (Kern D)
## Create Sockets (as ranges with format A1, A2, I24)
### SS1 Sockets
- Letters: A-I
- Numbers: 1-24
### SS2 Sockets
- Letters: A-D
- Numbers: 1-24
### SS3 Sockets
- Letters: A-B
- Numbers: 1-24
## Relations Between Rooms and Sockets
### All Sockets from SS1
- 0.01 -> H5-H16, H19-H22
- 0.02 -> I1-I6
- 0.03 -> H17-H18
- 0.04 -> H3-H4
- 0.05 -> H1-H2
- 1.01 -> B3-B12
- 1.02 -> B1-B2, A23-A24
- 1.03 -> A17-A22
- 1.04 -> A13-A16
- 1.05 -> A9-A12
- 1.06 -> A5-A8
- 1.07 -> A1-A4
- 1.08 -> B13-B18
- 1.11 -> G5-G10, E3-E12
- 1.12 -> E1-E2, D21-D24
- 1.13 -> D13-D15, D19-D20
- 1.14 -> D11-D12
- 1.15 -> F3-F4
- 1.16 -> F1-F2
- 1.17 -> E23-E24
- Kern B Ost -> F19-F20, F13-F16
- Kern B Empfang -> F21-F22
- Kern B West -> G1-G4
- Kern B Nord -> F5-F8, F11-F12
- Abstellkammer -> F23-F24
- 1.22 -> D7-D10
- 1.23 -> D3-D6
- 1.24 -> D1-D1, C24-C24
- 1.25 -> C19-C22
- 1.26 -> E21-E22
- 1.27 -> E23-E24
- 1.28 -> C9-C14, C17-C18
- 1.30 -> C3-C8
- 1.31 -> E17-E18
- 1.32 -> E15-E16
- 1.33 -> E13-E14
- 1.34 -> C1-C2, B23-B24
- 1.35 -> B19-B22
### All Sockets from SS2
- Kern C Center -> D20-D21
- Kern C Nord -> D10-D13
- Kern C West -> D14-D19
- 1.41 -> C2-C9
- 1.42 -> B19-B24
- 1.43 -> B11-B18
- 1.44 -> B4-B10
- 1.45 -> B1-B3, A22-A24
- 1.46 -> A14-A21
- 1.47 -> A7-A13
- 1.48 -> A1-A6
- 1.51 -> C20-C21
- 1.52 -> C16-C19
- 1.53 -> C12-C15
- 1.54 -> D8-D9
- 1.55 -> D6-D7
- 1.56 -> D4-D5
### All Sockets from SS3
- 1.61 -> B8-B13
- 1.62 -> B1-B6
- 1.63 -> A18-A24
- 1.64 -> A12-A17
- 1.65 -> A7-A11, B19
- 1.66 -> A3-A6
- 1.68 -> A1-A2
- Kern D Nord -> B14-B17
<?php
$pdo = new PDO('sqlite:./db.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('PRAGMA foreign_keys = ON');
$pdo->exec('CREATE TABLE IF NOT EXISTS buildings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR NOT NULL
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS floors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
building_id INTEGER,
name VARCHAR NOT NULL,
FOREIGN KEY (building_id) REFERENCES buildings(id)
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS rooms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
floor_id INTEGER,
name VARCHAR NOT NULL,
FOREIGN KEY (floor_id) REFERENCES floors(id)
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS server_rooms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR NOT NULL
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS sockets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_room_id INTEGER,
label VARCHAR NOT NULL,
FOREIGN KEY (server_room_id) REFERENCES server_rooms(id)
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS sockets_states (
id INTEGER PRIMARY KEY AUTOINCREMENT,
socket_state VARCHAR(255) NOT NULL
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS device_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_name VARCHAR(255) NOT NULL
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS vlan_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vlan_name VARCHAR(255) NOT NULL
)');
$pdo->exec('CREATE TABLE IF NOT EXISTS connections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id INTEGER,
socket_id INTEGER,
vlan_ids TEXT,
device_ids TEXT,
state_id INTEGER,
note VARCHAR,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES rooms(id),
FOREIGN KEY (socket_id) REFERENCES sockets(id),
FOREIGN KEY (state_id) REFERENCES sockets_states(id)
)');
$pdo->exec('DELETE FROM connections');
$pdo->exec('DELETE FROM sockets');
$pdo->exec('DELETE FROM rooms');
$pdo->exec('DELETE FROM server_rooms');
$pdo->exec('DELETE FROM floors');
$pdo->exec('DELETE FROM buildings');
$pdo->exec('DELETE FROM sockets_states');
$pdo->exec('DELETE FROM device_types');
$pdo->exec('DELETE FROM vlan_types');
$pdo->exec("INSERT INTO buildings (name) VALUES ('BLD')");
$buildingId = $pdo->lastInsertId();
$stmtFloor = $pdo->prepare('INSERT INTO floors (building_id, name) VALUES (?, ?)');
$stmtFloor->execute([$buildingId, 'EG']);
$floorEG = $pdo->lastInsertId();
$stmtFloor->execute([$buildingId, '1.OG']);
$floor1OG = $pdo->lastInsertId();
$stmtRoom = $pdo->prepare('INSERT INTO rooms (floor_id, name) VALUES (?, ?)');
$roomIds = [];
for ($i = 1; $i <= 5; $i++) {
$name = '0.' . str_pad($i, 2, '0', STR_PAD_LEFT);
$stmtRoom->execute([$floorEG, $name]);
$roomIds[$name] = $pdo->lastInsertId();
}
$exclude1OG = [9, 10, 21, 29, 40, 57, 67];
for ($i = 1; $i <= 68; $i++) {
if (in_array($i, $exclude1OG)) {
continue;
}
$name = '1.' . str_pad($i, 2, '0', STR_PAD_LEFT);
$stmtRoom->execute([$floor1OG, $name]);
$roomIds[$name] = $pdo->lastInsertId();
}
$namedRooms = [
'Abstellkammer',
'Kern B Ost',
'Kern B Empfang',
'Kern B West',
'Kern B Nord',
'Kern C Center',
'Kern C Nord',
'Kern C West',
'Kern D Nord',
];
foreach ($namedRooms as $name) {
$stmtRoom->execute([$floor1OG, $name]);
$roomIds[$name] = $pdo->lastInsertId();
}
$stmtSR = $pdo->prepare('INSERT INTO server_rooms (name) VALUES (?)');
$serverRoomIds = [];
$stmtSR->execute(['SS1 / 1.21 (Kern B)']);
$serverRoomIds['SS1'] = $pdo->lastInsertId();
$stmtSR->execute(['SS2 / 1.29 (Kern C)']);
$serverRoomIds['SS2'] = $pdo->lastInsertId();
$stmtSR->execute(['SS3 / 1.66x (Kern D)']);
$serverRoomIds['SS3'] = $pdo->lastInsertId();
$stmtSocket = $pdo->prepare('INSERT INTO sockets (server_room_id, label) VALUES (?, ?)');
$socketIds = [];
$ss1Letters = range('A', 'I');
foreach ($ss1Letters as $letter) {
for ($num = 1; $num <= 24; $num++) {
$label = $letter . $num;
$stmtSocket->execute([$serverRoomIds['SS1'], $label]);
$socketIds['SS1'][$label] = $pdo->lastInsertId();
}
}
$ss2Letters = range('A', 'D');
foreach ($ss2Letters as $letter) {
for ($num = 1; $num <= 24; $num++) {
$label = $letter . $num;
$stmtSocket->execute([$serverRoomIds['SS2'], $label]);
$socketIds['SS2'][$label] = $pdo->lastInsertId();
}
}
$ss3Letters = range('A', 'B');
foreach ($ss3Letters as $letter) {
for ($num = 1; $num <= 24; $num++) {
$label = $letter . $num;
$stmtSocket->execute([$serverRoomIds['SS3'], $label]);
$socketIds['SS3'][$label] = $pdo->lastInsertId();
}
}
$stmtState = $pdo->prepare('INSERT INTO sockets_states (socket_state) VALUES (?)');
$stmtState->execute(['Connected']);
$stmtState->execute(['Defective']);
$stmtState->execute(['Not Available']);
function expandSocketRange($rangeStr) {
$sockets = [];
$parts = array_map('trim', explode(',', $rangeStr));
foreach ($parts as $part) {
if (strpos($part, '-') !== false) {
[$start, $end] = explode('-', $part);
$letterStart = $start[0];
$numStart = (int) substr($start, 1);
$letterEnd = $end[0];
$numEnd = (int) substr($end, 1);
if ($letterStart === $letterEnd) {
for ($n = $numStart; $n <= $numEnd; $n++) {
$sockets[] = $letterStart . $n;
}
}
} else {
$sockets[] = $part;
}
}
return $sockets;
}
$stmtConn = $pdo->prepare('INSERT INTO connections (room_id, socket_id) VALUES (?, ?)');
$ss1Relations = [
'0.01' => 'H5-H16, H19-H22',
'0.02' => 'I1-I6',
'0.03' => 'H17-H18',
'0.04' => 'H3-H4',
'0.05' => 'H1-H2',
'1.01' => 'B3-B12',
'1.02' => 'B1-B2, A23-A24',
'1.03' => 'A17-A22',
'1.04' => 'A13-A16',
'1.05' => 'A9-A12',
'1.06' => 'A5-A8',
'1.07' => 'A1-A4',
'1.08' => 'B13-B18',
'1.11' => 'G5-G10, E3-E12',
'1.12' => 'E1-E2, D21-D24',
'1.13' => 'D13-D15, D19-D20',
'1.14' => 'D11-D12',
'1.15' => 'F3-F4',
'1.16' => 'F1-F2',
'1.17' => 'E23-E24',
'Kern B Ost' => 'F19-F20, F13-F16',
'Kern B Empfang' => 'F21-F22',
'Kern B West' => 'G1-G4',
'Kern B Nord' => 'F5-F8, F11-F12',
'Abstellkammer' => 'F23-F24',
'1.22' => 'D7-D10',
'1.23' => 'D3-D6',
'1.24' => 'D1-D1, C24-C24',
'1.25' => 'C19-C22',
'1.26' => 'E21-E22',
'1.27' => 'E23-E24',
'1.28' => 'C9-C14, C17-C18',
'1.30' => 'C3-C8',
'1.31' => 'E17-E18',
'1.32' => 'E15-E16',
'1.33' => 'E13-E14',
'1.34' => 'C1-C2, B23-B24',
'1.35' => 'B19-B22',
];
foreach ($ss1Relations as $roomName => $socketRange) {
$sockets = expandSocketRange($socketRange);
foreach ($sockets as $socketLabel) {
$stmtConn->execute([$roomIds[$roomName], $socketIds['SS1'][$socketLabel]]);
}
}
$ss2Relations = [
'Kern C Center' => 'D20-D21',
'Kern C Nord' => 'D10-D13',
'Kern C West' => 'D14-D19',
'1.41' => 'C2-C9',
'1.42' => 'B19-B24',
'1.43' => 'B11-B18',
'1.44' => 'B4-B10',
'1.45' => 'B1-B3, A22-A24',
'1.46' => 'A14-A21',
'1.47' => 'A7-A13',
'1.48' => 'A1-A6',
'1.51' => 'C20-C21',
'1.52' => 'C16-C19',
'1.53' => 'C12-C15',
'1.54' => 'D8-D9',
'1.55' => 'D6-D7',
'1.56' => 'D4-D5',
];
foreach ($ss2Relations as $roomName => $socketRange) {
$sockets = expandSocketRange($socketRange);
foreach ($sockets as $socketLabel) {
$stmtConn->execute([$roomIds[$roomName], $socketIds['SS2'][$socketLabel]]);
}
}
$ss3Relations = [
'1.61' => 'B8-B13',
'1.62' => 'B1-B6',
'1.63' => 'A18-A24',
'1.64' => 'A12-A17',
'1.65' => 'A7-A11, B19',
'1.66' => 'A3-A6',
'1.68' => 'A1-A2',
'Kern D Nord' => 'B14-B17',
];
foreach ($ss3Relations as $roomName => $socketRange) {
$sockets = expandSocketRange($socketRange);
foreach ($sockets as $socketLabel) {
$stmtConn->execute([$roomIds[$roomName], $socketIds['SS3'][$socketLabel]]);
}
}
echo "Seeding completed successfully.\n";
он потом еще сделал проверочный файл check.php, хотя я не просил его
вроде я так понял, что мсп сервер не работает для подключения к sqlite, хотя он прописан и путь до папки тоже прописан или может модель слишком умная? я юзал опус 4.6 сейчас, раньше флеш 3 .1 делал и не умничал
<?php
$pdo = new PDO('sqlite:./db.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tables = ['buildings', 'floors', 'rooms', 'server_rooms', 'sockets', 'connections'];
foreach ($tables as $t) {
$cnt = $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
echo "$t: $cnt rows\n";
}
echo "\n--- Sample connections (first 10) ---\n";
$rows = $pdo->query("
SELECT c.id, r.name AS room, sr.name AS server_room, s.label AS socket
FROM connections c
JOIN rooms r ON r.id = c.room_id
JOIN sockets s ON s.id = c.socket_id
JOIN server_rooms sr ON sr.id = s.server_room_id
LIMIT 10
")->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
echo "#{$row['id']} Room:{$row['room']} | {$row['server_room']} | Socket:{$row['socket']}\n";
}
echo "\n--- Rooms missing from roomIds (not in connections) ---\n";
$missing = $pdo->query("
SELECT r.name FROM rooms r
LEFT JOIN connections c ON c.room_id = r.id
WHERE c.id IS NULL
")->fetchAll(PDO::FETCH_COLUMN);
foreach ($missing as $m) echo " MISSING: $m\n";
вроде как все правильно сделал, я так глянул мельком в бд, но прога что-то не дает открыть 2 таблицы и смотреть связи, сейчас спать пойду, но вижу в проверочном файле запросы какие-то мудреные, надо будет закинуть в прогу и посмотреть, что выдает
упд
отоспался, вот решил сразу проверить запрос в бд
SELECT c.id, r.name AS room, sr.name AS server_room, s.label AS socket
FROM connections c
JOIN rooms r ON r.id = c.room_id
JOIN sockets s ON s.id = c.socket_id
JOIN server_rooms sr ON sr.id = s.server_room_id
LIMIT 30
где я разрабатываю новые пду с последовательным включением всех розеток
там я описал алгоритм и методику калибровки всех реле, а тут я напишу, как я это вчера сделал при помощи гемини, но пока еще в железе не тестил, а то стол завален барахлом и надо еще принтер доделать и разгрести барахло
разработай двунаправленных протокол связи atmega328 и chrome browser over uart using arduino.
сделай web gui только в одном файле html, используй любые библиотеки онлайн cdn для работы с com port, charts, css framework, javascript. пиши только vanilla javascript.
основная идея: пользователь имеет web gui, где отображается кнопока запуска теста скорости замыкания контактов механического реле.
при нажатии на нее, в контроллер посылается команда, которая запускает процедуру тестирования времени срабатывания реле. контроллер запускает аппаратный таймер и сразу включает соответствующее реле, реле замыкает контакты и на одном входе контроллера появляется лог1, таймер останавливается и контроллер передает время в миллисекундах в браузер.
в интерфейсе должна быть возможность поставить галочку, которая включает циклический тест и количество циклов тоже задается контролом input type number 1-10.
если делался одинарный тест (галочка не стоит), то рисуется stacked column chart. ось x - номер теста, ось y - время срабатывания в миллисекундах
так же сделай кнопки, которая динамически на странице будет добавлять новый тест в дополнению к предыдущему, данные теста так же отображаются на общем графике новым цветом.
рядом с этой кнопкой сделай еще кнопку удаления этого теста
сделай кнопку save, которая сохраняет все данные в localstorage.
сделай кнопку delete для всех данных, которые удалятся из localstorage.
если тест делается циклический, то для него рисуется отдельный график, где каждая column показывает время в миллисекундах и они все такого же цвета, как column на общем графике. так же этот график подписан как test #x с указанием номера теста
у контрллера используй таймер1 и аппаратное управление выходом OC1A, который будет управлять реле, так же используй вход int0 для отлова изменения входа, когда замкнется реле и подаст на этот вход лог1. сделай минимально возможную задержку между запуском таймера и активацией вывода, который управляет реле, чтобы была минимальная погрешность в виде задержки, вносимой контроллером.
если есть вопросы - здавай сейчас, если нет, то напиши промпт для тебя самого, чтобы ты сделал в антигравити часть для контроллера, потом часть для браузера. или как тебе это проще сделать?
вот что он отвечает
Вопросов нет, задача предельно понятна и технически красива! Чистый аппаратный захват времени на AVR и современный Web Serial API в браузере — отличный стек для точных измерений.
Чтобы не разбивать логику и не заставлять тебя прыгать между сообщениями, я сделаю всё в одном этом ответе: сначала код для ATmega328 (Arduino Uno/Nano), а затем полноценный Web GUI в одном HTML-файле.
Для максимальной точности на стороне контроллера мы настроим Timer1 так, чтобы он запускался в режиме Toggle OC1A on Compare Match. Это позволит замкнуть реле аппаратно на микросекундном уровне в момент старта таймера, убирая программную задержку (jitter). Стоп-сигнал отловит прерывание INT0 (Pin 2).
Часть 1: Код для ATmega328 (Arduino IDE)
Прошей этот скетч в Arduino Uno/Nano. Реле подключается к пину 9 (PB1/OC1A). Обратная связь от сухого контакта реле (замыкание на VCC 5V с подтяжкой к GND резистором 10 кОм) заводится на пин 2 (PD2/INT0).
#include <Arduino.h>
volatile bool test_running = false;
volatile uint32_t total_cycles = 0;
void setup() {
// Настройка UART на скорость 115200
Serial.begin(115200);
// Пины: Pin 9 (OC1A) - Выход на реле, Pin 2 (INT0) - Вход от контакта
pinMode(9, OUTPUT);
digitalWrite(9, LOW);
pinMode(2, INPUT); // Важно: нужен внешний стягивающий резистор 10к к GND
// Настройка Timer1
// Режим Normal, но настраиваем COM1A1:0 так, чтобы при старте аппаратно взвести пин 9
TCCR1A = 0;
TCCR1B = 0;
// Настройка внешнего прерывания INT0 на возрастающий фронт (Rising Edge)
EICRA = (1 << ISC01) | (1 << ISC00);
}
// Обработчик прерывания INT0 (Замыкание контакта)
ISR(INT0_vect) {
if (test_running) {
// Сразу останавливаем таймер (сбрасываем CS биты)
TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));
uint32_t ticks = TCNT1; // Читаем счетчик
// Переводим тики таймера в миллисекунды.
// При прескейлере 64: 1 тик = 64 / 16000000 сек = 4 мкс.
// Время в мс = (ticks * 4) / 1000 = ticks / 250
float duration_ms = (float)ticks / 250.0;
// Сбрасываем пин OC1A в LOW программно для безопасности
TCCR1A = 0;
digitalWrite(9, LOW);
test_running = false;
EIMSK &= ~(1 << INT0); // Выключаем прерывание INT0
// Отправляем результат в Web GUI
Serial.print("RESULT:");
Serial.println(duration_ms, 3); // 3 знака после запятой
}
}
void floatStartTest() {
if (test_running) return;
digitalWrite(9, LOW); // Убеждаемся, что выход в нуле
TCNT1 = 0; // Сбрасываем счетчик таймера
test_running = true;
// Очищаем флаг прерывания INT0 на случай дребезга до теста
EIFR = (1 << INTF0);
// Разрешаем прерывание INT0
EIMSK |= (1 << INT0);
// Аппаратный запуск:
// 1. Настраиваем COM1A1 и COM1A0 в 1 (Set OC1A on Compare Match)
// 2. Записываем в OCR1A единицу (или 0), чтобы совпадение произошло мгновенно
OCR1A = 1;
TCCR1A = (1 << COM1A1) | (1 << COM1A0);
// 3. Запускаем таймер с прескейлером 64 (1 тик = 4 мкс, макс. время ~262 мс)
// Если реле медленнее 260 мс, нужен прескейлер 256.
TCCR1B = (1 << CS11) | (1 << CS10);
}
void loop() {
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd == 'START') {
floatStartTest();
}
}
}
Часть 2: Web GUI (index.html)
Сохрани этот код в файл index.html. Для работы с COM-портом (Web Serial API) открывай этот файл в браузере Google Chrome или Edge (работает через протокол http:// или просто при открытии локального файла file:/// в современных версиях Chrome).
Использованы библиотеки: Tailwind CSS (стили), ApexCharts (для красивых stacked column и циклических графиков) и иконки FontAwesome.
превзошел все ожидания
я бы такой гуй часамы бы рисовал и то у меня в башке не было четкой картины, чего там куда поставить
весь гуй занимает 431 строку кода с каментами, а в контроллере сделал минимум кода
осталось только проверить все это
при помощи звуковой карты можно передавать данные в контроллер или даже заливать прошивку, если предварительно туда прошить 2кб загрузчик
еще тогда я подумал, а можно ли сделать такой хитрый передатчик из готовых радиомодулей дешевых, чтобы его подключить не в усб, не в ком порт, а просто к выходу звуковой карты и чтобы питание он тоже получал от звуковой карты
я провел небольшое исследование (как это модно сейчас писать) и выяснил кое какие детали, которые я не буду тут писать и копипастить весь чатик тоже не буду (было бы неплохо иметь возможность прицеплять пдф файлы)
туть такова:
перед передачей, в одном канале звука запускается генерация прямоугольного сигнала на 1кгц
к этому каналу подключен вход умножителя напряжения на диодах и конденсаторах, к выходу умножителя подключен конденсатор на 1000мкф и сам передатчик
конденсатор заряжается примерно за 1сек до напряжения около 3.3в, при котором работает передатчик
потом во второй канал подается частоты 1200 и 2200гц, которые кодируют 0 и 1
вот портянка с заданием, которое сам подготовил гемини для себя, которое будет запущено малость потом
теперь можно использовать любой компутер, не нужен драйвер никакой, прога будет на плюсах - не нужны библиотеки никакие + браузер можно использовать
на приемной стороне будет контроллер, а я это все задумывал для цифровых табло формата 88:88 + пищалка + вывод на ргб адресные светодиоды (есть китайские большие часы такие, там только плату свою надо сделать)
часы эти должны были быть часть одной системы, которую я собирался пилить, но не запилил (не осилил бы я тогда в одно рыло столько задач разных)
часы должны были показывать оставшееся время до конца аренды помещения и пищалкой извещать скорый конец
ви таки спросите, чего не приделал есп8266? я про это тоже думал, но они не очень хорошо ловят сигнал, а на 433мгц он лучше проходит через стены
Техническое задание: Радиомодемный комплекс со звуковым питанием (AFSK + Накачка 1 кГц)
1. Общее описание комплекса
Необходимо реализовать систему беспроводной передачи текстовых данных, состоящую из передающего программного комплекса (ПК под Windows 10+) и аппаратного приемника на базе микроконтроллера. Для беспроводной связи используется пара радиомодулей ASK/OOK 433 МГц (STX882 на передаче и SRX882 на приеме на базе чипа PT4303).
Ключевые особенности аппаратной интеграции и цепи питания:
Паразитное аудио-питание: Передатчик STX882 потребляет 34 мА при 3 В и не имеет внешнего источника питания. Он запитывается от энергии звуковой карты ПК через диодный умножитель напряжения на диодах Шоттки.
Конфигурация выходов звуковой карты (Стерео, 44100 Гц):
Канал 1 (Левый) — Данные: Выводит звуковой сигнал, модулированный по принципу AFSK. Наличие звуковых колебаний заставляет передатчик излучать несущую частоту 433 МГц.
Канал 2 (Правый) — Питание (Накачка): Непрерывно генерирует меандр (Square Wave) частотой 1000 Гц с максимальной амплитудой. Благодаря высокой частоте (1 кГц) промежуточные конденсаторы умножителя уменьшены до компактных 47–100 мкФ (допустимо SMD исполнение). Накопительный буферный конденсатор имеет емкость 1000 мкФ (обеспечивает автономный ход до ~61.8 мс при полном падении подзарядки от 3.3 В до минимально рабочих 1.2 В).
Режим работы накачки: В фазе предварительной зарядки (параметр charge_time_ms) правый канал уже генерирует 1000 Гц, а левый молчит. В фазе передачи данных правый канал продолжает непрерывно выдавать 1000 Гц, подпитывая буферную емкость в процессе трансляции.
2. Спецификация протокола передачи (AFSK)
Для обеспечения 100% DC-баланса, удержания уровня встроенного дата-слайсера (компаратора) PT4303 и обхода ограничений звукового тракта, передача кодируется частотной манипуляцией (AFSK) на чистых синусоидах:
Преамбула (Preamble): Чередующаяся последовательность бит 01010101... длительностью 30–50 мс. Необходима, чтобы адаптивный компаратор (Data Slicer) чипа PT4303 настроил внутреннее опорное напряжение и надежно заблокировал белый шум эфира.
Стартовый маркер (Sync Word): Уникальный синхробайт 0xB4 для точной синхронизации начала полезной нагрузки.
Полезная нагрузка (Payload): Текст, переведенный в битовый поток.
Контрольная сумма (CRC8): 1 байт в конце пакета для валидации данных на приемнике.
3. Требования к передатчику (Windows 10+, C/C++)
3.1. Технологический стек и интеграция
Код пишется на C/C++ (стандарт C++17 или новее) для сборки в актуальной версии MS Visual Studio (тулсет v143).
Вывод звука реализуется строго через WASAPI (Windows Audio Session API) в режиме Shared Mode. Использование устаревшего WinMM (waveOut*) запрещено.
При генерации кода необходимо использовать MCP-инструменты (fileserver) для автоматического создания структуры файлов (main.cpp, wasapi_io.cpp/.h, modulator.cpp/.h).
3.2. Интерфейс командной строки (CLI)
Запуск из консоли со следующими параметрами: sndtx.exe [-h | --help] [-d <device_index>] [-t <charge_time_ms>] <текст_или_путь_к_файлу>
Флаги -h или --help: Выводят справку и список всех доступных в системе аудиоустройств вывода через WASAPI (IMMDeviceEnumerator) с их индексами.
Флаг -d <device_index>: Выбор индекса звуковой карты (по умолчанию — устройство вывода по умолчанию).
Флаг -t <charge_time_ms>: Время предварительной накачки конденсатора в миллисекундах (по умолчанию 1000 мс).
4. Требования к приемнику и алгоритмам защиты от помех
Радиомодуль SRX882 выдает на контакте DATA восстановленный цифровой меандр. Приемник на стороне МК считывает длительность периодов/полупериодов с помощью аппаратного таймера и внешних прерываний.
4.1. Встроенные программные фильтры защиты от помех:
Фильтр коротких импульсов (Защита от глитчей/искровых помех): При возникновении прерывания код проверяет интервал времени. Если дельта составляет меньше 300 мкс, данный замер отбрасывается как ложный импульс (шум), так как он физически быстрее самого короткого валидного периода логической единицы ($416.6\text{ мкс}$).
Фильтр пропадания сигнала (Таймаут конца пакета / тишины): Если на входе нет прерываний в течение более 1500 мкс, алгоритм считает, что передача завершена или сигнал потерян. Внутренний сдвиговый регистр сбрасывается, декодер возвращается в режим ожидания Преамбулы.
Фильтр ложного захвата частоты (Диапазонные ворота): Попадание в логические уровни должно иметь жесткие допуски:
Интервал в рамках 350–480 мкс $\rightarrow$ распознается как бит 1.
Интервал в рамках 750–950 мкс $\rightarrow$ распознается как бит 0.
Любые другие значения между 480 и 750 мкс трактуются как ошибка декодирования бита, переводящая систему в ожидание новой преамбулы.
4.2. Вариант А: Прошивка для ATmega328 (Arduino Nano / Uno)
Написано на чистом AVR-C (регистры, без Arduino-библиотек).
Входной сигнал заведен на пин INT0 (Pin D2), прерывание настроено по переднему фронту (Rising Edge).
Для замера времени используется 16-битный Timer1.
При успешном приеме пакета и валидном CRC8, строка данных отправляется в аппаратный UART на скорости 115200 бод.
4.3. Вариант Б: Прошивка для CH32V003 (RISC-V, 48 МГц)
Написано под стандартный тулсет MounRiver Studio / GCC RISC-V с использованием официальной библиотеки периферии (SPL).
Обработка входящего меандра настроена через прерывание EXTI на одном из GPIO.
Для высокоточного замера временных интервалов используется аппаратный таймер TIM1 или TIM2.
Вывод принятых текстовых пакетов осуществляется через встроенный периферийный узел USART.
5. Ожидаемый результат работы ИИ-агента в Антигравити
Используя MCP-инструменты файлового сервера, ИИ должен поочередно создать в текущей рабочей директории:
Структуру файлов C++ проекта для Windows (полностью готовый код для сборки в VS 2022).
Автономный файл прошивки receiver_atmega328.c.
Автономный файл прошивки receiver_ch32v003.c.
я еще заказал ргб панельку светодиодов для подсветки компутера изнутри с эффектами всякими, только плашку я повешу над дверью у нас в конторе и подключу к тасмоте
это как раз индикаторный свет для тех, кто хочет зайти в админскую комнату
чтобы управлять светом - я спросил гемини, как пишется плагин для браузера и просил рассказать, что там за файлы и за что отвечают
ну он мне накидал теорию
я хочу сделать плагин, чтобы было видно цвет в квадратике на панели браузера
браузер будет делать запросы к есп8266 и спрашивать статус
при нажатии на цветной квадратик там еще выпадают зеленый и желтый и можно сменить цвет быстро и в других браузерах тоже поменяется, т.к. они тоже опрашивают есп8266
или может быть там есть какой-то другой путь отправки браузеру уведомления? надо будет это тоже спросить, чтобы не дергать постоянно контроллер, а то он может зависнуть от большой нагрузки
я потом сделаю схему и плату с есп8266, чтобы туда можно было подключать максимальное количество цветов в виде отдельных светодиодных фонарей в башнях, как с общим плюсом, так и с общим минусом + адресные диоды, т.к. китайцы продают разные светильники и туда можно воткнуть разные готовые платы с адресными диодами вс8212 - есть круглые платки, которые можно воткнуть в матовые лампочки, есть большие лампы для кемпинга, куда можно воткнуть матрицу 8х8 диодов, правда она вроде будет жрать столько, что надо будет ей резинку и радиатор делать
так же будет выход шим для подключения пищалки, которая будет генерить звуки разной частоты и длительности
вот столько у меня проектов завалялось, но благодаря ии я могу их все делать потихоньку
с утра посмотрел ролик про звуки в виндовсе и там автор говорит, что типа звуки запилили, чтобы компутер был как бы похож на привычные штуки в офисе, поэтому даже при очистке корзины был звук сминаемой бумаги
а я всегда эти звуки отключал первым делом, потому что рабочий инструмент не должен издавать никаких звуков
у вас же электроотверта не пиликает, когда вы ее берете в руки?
потом автор сказал, что типа модем тоже издает звуки набора номера, ведь он же звонит как телефон
и тут я понял, что автор хоть и занимается звуком, но понятия не имеет о работе телефонии
сделано так, чтобы в разговоре человек не смог воспроизвести сразу 2 частоты, чтобы не было сбоев и если неточность по частоте больше 1.5%, то тон считается недействительным
ну так вот, я потом подумал и вспомнились мне пейджеры, точнее биперы для начала, для простого вызова персонала, а потом прикрутили к ним номера, а потом уже и текст, что сделало пейджеры с селективным вызовом
потом я вспомнил про селективный вызов в радиостанциях, который вы точно слышали в играх и фильмах
больше всякой терории
вобщем zvei это система 5 тоновой кодовой посылки для всяких служб, где применяется селективный вызов
так же она может запускать сирены оповещения, а 3я версия расширила частоты и применяется еще на больших предприятиях типа электростанций для вызова персонала
есть вот аткая таблица частот для кодирования цифр
набираеш код из 5 цифр и двумя руками зажимаеш 2 красные кнопки
вся эта байда работает в аналоговом режиме в звуковых каналах аналоговой связи и была давно еще придумана
а к чему я это все?
к тому, что в прошлом посте я писал о цифровой передаче, где мы тоже использует 2 частоты для кодирования нуля и единицы и тоже в звуковом диапазоне, потому что такие радиомодули не позволяют работать на высоких частотах, т.к. это расширяет спектр радиочастоты, что требует энерегетических затрат, но мощность там ограничена и если расширить спектр, то амплитуда упадет, а значит упадет и дальность
поэтому для повышения дальности связи делают узкополосные системы narrow band
есть даже сотовые модемы nb-iot или же специальные сложные модуляции
на что похож звук? примерно так матерится робой из зведных войн
нужна будет такая пукалка за 27 евров, всю деревню покроет точно
в предверии чебурнета - вполне себе норм решение
гемини говорит еще, что можно купить готовые платы на базе есп32 + лора модуль на 433мгц
тогда можно все это запилить в контроллере есп32, дергать время по сети и передавать его автоматом и сообщения передавать тоже без звуковой карты, правда нужен будет усилитель мощности и наверное перепаять конденсаторы и катушки под нужную частоту
получится автономная базовая станция с доступом по сети и вебмордой, компутер не нужен как бы
вспомнил, что еще когда у меня был этот скайпер, то он время ловил (тогда еще работали базовые станции, сейчас вроде совсем загнулось все)
решил узнать, каким макаром это реализовано, потому что я помню, что на уровне протокола такого не было
вот допилил - 416 строк, флеш 3.5 гемини справляется норм
возвращаемся к посту про передатчик для звуковой карты
Техническое задание: Радиомодемный комплекс со звуковым питанием (AFSK + Накачка 1 кГц)
сегодня я его сделал и выюзал опять недельный лимит гемини + еще опус половину
на с++ я первый раз делаю, так что возни много было и я к тому же забыл уже все, как там проекты настраивать и все такое
сразу скажу, что таки файл создался, гемини накатал свой велик на почти 500 строк для работы с виндовс аудио
WasapiAudioEngine вглядит как-то слишком сложно, столько кода реально что ли нужно было писать?
Да, для работы с WASAPI в Windows действительно требуется написать много шаблонного (boilerplate) кода. Это связано с низкоуровневой природой API, основанной на COM (Component Object Model).
Основные причины, почему кода получилось немало:
Многоступенчатая инициализация COM и устройств:
Нужно создать перечислитель (IMMDeviceEnumerator).
Получить список устройств (IMMDeviceCollection) или устройство по умолчанию.
Активировать интерфейс IAudioClient.
Получить сервис рендеринга IAudioRenderClient.
Согласование форматов (Shared Mode):
Поскольку мы работаем в монопольном или общем режиме (здесь строго Shared Mode), Windows диктует формат звука (микшер Windows).
Разные звуковые карты и настройки пользователя могут возвращать разные форматы: 32-bit Float, 16-bit PCM, 24-bit PCM или 32-bit PCM. Наш код обязан проверять, какой формат вернул GetMixFormat, и приводить наши нормализованные float значения к типу данных звуковой карты на лету. Без этой логики программа будет падать или зависать на других ПК.
Собственная буферизация:
В WASAPI мы не можем просто сказать «проиграй этот большой буфер». Мы должны постоянно запрашивать размер свободного места в кольцевом буфере звуковой карты (GetCurrentPadding), заполнять ровно столько кадров, сколько свободно, и спать/ждать, пока звуковая карта их воспроизведет.
Требуется логика ожидания окончания проигрывания в конце передачи (иначе звук обрежется на полуслове при закрытии программы).
Альтернативы
Если бы мы использовали WinMM (waveOutWrite), кода было бы в 3-4 раза меньше, но эта технология считается устаревшей (deprecated) со времён Windows Vista, имеет высокую задержку и нестабильность. Для современных C++ приложений под Windows стандарт разработки требует использовать именно WASAPI.
а весь этот код уже есть в какой-то сторонней библиотеке?
Да, безусловно. Поскольку писать такой низкоуровневый код с нуля для каждой платформы долго и сложно, существует множество популярных библиотек (как небольших, так и крупных фреймворков), которые скрывают всю эту рутину внутри себя и предоставляют простой API.
Вот основные варианты:
1. Специализированные аудио-библиотеки (C/C++)
Miniaudio (наиболее рекомендуемая для небольших проектов)
Особенность: Это библиотека из одного заголовочного файла (miniaudio.h). Она весит около 1-2 МБ, не требует сборки и сложных настроек линкера.
Под капотом: Сама инициализирует WASAPI (в Windows), CoreAudio (в macOS/iOS), ALSA (в Linux) и делает конвертацию форматов автоматически.
Как выглядел бы код: Вы просто передаете функции обратного вызова (callback) свои данные, а библиотека сама заботится о буферах.
RtAudio
Популярная легковесная C++ библиотека для кроссплатформенного ввода-вывода звука. Часто используется в научных и академических проектах.
PortAudio
Одна из самых старых и проверенных библиотек. Используется во множестве проектов (например, в редакторе Audacity).
2. Игровые и мультимедийные фреймворки
Если вы делаете проект, где кроме звука есть графика или интерфейс, часто используют встроенные аудио-движки:
SFML (SFML-Audio) — очень простой C++ фреймворк. Воспроизведение кастомного буфера в реальном времени там занимает буквально 20-30 строк кода через класс sf::SoundStream.
SDL (SDL_Audio) — классическая библиотека для разработки игр, также содержит кроссплатформенную абстракцию над аудио-драйверами.
Как изменился бы наш проект с такой библиотекой (например, miniaudio)?
Вместо 440 строк в [wasapi_io.cpp](file:///d:/projects/prog/cpp/soundmodem/sound-tx/wasapi_io.cpp), работа со звуком заняла бы около 30-50 строк кода. Нам не пришлось бы вручную писать конвертеры sample-форматов (из float в int16/int32/int24) и обрабатывать системные структуры данных Windows COM.
я miniaudio не заюзал, а взял rtaudio, потом переделаю может быть
если изначально гемини накидал 500 строк для работы с аудио и скомпилил, то было 74к ехе релиз
когда же я подключил библиотеку и вкомпилил в ехе, то он стел весить 374к
помимо этого я еще включил там, чтобы рантайм тоже был в файле, иначе на каком нибудь виндовсе пришлось бы ради этого ставить еще райнтайм с++ библиотеки
пожертвовал размером ради портативности, но мне очень понравилось
я теперь юзаю антигравити не иде, а просто антигравити 2, у которго белая круглая иконка и это типа тоже как чатик в браузере, только еще может файлы трогать и не тормозит
теперь осталось создать приемную часть
кстати я прочитал вчера его портянку и понял, что там все в кучу навалено и сказал поделить их на 3 части - для виндовса передатчик и приемник для меги 328 и для ch32v003
и все равно они там требуют допиливания человеком, так что без понимания того, как и что конкретно должно работать - код нормальный вы врят ли сделаете
как токены снова вернутся, то я попробую юзать flash 3.5 low, вроде он не так сильно жрет токены, по крайней мере в браузере я даже 5 часовой лимит не переходил больше 15% и то при генерации картинки, асейчас я юзал высокий уровень и весь его выюзал
продолжаем ковырять тему передачи цифровых данных через аудиокарту
сейчас я заюзал для теста haiku 4.5 и отключенный Deep reasoning - модель вообще тупая и бесполезная, даже не знает где в ее проге какие настройки, не видит свой же плагин для работы с локальными файлами пока ее не пнеш
вот простое сравнение (новый чат)
какова скорость передачи магнитофонного интерфейса у компьютера zx spectrum?
Я не знаю точную скорость передачи магнитофонного интерфейса ZX Spectrum.
ZX Spectrum действительно использовал кассетный магнитофон для загрузки программ, но я не уверен в конкретных технических характеристиках этого интерфейса (битрейт, частота дискретизации и т.д.).
Если тебе нужна эта информация, рекомендую:
Поискать в технической документации ZX Spectrum
Посмотреть на сайтах, посвященных истории компьютеров (например, на тематических вики или форумах ретро-компьютеров)
Проверить исторические источники о протоколе загрузки с кассет
Это тебе нужно для какого-то конкретного проекта или просто из интереса?
переключаем на соннет 4.6 low в новый чат
ZX Spectrum использовал магнитофонный интерфейс со скоростью около 1500 бод (baud).
Конкретнее:
Скорость загрузки стандартного ROM-загрузчика — примерно 1500 бит/с
Один байт кодировался двумя тонами: 0 — короткий импульс (~855 мкс), 1 — длинный (~1710 мкс)
Реальная скорость передачи данных — около 1500 байт в минуту (примерно 25 байт/с)
Были и нестандартные загрузчики (Turbo-загрузчики), которые разгоняли скорость до 3000–6000 бод и выше, но они уже не совместимы со стандартным ROM-загрузчиком.
sonnet max
Магнитофонный интерфейс ZX Spectrum работает со скоростью 1500 бод (baud).
Точнее:
Короткий импульс (0) — 855 тактов процессора (~356 мкс)
Реальная скорость загрузки данных — примерно 1500 бит/с, то есть около 187 байт/с.
Для сравнения, стандартный блок данных (Basic-программа или код) грузился со скоростью примерно 1500 бод, а сам процесс загрузки 48KB занимал порядка 5 минут.
Формат записи — FSK (частотная манипуляция): две частоты, соответствующие коротким и длинным импульсам.
вот этот ответ довольно хороший, т.к. коротко, ясно + формат запись фск, что очень важно для понимания всего этого дела
самое смешное, что они путают 855 тактов и микросекунды
а теперь гемини флеш 3.5
Скорость передачи данных через магнитофонный интерфейс у классического ZX Spectrum составляет в среднем 1500 бод (приблизительно 1500 бит/с или около 185 байт в секунду).
Однако этот процесс устроен довольно интересно, и реальная скорость немного колеблется в зависимости от записываемых данных.
Как кодировались данные?
В ZX Spectrum использовалось частотное кодирование (тональные сигналы разной длительности). Поскольку компьютер управлял сигналом напрямую через процессор Z80, каждый бит формировался из полных циклов колебаний:
Логический «0»: состоит из двух полупериодов по 855 тактов процессора каждый (общая длительность — ~489 мкс).
Логическая «1»: состоит из двух полупериодов в два раза длиннее — по 1710 тактов (общая длительность — ~977 мкс).
Из-за того, что «единица» звучит в два раза дольше «нуля», реальная скорость загрузки зависела от содержимого файла. Если в файле было много единиц, он грузился медленнее, если много нулей — быстрее.
Структура записи на кассете
Полный процесс загрузки блока данных (например, знаменитый “скрипучий” звук) состоит из нескольких фаз:
Пилот-тон (Pilot tone): Длинный постоянный гул (синий/красный цвет на экране). Нужен для того, чтобы спектрумовский ROM настроился на частоту и уровень сигнала.
Синкроимпульс (Sync pulse): Очень короткий сигнал, который говорит компьютеру: «Внимание, сейчас пойдут байты!».
Полезные данные (Data): Сами байты программы (чередующиеся полоски на бордюре).
Контрольная сумма (Checksum): Последний байт, который сверяется с посчитанным во время загрузки. Если они не совпадали, выдавалась знаменитая ошибка R Tape loading error.
Турбо-загрузчики (Turbo Loaders)
Стандартная скорость в 1500 бод заставляла игроков ждать загрузки средней игры (около 40–48 КБ) по 4–5 минут.
Чтобы ускорить этот процесс, сторонние разработчики игр начали создавать свои собственные, кастомные процедуры загрузки — турбо-загрузчики (например, Speedlock). Они уменьшали длительность полупериодов для «0» и «1», за счет чего скорость поднималась до 3000–4000 бод, сокращая время ожидания до 1.5–2 минут. Однако такие записи требовали очень качественных магнитофонов и хорошей плёнки.
тут тоже довольно интересный ответ с новой инфой, но нет инфы о фск
кстати для справки - фск 2х тональная была разработана очень давно bell labs, использовалась в первых модемах и для нее характерно чередование нулей и единиц, что называют преамбулой, чтобы приемное устройство (система автоматической регулировки усиления) настроилась на уровень сигнала (а в магнитофонах как раз она тоже была)
в пейджерах почему так долго одна батарейка работала? потому что приемник там не был включен постоянно, а контроллер каждые 300мс вроде подавал питание, приемник включался, контроллер смотрел на его выходе колебания напряжений логических уровней и понимал, что идет передача или же передачи нет
при включении приемника в нем некоторое время идут переходные процессы, поэтому у пейджеров была длинная преамбула
в процессе изучения измерителя уровня аудиосигнала я натыкаюсь на непонятные куски кода в чужом проекте и спрашиваю гемини про все это и вот застрял на цифровом фильтре, который убирает постоянную составляющую на входе ацп
не буду вдаваться в подробности, а то уже глаз закрывается и спать пойдут, но вот что нарисовал гемини в качестве симулятора, хотя я сначала попросил его нарисовать график, но он рисовал текстовыми символами и там было не очень, потом нарисовал в свг и я подумал, что было бы неплохо, если он вообще сделает симулятор
мой индикатор стрелочный тоже будет иметь прогу для настройки баллистики стрелки и там тоже есть фильтр и коэффициенты
следущий проект будет типа этого - тут типа плеер аудио и фильтры в виде эквалайзера с графиками такими
именно это я и хотел сделать, но быстро нашел готовое + я у гемини спрашивал, как вообще делаются красивые интерфейсы такие, особенно для аудиософта, а он говорит, что типа есть фреймворк на с++ как раз для разработки аудиософта, но можно делать и на хмтл при помощи webview2 компонента, который есть в виндовсе по дефолту
а я тогда его спрашиваю, типа это же кучу памяти будет жрать, если это по сути движок хрома и он согласился и сказал, что есть альтернативные движки хтмл5
так что можно сначала сделать хтмл, отладить его и потом затолкать в ехе, где есть микродвижок браузера яваскрипта, но нет разных плюшек, что затолкали в обычные браузеры и они по сути не нужны в приложениях
а зачем мне приложение такое, спросите вы?
для светомузыки
а что такое свтомузыка? это фильтры
а тему цифровых фильтров я уже начал, причем плавно так и даже не знал, что это фильтры пошли, потому что гемини мне уже выдал формулу раньше, когда я с ним катал вату по поводу баллистики стрелки и как бы так ее сделать, чтобы стрелка вперед шла с одной скоростью, а обратно с другой
оказывается там тоже есть фильтр и коэффициенты и формула похожая, а я фильтры я еще начал давно изучать, но не осилил ролик до конца и с тех пор забил
и вот настало опять время, но теперь уже нейросети могут объяснить эту тему и даже сделать симулятор
упд
вот еще сделал симулятор частотных фильтров
пришлось повозиться, пока гемини допилил
эти серые кнопки со стрелками сильно выбиваются из дизайна и в оригинале они тоже серые, даже если включить темную тему на сцайте
пару недель назад я делал при помощи гемини тестировщика времени срабатывания контактов реле
сейчас смотрел опять ролики про ии и в ролике узнал про другую модель, которая подешевле и типа тоже код может писать и я ее попробовал.
клепаю новую версию тестировщика
как раз закончился мой лимит, которого хватило прилично так, если учесть, что каждый раз генерится порядка 550 строк кода
конечно, модель туповата наверное по сравнению с гемини 3.5 и токены быстро кончились в чатике, а ведь в гемини я даже дневной лимит не достигал ни разу.
кстати по поводу лимитов, почему в иде они так быстро уходят
оказывается агент в иде шлет огромный запрос скрытно, типа системный промпт называется и получается так, что 1 простой запрос сжирает порядка 12к токенов, а потом они орут, что типа квоты урезали
по мне так системные промпты слишком жирные и что самое странное - я нигде не нашел мануала по выпиливанию их полностью, а так же с гемини работает только cline плагин для вс кода
вобщем пришлось опять в чатик к гемини перейти и доделывать
и таки доделал
поясню еще раз, в чем цимес
браузер подключается к контроллеру и отправляет команду на запуск теста
контроллер получает команду, запускает таймер, таймер сразу включает реле
контакты реле замыкаются и они подключены к специальному входу контроллера и когда там появляется лог0, то таймер останавливается
в регистре таймера есть некое число тиков, которое пересылается в браузер, браузер пересчитывает его в миллисекунды
как работает гуй? я попросил добавить еще режим симуляции, если галочку включить и там генерится случайное число тиков, как будто бы их контроллер шлет
есть 2 вида тестов - одинарный и циклический с заданным количеством тестов, после чего высчитывается среднее время и рисуется график в основном поле и количество графиков в отдельном окне для циклического теста
циклические тесты можно пропускать
можно делать до 15 тестов, чтобы они влезли в окно без прокрутки
можно удалять любой тест и потом снова добавить - он добавится в конце справа тем же цветом, каким был
код имеет 573 строки
в этот раз сделано не tailwind css, a bootstrap 5
я подумал, что будет интересно кому посмотреть код такого достаточно сложного приложения
упд. переписал на tailwind, заставил его сделать более интересный дизайн и чтобы он сам его загуглил, а то переписать-то он переписал, а дизайн старый остался
модель туповата еще и не видит довольно большого контекста или же логических связей
все расписывать лень, но кодить можно и оно даже показывает, что будет делать и что сделано
в дизайне особо понравилось вон те цветные левые стороны на обертке каждого теста
лимит на 5 часов опять вышел и еще быстрей, т.к. я поставил десктопное приложение, но оно очень странно тоже работает и жрет токены, хотя показывает только измененные строчки, а в чатике генерил каждый раз полный код и работал я куда дольше
вобщем интерфейс топ вышел, не зря токены извел
надо было изначально указывать ему tailwind, а то он взял бутстрап
шо це опять таке?
очередная давнишняя моя идея сделать светомузыку с настраиваемыми полосовыми фильтрами, добротностью фильтров, фоновым каналом и стробоскопом, а так же компрессорами на каждый канал (гемини мне тоже это сказал)
краткая предыстория
еще когда я был тупым школьником, то узнал про светомузыки из журнала радио и книжек и даже посчастливилось мне заиметь 3 стеклянных колпака от сду, только шар матовый разбили, но и так норм было
собрал я значит фильтры, лампочки по 40вт и боле менее норм было, особенно снаружи это круто смотрелось, когда на подоконнике снега навалило полокна и стекло еще замерзло и получился такой матовый экран
один раз даже мужик зашел, просил продать, но я не продал (потом в школу отнес)
потом я еще пробовал делать на галогеновых прожекторах и пленочных светофильтрах, но они расплавились, да и свет так себе был, хоть и 150вт лампы
был я еще в 2016 на стекольном заводе, думал обрезки стекла цветного дадут нахаляву, а они даже обрезки и битые куски продавали и все они были слишком мелкие, а нарезать нужные - это вроде было евров под 100 и я забил тогда, а прожекторы продал
и вот году в 2023м нашел я наконец-то светодиоды годные с люминофором, а не просто голыми кристаллами, которые дают противный свет, очень такой насыщенный и неестественный
поэтому добавляют люминофор, чтобы спектр чуть расширился и чтобы зеленый был не слишком зеленым и синий не слишком синим
светодиоды эти по 30вт, что хватит на освещение комнаты в хрущобе, правда надо 90 вольт драйвер, но я их купил тоже, но еще не запускал
драйвер без регулировки, что очень плохо, поэтому пришлось купить другие и им надо на вход 24в или даже 32
хо тя видел еще на али отдельные драйвера со входом 230в и входом регулировки яркости и на нужное напряжение, но дороже купленых в 2 раза уже
вобщем фонари топ, надо только светодиоды воткнуть туда и повесить на потолок
частотное разделение каналов
Низкие частоты (НЧ) — Бас, ударные
Диапазон: от 20 Гц до 300 (или 400) Гц.
Цвет свечения: Красный.
Средние частоты (СЧ) — Вокал, гитара, клавишные
Диапазон: от 250 (или 300) Гц до 2500 (или 3000) Гц.
Цвет свечения: Зеленый.
Высокие частоты (ВЧ) — Тарелки, высокие ноты синтезатора
Диапазон: от 2000 Гц и выше (до 10 000–15 000 Гц).
Цвет свечения: Синий.
сразу скажу, что 95% схем с фильтрами на транзисторе или вообще пассивные - это плохие фильтры
годные фильтры делаются на нескольких оу или же лц контуре
единственный, кто додумался до этого в ютубе
так же там применен компрессор, хотя писали, что он нужен для ламп, но как оказалось - и для светодиодов тоже
кстати у автора этого светидоды как раз с люминофором как у меня, только они квадратные, а у меня в фонарях круглые были белые, поэтому долго искал круглые и с люминофором
вобщем нам нужен какой-то мощный процессор, который может обрабатывать 5 каналов, пересчитывать коэффициенты частоты и добротности фильтра, а так же реализовывать компрессор
ну и тут мне как-то попадается случайно дсп для звука ADAU1701 (кстати ему 20 лет уже где-то)
он имеет 2 стерео цапа, чего хватает только на 4 канала, а стробоскоп надо делать на простом выводе, ибо он должен просто включать светодиод на всю яркость
и чем же он крут? он крут не сам по себе, ибо тысячи их, а софтом
в нем можно визуально соединять блоки всякие и этих блоков влазит туда приличное такое количество и все эти ручки можно крутить из софта, а через специальный программатор настройки уходят в дсп
можно припаять еще пзу и сохранять их там
зачем это все нужно? для аудиофильства - при помощи измерительного микрофона и комплекса софта снимается ачх помещения, где будет сидеть слушатель (прям в его кресле)
при помощи эквалайзера делается так, чтобы ачх была ровная и вот этот эквалайзер и реализуется в дсп, настройки записываются в пзу и все
еще делаютдсп для машин, типа кто хочет в тачку себе топовый звук и вот тут я еще понял, что менять значения фильтров можно аппаратными крутилками
потом я узнал, что еще можно подключить вместо программатора свой контроллер по i2c шине и так же менять значения крутилок
получается так, что я могу сделать софт, который будет менять частоты фильтров и ширину полосы, а так же сделать 4 компрессора на каждый канал, ибо делать это в схеме очень дорого и поэтому все аналоговые светомузыки имели только 1 общий компрессор, но и с ним было круто
катали мы вату с гемини и он мне и говорит, типа зачем тебе дсп, если можно на контроллере сделать, а я как раз и хотел его спросить про есп32 и он мне как раз и говорит название модуля, а то есп32 есть куча разных и тут оказывается, что у меня такая плата есть
тогда я его и прошу составить портянку, чтобы наклепать гуй предварительный и оказалось так, что он не просто контролы просил сделать, а реальные фильтры и захват аудио с микшера
я настолько проперся с результатов, что решил выложить даже этот прототип в виде ролика
кстати 1640 строк кода, я побил все рекорды свои
ролик получился мыльный, уж не разбираюсь я в этих настройках
желтый канал может работать как обычный фильтр средне-высоких частот, а может как фоновый, когда при отсутствии сигнала он просто светится на заданной яркости, а когда горит зеленый, то притухает
насколько притухать - там второй регулятор определяет
белый канал - стробоскоп - он тоже малость криво работает
там не должо быть белого индикатора уровня, только горизонтальная полоска на общем графике, которая задает порог срабатывания
регулятор частоты настраивает этот фильтр на нужную частоту ритмового инструмента, а время длительности вспышки тоже настраивается, причем если сигнал длиться дольше установленного времени, то белый надо выключить и ждать, пока уровень спадет
но эта функция пока не работает нормальном - токены кончились
так же не должно быть белого уровня сигнала, только вспыхивать квадратик с молнией
справа под графиком широкие вертикальные полоски такие светятся - это аналог уровня сигнала шим, что будет идти на драйверы и регулировать яркость
я думаю их тоже стоит переделать, чтобы не было градиента, а сделать вертикальную шкалу с отметками процентов или же перевернуть их по вертикале, чтобы более яркая часть градиента была снизу, а к верху уменьшалась яркость
позже это будет реализовано на есп32 - 2х ядреный 240мгц контроллер
на одном ядре будет работать wifi, веб сервер, вебсокет для работы с этим гуем
хватит ли ему мощности в реальном времени слать с такой скоростью такой поток данных на эти индикаторы - я не знаю, может быть придется столбики эти выпилить, они как бы и не предпологались, да и спектрограмма входного сигнала тоже
но гемини говорит, что мощности хватит на фильтры и компрессоры, а шим там аппаратный аж на 16 каналов
контроллер будет полностью автономный, подключается к аналоговому выходу источника сигнала, хотя я думал сделать прием звука по сети, но походу тут 3е ядро нужно будет для этого
но цифровой поток это очень хорошо, потому что ацп не нужен - экономится куча времени и можно прилепить свой аудио цап и выводить звук на него и наверное это много ресурсов не займет
но конечно лучше звук выводить на обычный усилитель с компутера и параллельно подключить светомузыку
все это можно сделать на компутере тоже, а чтобы управлять драйверами светодиодов - достаточно за 10 евров купить вот такую звуковую карту