Author SHA256 Message Date
AidarKC daa516babe Регистрация: ожидание подтверждения Solana 2026-07-17 18:31:05 +04:00
AidarKC 2890cdd457 Добавить экран проверки public Solana RPC 2026-07-16 20:33:48 +04:00
AidarKC e4dfb43b5e Переименовать server2 deploy-скрипт 2026-07-16 19:19:59 +04:00
AidarKC 3926d561c0 Доработать финал регистрации для мобильного UI 2026-07-16 19:03:04 +04:00
AidarKC 85133db483 Удалить проверенные pending-фичи 2026-07-16 12:15:22 +04:00
AidarKC 6fc7d75e17 Улучшить финал регистрации и автовход 2026-07-16 12:08:59 +04:00
AidarKC be321813fa Убрать test.shineup.me из deploy-схемы 2026-07-16 11:54:21 +04:00
AidarKC d2c0ecdf62 Исправить регистрацию и навести порядок в UI deploy 2026-07-16 11:46:42 +04:00
AidarKC 24cca1f1c6 UI: доработка регистрации и документации 2026-07-15 16:16:24 +04:00
AidarKC 118be418b5 Обновить диагностику звонков и документацию деплоя 2026-07-14 20:07:31 +04:00
AidarKCandClaude Fable 5 2a4c2c64c9 ИТХ: вход в график закрытий по доверию, а не по депозиту
- Из TODO убран депозитный/платный вход — сознательно отвергнут.
- Зафиксирована модель: DAO ведёт список как временный уровень,
  пока не сформировано сообщество сияющих; целевое состояние —
  приём/исключение серверов через голосование сияющих.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 19:57:12 +04:00
AidarKCandClaude Fable 5 172f007e1e Дополнить дизайн ИТХ: форки, списки доверия, децентрализация
- Раздел 6: правило признания — правило чтения (дефолт rota PDA,
  наблюдатель может считать по своему списку доверия); параллельные
  группы закрытий разрешены by design.
- Новый раздел 10 «Форки пользовательских цепочек»: fork-choice
  дежурного (длиннейшая ветка + tie-break), зачекпоинченное побеждает,
  заморозка chain_frozen только при противоречии архивов двух групп,
  рестарт с login-002, байт-в-байт повтор для клиентов.
- Шаг 9.9 проверки: конфликт -> принять версию чекпоинта (один
  пользователь больше не срывает закрытие дня).
- TODO: хэш-выбор дежурного, вход по депозиту, механизм цепочки -002.
- CHANGELOG блокчейна дополнен; формат блоков и AddBlock не менялись.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:50:41 +04:00
AidarKC 4cb98f7ca0 Обновить UI чатов и связей и добавить документацию ИТХ 2026-07-14 13:39:01 +04:00
AidarKC af12d7b954 Перенести документацию в docs и добавить социальный граф 2026-07-13 12:58:41 +04:00
AidarKC c9cfb394d7 Добавить ответы в DM и обновить UI чатов 2026-07-10 19:27:49 +04:00
AidarKC 0d2d7b6dc5 Добавить SHiNE теги в DM и безопасный рендер превью 2026-07-10 18:57:44 +04:00
AidarKC 076b43e542 Сделать DM body opaque для сервера и улучшить fallback UI 2026-07-10 18:12:17 +04:00
AidarKC cc1f78106d Обновить backup-снимок production сервера 2026-07-10 12:32:17 +04:00
AidarKC 5a5e9c01ce Обновить UI чата и закрыть проверенные DM задачи 2026-07-10 12:26:36 +04:00
AidarKC 0fb1147eb7 Зафиксировать накопленные изменения по deploy и devnet 2026-07-10 11:57:33 +04:00
AidarKC 2b23aa7b95 Исправить UI статусы DM 2026-07-10 11:57:00 +04:00
AidarKC bb92cb6b2b Исправить восстановление DM после перелогина 2026-07-10 11:47:39 +04:00
AidarKC 0730837ad7 Изолировать страницу DEVNET пополнения от mainnet 2026-07-08 20:28:29 +04:00
AidarKC 98a2631590 Довести DM v1 до соответствия коду и документации 2026-07-08 17:28:39 +04:00
AidarKC faada9dcdb Добавить временную миграцию v11 для очистки старых DM 2026-07-07 20:26:16 +04:00
AidarKC 1b7a2a8f7c Реализовать SHiNE_DM v1 с E2EE и tombstone 2026-07-07 18:07:10 +04:00
AidarKC 9c588bd9b5 Документировать новый DM-протокол и формат (в коде пока не реализовано) 2026-07-07 01:19:04 +04:00
AidarKC 31563fe9ed Добавить исторические файлы создания реального DAO 2026-07-07 01:18:16 +04:00
AidarKC 81bef1e1cc Синхронизировать изменения проекта 2026-07-05 18:18:03 +04:00
AidarKC 0240db59ee Подготовка перед деплоем в mainnet 2026-07-01 12:23:30 +04:00
AidarKC ab4dab34aa Исправить Ed25519 promo-подпись в UI 2026-06-30 18:54:39 +04:00
AidarKC 63f13a4c29 Сократить promo-аргументы регистрации в Solana 2026-06-30 18:40:33 +04:00
AidarKC 0a416a2f5c Добавить promo-регистрацию и оффлайн генератор промокодов 2026-06-30 17:39:40 +04:00
AidarKC b0887648f7 Обновить документацию и словари 2026-06-30 11:39:31 +04:00
AidarKC 5720f1cb50 Переименовать экран предоплаченного места 2026-06-29 08:45:30 +04:00
AidarKC 9324da5cb7 Разделить покупку билета на два экрана 2026-06-28 15:11:05 +04:00
AidarKC 408b0eeb39 Закрыть проверку remote AddBlock через homeserver 2026-06-28 14:49:57 +04:00
AidarKC ed83b1f906 Переделать remote AddBlock на сборку блока на homeserver 2026-06-28 14:45:29 +04:00
AidarKC 3068c3e2b8 Добавить раздел поддержки проекта Сияние 2026-06-28 13:53:12 +04:00
AidarKC 93c6f247f7 Исправить ответный SendSignal на ESP32 2026-06-28 12:29:01 +04:00
AidarKC 05a9441493 Исправить подпись SendSignal в UI 2026-06-28 12:09:13 +04:00
AidarKC aa02e92e4d Добавить SendSignal и remote AddBlock через homeserver 2026-06-28 11:20:51 +04:00
AidarKC c397c28acb Удалить старый мусор из документации 2026-06-28 10:29:27 +04:00
AidarKC c93cc6c522 Перенести backlog в TODO 2026-06-28 09:30:59 +04:00
AidarKC 0cdcc77606 Добавить TODO с планами на будущее 2026-06-26 17:55:05 +04:00
AidarKC 87eec7e5c9 Зафиксировать успешную синхронизацию на тестовом сервере 2026-06-26 17:46:34 +04:00
AidarKC 44a1ba01f3 Починить восстановление blockchain_state при full resync 2026-06-26 17:30:10 +04:00
AidarKC d49661fa29 Исправить хэш коммита в changelog блокчейна 2026-06-26 17:06:05 +04:00
AidarKC 71fdee0cfd Вернуть crash-safe запись AddBlock через tmp_bch 2026-06-26 17:05:37 +04:00
AidarKC 1ced351ea2 Закрыть проверенные pending-фичи 2026-06-26 16:56:57 +04:00
AidarKC c048347f2e Добавить startup recovery для resync цепочек 2026-06-26 15:51:52 +04:00
AidarKC be4f76834a Добавить resync блокчейна при рассинхроне 2026-06-26 15:25:11 +04:00
AidarKC 23edad416c Добавить sync-профиль пользователя и обход Solana RPC 2026-06-25 18:46:47 +04:00
AidarKC f3e4233285 Исправить загрузку внешнего application.properties 2026-06-25 18:04:04 +04:00
AidarKC 84e0f039cb Добавить периодический sync блокчейнов каждые 12 часов 2026-06-25 17:58:07 +04:00
AidarKC 1f8b20a7d1 Добавить ListBlockchainHeads для межсерверной сверки 2026-06-25 17:52:04 +04:00
AidarKC f0e1ab3af8 Перевести тестовый контур на t.shineup.me 2026-06-25 13:44:22 +04:00
AidarKC 112ab4d5d5 Улучшить звуки входящих личных сообщений 2026-06-25 12:55:42 +04:00
AidarKC 8768e142e3 Исправить мобильный ввод в личном чате 2026-06-25 11:20:30 +04:00
AidarKC 827d2e9c3e Улучшить открытие личного чата 2026-06-25 10:55:17 +04:00
AidarKC 0f3c4a621d Улучшить UX личного чата на мобильных 2026-06-25 10:46:45 +04:00
AidarKC e60475f351 Обновить синхронизацию серверов и экран сохранения ключей 2026-06-24 20:18:40 +04:00
AidarKC 0f63f7dae6 Добавить документацию по Figma 2026-06-24 16:30:16 +04:00
AidarKC f9a15ab192 Обновить список каналов и кнопку сообщения 2026-06-24 14:59:08 +04:00
AidarKC 77f5759d60 Обновить UI кошелька и регистрацию 2026-06-24 13:48:07 +04:00
AidarKC 684f3237cf Исправить подключение и подпись в браузерном кошельке 2026-06-24 10:28:54 +04:00
AidarKC 23e61cc182 ESP32 регистрация и архив тестового удаления PDA 2026-06-23 20:31:05 +04:00
AidarKC d2f45ff67a ESP32: приостановить reconnect на экранах регистрации 2026-06-23 19:19:18 +04:00
AidarKC 06e12e9103 ESP32: убрать ранние checkpoint при add homeserver 2026-06-23 19:12:58 +04:00
AidarKC 29dddeff4f ESP32: убрать временный автозапуск homeserver 2026-06-23 19:03:32 +04:00
AidarKC 017d568aea ESP32: автозапуск Add Homeserver для отладки 2026-06-23 18:56:33 +04:00
AidarKC c91b52cfd2 ESP32: checkpoint перед чтением PDA homeserver 2026-06-23 18:46:41 +04:00
AidarKC 2bd38d8d78 ESP32: диагностический checkpoint для update homeserver 2026-06-23 18:39:58 +04:00
AidarKC 7d9db68d80 ESP32: NTP для update user_pda 2026-06-23 18:32:32 +04:00
AidarKC 4b94303d67 ESP32: server login и NTP для регистрации 2026-06-23 18:11:11 +04:00
AidarKC 08628704c7 UI: добавить просмотр любого PDA 2026-06-23 17:05:57 +04:00
AidarKC f1c1132690 AuthChallenge: поддержать RecoveryKeyBlock 2026-06-23 17:04:47 +04:00
AidarKC d2426c473c ESP32: убрать кэш генерации секрета 2026-06-23 14:42:02 +04:00
AidarKC 66986b804c Вынести дефолтный сервер в конфиг UI и оживить счетчик пароля 2026-06-23 14:11:09 +04:00
AidarKC 95daa230bb Обновить серверный UI под recovery key 2026-06-23 13:44:18 +04:00
AidarKC 365b22d778 ESP32: кэш последних генераций секрета 2026-06-23 12:06:24 +04:00
AidarKC cf2b54464e Исправить update user_pda для homeserver на ESP32 2026-06-23 11:42:44 +04:00
AidarKC 4e60c1274a ESP32: ускорить и упростить secret screen 2026-06-23 11:08:19 +04:00
AidarKC 2f65e63fbe ESP32: новая derivation ключей 2026-06-23 10:51:03 +04:00
AidarKC b461431197 Смена адреса shine_users и выкладка на test2 2026-06-23 10:40:52 +04:00
AidarKC 5c92b6a734 Миграция PDA на client.key 2026-06-22 21:57:09 +04:00
AidarKC ba348dafb3 Add Wallet Standard registration for browser wallet 2026-06-22 01:35:26 +04:00
AidarKC ce2d310e8c ESP32 wallet RPC, browser wallet provider, and side panel 2026-06-22 01:30:08 +04:00
AidarKC 475db28095 main: это именно та версия, которая уже стоит на тестовом продакшен сервере
Да, звучит немного удивительно, но именно так: люди уже могут регистрироваться, писать друг другу личные сообщения, звонить и пользоваться каналами.

При этом регистрация пока идёт через тестовые Solana/devnet, так что это всё ещё этап тестирования. И в UI ещё куча всего не доделана.

Но тем не менее всё равно.

Прикольно.
2026-06-21 13:14:02 +04:00
AidarKC 823a41c027 chore idea vcs mappings cleanup 2026-06-21 13:10:51 +04:00
AidarKC 2a834f1b14 fix ui dm chatid lowercase normalization 2026-06-21 12:27:41 +04:00
AidarKC c8ffb6cf29 Настроить test2 как основной контур деплоя 2026-06-20 23:34:41 +04:00
AidarKC ecc9efd434 UI: скрыть пароль при режиме 12 слов 2026-06-20 21:33:44 +04:00
AidarKC dd35e56029 Добавить временную бесплатную загрузку аватаров в Arweave 2026-06-20 21:29:35 +04:00
AidarKC d0e7998650 UI: обновить экраны входа 2026-06-20 20:15:40 +04:00
AidarKC fec5e49304 UI: FAQ регистрации и режим пароля из 12 слов 2026-06-20 19:05:45 +04:00
AidarKC 3b12e14e71 Docs: добавить идею homeserver команд и обмена файлами 2026-06-20 17:21:47 +04:00
AidarKC 86eaf2139d UI: улучшить личные сообщения и поиск контактов 2026-06-20 17:19:32 +04:00
AidarKC 65fad993ad UI: вернуть старую вкладку Личные и починить аватары в Связях 2026-06-20 16:43:53 +04:00
AidarKC 55e6e477be merge(main): объединить esp-and-wallet и UI Pixel 2026-06-20 12:17:11 +04:00
PixelandClaude Opus 4.8 ba5efcc152 Merge: UI «Связи» (финал) + редизайн «Личные» (чистый прод) в main
- Граф «Связи»: обновлён до финальной версии поверх PR #3 «pixel-связи»
  (орбы 12/13.06, единый PNG-оверлей орбов, мягкий край, вибрация выключена).
- «Личные»: редизайн списка как формы «Связей» — фото-аватары/инициалы,
  золотая галочка подтверждения у имени, значок-цепочка связи с попапом пути
  (Ты → посредники → цель) и переходом в профиль, граница карточки по типу
  связи, шапка «Shine». Данные пока мок-плейсхолдер (реальные relations/чаты —
  отдельная задача с бэкендом).
- Чистый прод: сняты обе демо-лаборатории (граф/ЛС), demo-чат, гость-обвязка
  ЛС и demo-avatars; экран «Личные» под логином.
- Сохранена работа агента в main (DM-ревизии/редактирование, wallet/pairing, esp32).
- VERSION: client 1.2.217 (server 1.2.204 без изменений).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 23:10:59 +03:00
PixelandClaude Opus 4.8 26253564d5 chore: bump client 1.2.169
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:31:10 +03:00
PixelandClaude Opus 4.8 92791c77a9 прод: убрать ЛС-лабу (demo-чат, гость-доступ, isDemo, demo-avatars); мок→плейсхолдер
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:31:09 +03:00
PixelandClaude Opus 4.8 465792b2ab прод: убрать граф-лабу (network/lab.js, selftest.js, граф-мок в mock-data)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:31:09 +03:00
PixelandClaude Opus 4.8 de269fd828 chore: bump client 1.2.168 + .gitignore (.claude, бэкап-ассеты)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:28 +03:00
PixelandClaude Opus 4.8 8c91484f37 nav: вкладка «Личные» (лейбл)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:27 +03:00
PixelandClaude Opus 4.8 6904ac8b7c ЛС: редизайн списка (фото-аватары, галочка/значок связи у имени, попап-цепочка→профиль) + demo-чат и lab-маршрут
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:27 +03:00
PixelandClaude Opus 4.8 aea6bbcb0e ЛС: токены связей + резолвер визуала + семантический мок (connectedVia/login)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:48:26 +03:00
AidarKC a788d8bcf5 Обновить pairing устройств и доработать ESP32 UI 2026-06-19 20:47:56 +04:00
AidarKC cc074a941f Исправить маршрутизацию call push по sessionId 2026-06-19 19:18:16 +04:00
AidarKC 47574100f9 Исправить самообрыв звонка и обновить TURN 2026-06-19 18:25:47 +04:00
PixelandClaude Opus 4.8 7ad74942e0 Связи: отключена вибрация в графе
haptic() сделан no-op — на экране «Связи» телефон не вибрирует ни на тапах по узлам,
ни на переходах (раскрытие/погружение/всплытие/пан). Вызовы haptic(...) оставлены, тело пустое.
Версия 1.2.167.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:03:32 +03:00
PixelandClaude Opus 4.8 ac1cc04637 nav: неоновые PNG-иконки вкладок бара + единый вид
- toolbar.js: data-driven иконки (iconImg/glow/hero); все 5 вкладок → <img> неон-PNG;
  «Связи» помечена hero.
- components.css: единый размер (--tab-icon-size 27px), «Связи» крупнее ВИЗУАЛЬНО через
  transform: scale (без сдвига раскладки — иконки на одной линии); active/tap-состояния;
  у «Связи» убран лишний drop-shadow-ореол (светится сама PNG); глобально
  -webkit-tap-highlight-color: transparent (нет синего tap-квадрата нигде).
- assets: icon_lichnye/kanaly/svyazi/uvedomleniya/profil.png. Иконка «Уведомления»
  приведена к прозрачному фону (была без альфы) и обрезана до ~92% заполнения, как у других.
Версия 1.2.166.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:03:04 +03:00
aidar 722d055e2d Merge esp32-2-by-tbilisi into main
Merge ветки esp32-2-by-tbilisi в main: TrustedDeviceLogin, редактирование и удаление личных сообщений, browser wallet extension, аудит Solana smart contracts, удаление устаревшего SHiNE-promo-solana-devnet и закрытых pending features.
2026-06-18 12:21:37 +00:00
AidarKC a9a55da8e0 Удалить закрытые записи pending features 2026-06-18 16:11:36 +04:00
AidarKC f2b23ace8b Удалить устаревший SHiNE-promo-solana-devnet 2026-06-18 15:57:55 +04:00
AidarKC 1f2048e270 Перестроить структуру codex-agent-VPS для VPS-пакета 2026-06-18 15:29:09 +04:00
AidarKC b16a23243e Добавить переносимый шаблон codex-agent-VPS 2026-06-18 15:18:07 +04:00
AidarKC 653f1268a6 Проверено: DM-ревизии подтверждены, pending убран 2026-06-18 14:34:37 +04:00
AidarKC 56db6d0add TrustedDeviceLogin API и настройки входа через устройство
Что сделано:\n- публичный API сценария входа через доверенное устройство переведён на TrustedDeviceLogin\n- добавлен GetTrustedDeviceLoginSettings\n- отсутствие записи настроек на сервере теперь трактуется как enabled=true и hasPassword=false\n- ttlSeconds убран из клиентского API, TTL заявки фиксирован на сервере: 300 секунд\n- в shine-UI добавлен отдельный экран настроек входа через устройство и статус на основном экране\n- browser wallet переведён на новые TrustedDeviceLogin операции\n- в wallet добавлен выбор rootKey/deviceKey для будущего запроса подписи\n- документация API обновлена\n\nЧто ещё не проверено вручную end-to-end:\n- полный сценарий UI/plugin после этого деплоя не прогонялся руками до конца\n- сам signaling подписи в wallet всё ещё не реализован
2026-06-18 14:19:31 +04:00
AidarKC cf2152dcfc НЕ ПРОВЕРЕНО: UI редактирования и удаления личных сообщений 2026-06-18 13:04:06 +04:00
AidarKC a95bd245cf НЕ ПРОВЕРЕНО: откат DM-вложений, оставлены ревизии и удаление 2026-06-18 12:24:14 +04:00
AidarKC 92fd315505 НЕ ПРОВЕРЕНО: DM-вложения, upload файлов и ревизии личных сообщений 2026-06-18 11:46:58 +04:00
AidarKC 2225c2d173 Wallet plugin: офлайн wallet-session и выбор homeserver\n\nСделано:\n- wallet plugin сохраняет PDA-профиль и остаётся офлайн до действия;\n- добавлен каркас выбора ключа подписи и homeserver-устройства;\n- добавлен ручной refresh trusted devices через ListSessions;\n- на регистрации показан первый сервер SHiNE и его адрес;\n- обновлены pending notes для ручной проверки.\n\nЕщё не проверено / не доделано:\n- end-to-end ручная проверка plugin после этих правок не завершена;\n- signaling запроса подписи и ответ подписи ещё не реализованы;\n- локальный browser plugin нужно отдельно reload в Chrome/Opera. 2026-06-18 11:04:34 +04:00
AidarKC f8a76bcd7f Автоопределение SHiNE-сервера по логину через PDA 2026-06-16 16:32:33 +04:00
AidarKC 3efa8bb7ee Wallet-session pairing и browser plugin wallet, оплаты пока не работают 2026-06-16 16:23:08 +04:00
AidarKC 5c155ef503 UI: нормальное закрытие сессий и сортировка устройств 2026-06-16 10:36:43 +04:00
AidarKC 41d199e24a Показывать ошибки pairing-пароля в отдельном окне 2026-06-15 15:31:19 +04:00
AidarKC e1f2b54de3 Сузить диалог изменения pairing-пароля 2026-06-15 15:23:19 +04:00
AidarKC d6c5757dfa Переделать UI дополнительного pairing-пароля 2026-06-15 13:35:05 +04:00
AidarKC 9a489801c5 Доработать UX и отмену pairing по коду 2026-06-15 13:13:16 +04:00
AidarKC 9fcdcd087b Убрать QR-заглушку и очищать код после reject 2026-06-15 02:37:26 +04:00
AidarKC af1304022e Исправить синтаксис экрана pairing 2026-06-15 02:30:17 +04:00
AidarKC 7972676eb8 Исправить pairing без пароля и убрать фантомные заявки 2026-06-15 02:21:21 +04:00
AidarKC bef205aec7 Разрешить pairing без доп пароля 2026-06-15 00:54:56 +04:00
AidarKC 49fdbbf7ae Исправить переход со старта на экран выбора входа 2026-06-14 21:33:43 +04:00
AidarKC dd69a52273 Форсировать обновление UI модулей входа 2026-06-14 21:13:22 +04:00
AidarKC c681b4d684 Добавить UI pairing по коду и обновить документацию агента 2026-06-14 20:39:05 +04:00
AidarKC b166013707 Бот: починить resume-вызов Codex CLI 2026-06-14 20:30:17 +04:00
AidarKC 3e04727022 Добавить ESP pairing через доверенные сессии 2026-06-14 18:21:23 +04:00
AidarKC 5d13112b00 ESP32: уменьшить рамку wallet QR 2026-06-14 11:22:11 +04:00
AidarKC 373f88086e ESP32: подправить вертикальный ритм wallet QR 2026-06-14 11:16:49 +04:00
AidarKC 05492306c0 ESP32: смягчить SHiNE reconnect при плохом сервере 2026-06-14 11:01:47 +04:00
AidarKC 423d490939 ESP32: доработать home экран и wallet QR 2026-06-14 10:50:31 +04:00
AidarKC 7edc0ba901 ESP32: зафиксировать LVGL qrcode конфиг 2026-06-14 10:28:42 +04:00
AidarKC 0ebb71daf1 ESP32: добавить реальный wallet QR через LVGL 2026-06-14 10:27:10 +04:00
PixelandClaude Opus 4.8 b4480d89cf css: убрать закомментированный блок кластерной ауры (финально)
Решение по ауре финальное — не возвращаем. Удалён закомментированный «ТЕСТ»-блок
box-shadow по категориям; оставлено явное box-shadow:none для сияющих/фокуса.
Версия 1.2.165.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:13:17 +03:00
PixelandClaude Opus 4.8 ff584ba5d1 orb: мягкий край фото + убран фон-градиент категории из-под стекла
- растушёвка края фото радиальной маской (--feather-full 62% / --feather-edge 78%):
  фото сливается со стеклом без жёсткого ободка; параметры для подкрутки.
- убран фон-градиент категории на .node-dot (просвечивал через прозрачный центр
  стекла → читался как цветная «обводка»): селектор поднят до (0,4,0), чтобы
  перебить правила категории. Цвет категории остаётся на линиях.
- кластерная аура (box-shadow по категории) отключена.
Не тронуто: кромка PNG, свечение сияющих/фокуса/common, линии.
Версия 1.2.164.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:55:09 +03:00
AidarKC 4b15cabd4f ESP32: добавить быстрый QR-экран кошелька 2026-06-13 23:20:35 +04:00
AidarKC be4a2d135a Проект: включить ESP32-wallet в основной репозиторий 2026-06-13 23:01:57 +04:00
PixelandClaude Opus 4.8 69f0fdf120 orb: единый PNG-оверлей на все узлы + ретайр вектора
- glass_overlay_faithful.png в assets; орбы = фото (низ, круглая маска 78%) +
  стеклянный PNG (верх, бокс 119% от node-dot, контакт линий от ORB_R).
- PNG-оверлей применён ко ВСЕМ полным орбам (центр + спутники); tier-3 точки без изменений.
- ретайр мёртвого векторного стекла: удалён buildGlassOrb (+orbSeq) и CSS .fg-orb-svg,
  снят остаточный border .node-dot (синее кольцо) у PNG-хостов.
Версия 1.2.163.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:21:56 +03:00
PixelandClaude Opus 4.8 e3bebff618 anim (13.06): ускорение разлёта узлов — BLOOM_MS 900→550
Дети «выстреливают» из центра почти вдвое быстрее; easing и каскад (stagger)
прежние. Убирает ощущение тяжести/«подтупливания» при смене центра.
Версия 1.2.162.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:36:46 +03:00
PixelandClaude Opus 4.8 f19f7b0ec4 Связи (13.06): орбы по референсу, линии по категории, постоянная вселенная
Орбы:
- материал «хрусталь»: чистое лицо (виньетка 0.5→0.22), диффузный блик окна
  вместо «капли» (+мягкий blur sf), стекло прозрачнее (тело 0.38→0.3),
  полупрозрачная преломляющая кромка (blur + opacity 0.25→0.2).
- размер +11.5% (node-dot 52→58px); единый ORB_R=29 как источник радиуса.
- убран значок * у общих узлов (логика is-common цела).

Линии:
- цвет по категории на ВСЕХ рёбрах; плазма только сияющим.
- общий узел наследует сияние исходного человека (не серый).
- контакт линий ровно на кромке сферы орба (ORB_R), без зазора, все уровни.

Навигация:
- констелляция (паутина 2-3 уровней) — постоянный режим; кнопка «Вселенная»
  убрана; Семья/Друзья/Сияющие остаются фильтрами. Чистка осиротевшего CSS.

Версия 1.2.161.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:31:46 +03:00
AidarKC ca4cfd9d8d UI: выровнять online-флаг с ответом ListSessions 2026-06-13 15:50:47 +04:00
AidarKC 96d292074b API: добавить online-флаг для ListSessions 2026-06-13 15:49:34 +04:00
AidarKC 0536a018c6 ESP32: починить JSON auth для homeserver sessionType 2026-06-13 15:22:19 +04:00
AidarKC 81d1b84a7d ESP32: отправлять homeserver sessionType в SHiNE auth 2026-06-13 15:08:53 +04:00
AidarKC 61c21b245e UI: явно показать тип сеанса в списке устройств 2026-06-13 15:05:41 +04:00
AidarKC 919387f581 API сессий: добавить sessionType и clientPlatform 2026-06-13 14:15:42 +04:00
AidarKC 3b8ea70d3c ESP32: добавить диагностику подключения SHiNE и починить WS handshake 2026-06-13 13:09:32 +04:00
AidarKC 477ab3b580 ESP32: починить добавление homeserver и вернуть автопрогон 2026-06-13 12:53:40 +04:00
AidarKC a1da814030 ESP32: добавить flow обновления homeserver в user PDA 2026-06-13 09:07:49 +04:00
AidarKC 19fd5611b2 ESP32: добавить автотест регистрации и исправить signed server profile 2026-06-13 08:41:25 +04:00
AidarKC 556004a557 ESP32: исправить off-curve проверку для user PDA 2026-06-13 08:20:12 +04:00
AidarKC fba6d6bba0 ESP32: исправить derivation user_pda для Solana 2026-06-13 07:36:45 +04:00
AidarKC 04252e006b ESP32: сохранять полный текст ошибки регистрации 2026-06-13 00:24:42 +04:00
AidarKC 436e1f0c53 ESP32: добавить USB-диагностику регистрации Solana 2026-06-13 00:01:57 +04:00
AidarKC 21030b1d51 ESP32: исправить base64 сериализацию Solana транзакции 2026-06-12 23:48:38 +04:00
AidarKC b583a86ade ESP32: исправить ABI регистрации и подробные ошибки RPC 2026-06-12 23:43:42 +04:00
AidarKC 3262ec9b4a ESP32: перевести UI регистрации на английский 2026-06-12 23:35:05 +04:00
AidarKC 0c9afea67a ESP32: экран подтверждения регистрации 2026-06-12 23:02:07 +04:00
AidarKC b83543d018 ESP32: регистрация по кнопке и зазор между кнопками 2026-06-12 22:24:21 +04:00
AidarKC d4a0185507 Перенёс основной ESP32-скетч в main-device 2026-06-12 22:02:08 +04:00
AidarKCandClaude Opus 4.8 42dcf6970d homeserver: рендейм subserver→homeserver, документ деривации ключей, запрет пустого пароля
Основное (наша работа в этой сессии):
- Переименование «subserver» → «homeserver» по всему проекту: основной ESP32-скетч
  (папка shine_subserver_ui → shine_homeserver_ui, .ino, flash-скрипт, режим burn.sh
  homeserver-ui), скетч lvgl_nav_minimal_test (ключ homeserver.key:<имя>), spec-доки
  reference/*, формат PDA (терминология session_type=100 «Homeserver пользователя»),
  константа SESSION_TYPE_HOMESERVER в JS и Rust (значение 100 не менялось, формат не затронут),
  pending/future доки, AGENTS.md, DAO-док. Сохранены отдельный lvgl_subserver_touch_test и
  историческая пометка о рендейме в DERIVATION.md.
- Новый источник истины по деривации ключей: Dev_Docs/Keys/DERIVATION.md (Argon2id-секрет из
  пароля, формула Ed25519(SHA-256(base64(secret)|suffix)), суффиксы root/bch/dev/homeserver.key,
  Solana-ключ = dev.key). Уточнены роли root (главный/master) и dev (пополняемый кошелёк) в
  Dev_Docs/Keys/README.md.
- UI: убран легаси-путь пустого пароля (derivePasswordSeed и др.), deriveMasterSecretFromPassword
  бросает ошибку на пустом пароле, register-view блокирует пустой пароль; экран пополнения
  переведён на канонический device-адрес из preGeneratedKeyBundle (удалён расходящийся
  deriveWalletFromPassword).

Включены также параллельные правки Solana-аудита №3 (были в рабочем дереве, переплетены в lib.rs):
- shine_users: defense-in-depth «строгий список аккаунтов» (require!(it.next().is_none()))
  в init/update economy config и create/update user PDA, плюс описание в doc/programs/shine_users.md;
- Dev_Docs/audit/Solana-audit-3-by-Claude-12июня2026.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 21:16:12 +04:00
PixelandClaude Opus 4.8 0b4374141e Связи (test 12.06): центр-орб крупнее (FOCUS_SCALE 1.78) + шире ореол центра (glowSpread 7)
Рычаг 1: glowSpread центра 4.5→7 (мягче/шире свечение), спутники без изменений. Рычаг 2: FOCUS_SCALE 1.5→1.78 (иерархия). Версия 1.2.160.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:53:13 +03:00
PixelandClaude Opus 4.8 652ddc9d88 Связи (test 12.06): SVG-стеклянные орбы аватаров + цвет/свечение линий глубоких связей
Аватары → SVG GlassOrb (фото в стеклянной сфере, блик, rim, свечение). Линии глубоких связей (tier-2/3) — в цвете типа (друзья/семья/...), сияющие связи светятся (голубой ореол + ядро). Версия 1.2.159.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:10:06 +03:00
AidarKCandClaude Fable 5 cf6a2830c8 solana: закрыть griefing создания PDA и заморозку выплат, добавить аудит №2
shine_payments + shine_users:
- create_pda_account переведён на «создание поверх предзаполненного»
  (allocate+assign+добор ренты), чтобы подсев лампортов на детерминированный
  адрес PDA (тикет/логин) не блокировал создание — закрыт LOW из аудита №1;
  в shine_payments is_uninitialized_account перестала зависеть от баланса.

shine_payments (HIGH из аудита №2):
- запрещён recipient == inflow_vault в buy_ticket*, manager_add_ticket и
  change_ticket_recipient; добавлена защита по умолчанию в transfer_from_vault
  (require vault.key != recipient.key). Это убирает алиасинг аккаунта в
  step_payout, который навсегда замораживал очередь выплат и средства вольта.

Документация и учёт:
- doc/programs/shine_payments.md §3.4, §10.1; doc/programs/shine_users.md §3.3;
- Dev_Docs/audit: добавлен аудит №2, обе закрытые находки помечены ИСПРАВЛЕНО;
- Dev_Docs/Pending_Features: две записи на ручную e2e-проверку на devnet;
- VERSION.properties: client 1.2.161, server 1.2.150.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 04:10:31 +04:00
AidarKC 01d9553db4 merge(ui): влить PR #3 pixel-связи в main 2026-06-11 00:24:36 +04:00
AidarKC 578b526f96 Скорректировать main к базе 553a1f1 и UI из Pixel 2026-06-10 23:03:01 +04:00
AidarKC e3061b46f9 Связи: финальный вид сияющих связей
Перенести финальный плазменный рендер связей из коммита Pixel 2559f1e6 в main.
2026-06-10 19:00:22 +04:00
PixelandClaude Opus 4.8 2559f1e66b Связи: финальный вид сияющих связей — плазма (поле+трубка+ядро, screen-blend) + цвет связи у обычных
Промежуточные итерации линий схлопнуты в один коммит. Убран мёртвый фильтр fg-plasma-turb. Лаборатория (/network-view/lab) и автотесты сохранены. Версия 1.2.158.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:09:33 +03:00
AidarKC 9ca469a075 solana: усилить проверку Pyth oracle в shine_payments 2026-06-10 02:25:45 +04:00
PixelandClaude Opus 4.8 519bce6b78 Связи (pixel-aquarium, 10.06): партия 3 (лаборатория) — общие связи + доступность
Вариант 2 (всё в лаборатории, реальный путь /network-view не трогаем).

- Общие связи: среди друзей человека один помечен как «общий» (он и твой друг тоже) — золотой ободок
  + ★ (CSS .fg-node.is-common). В лаб-генерации addDeepLevels подставляет узнаваемого друга Ивана.
- Доступность: визуально скрытый (sr-only) текстовый список графа .fg-a11y (центр + связи 1-го уровня)
  для скринридеров; обновляется в updateA11y при перестроении (role=region, aria-label).

Автопроверки расширены до 19 ассертов (добавлены «общие связи ★» и sr-only список) — прогон 19/19 PASS.
Бамп client.version → 1.2.150.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:53:49 +03:00
PixelandClaude Opus 4.8 557ea96be0 Связи (pixel-aquarium, 10.06): партия 2 (UI-фишки) — поиск, хлебные крошки, бейджи, цветовые кластеры
Всё в лаборатории (вариант 2: реальный путь /network-view не трогаем).

- Поиск + телепорт: строка .fg-search; Enter → graph.findNode(имя) → камера летит к узлу
  (dive в «Вселенной», иначе перецентр).
- Хлебные крошки: .fg-breadcrumb «Иван › Нина › Ада» (движок шлёт onDiveChange(path), API getDivePath);
  клик по корню — полный сброс, по предку — навигация на его уровень.
- Бейдж числа связей: .fg-node-badge (degreeById → updateBadges; у центра — число связей 1-го уровня).
- Цветовые кластеры: мягкая аура узла по типу связи (CSS is-family/friend/business/contact).

Автопроверки расширены до 17 ассертов (добавлены поиск/крошки/бейдж) — прогон 17/17 PASS.
Фикс: TDZ breadcrumbEl (объявлен до createForceGraph, т.к. onDiveChange вызывается при монтировании).
Бамп client.version → 1.2.149.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:43:02 +03:00
PixelandClaude Opus 4.8 9a49cc67f0 Связи (pixel-aquarium, 10.06): партия 1 (полиш) + автопроверки графа
Усиления (движок-полиш) с детерминированной самопроверкой:
- Веер детей — полукругом «наружу» (DEEP_FAN, по sibIndex от направления деда→родитель): не перекрывает
  нить-крошку и родителя; равномерное распределение.
- LOD с гистерезисом (LOD_ZOOM_UP=1.6 / DOWN=1.4) — точки 3-го уровня ↔ аватарки без «мигания» у порога.
- Двойной тап по пустому фону и сильный pinch-out на минимальном зуме = быстрый выход из погружения.
- Префетч аватарок детей при наведении/нырке (prefetchChildren) — лица в кэше до раскрытия.

Автопроверки (dev-only, ТОЛЬКО при ?fgtest):
- js/pages/network/selftest.js — 14 ассертов: камера-центровка, collision (нет слипания), полукруг,
  spotlight (путь 1.0 / фон 0.25 / сброс при переключении / 100% на выходе), LOD, возврат зума.
- Движок: read-only graph.debugState() + graph.pumpForTest() (синхронно докручивает кадры до покоя,
  не зависит от троттлинга rAF в фоне). Граф как window.__fg — тоже только при ?fgtest.
- Прогон: 14/14 PASS (offset 0px, мин.дистанция детей 89px, веер ±99°, LOD 4/4).

В обычной работе тест-хелперы не активны. Реальный путь /network-view не затронут. Бамп client → 1.2.148.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:25:57 +03:00
PixelandClaude Opus 4.8 3012f0799b Связи (pixel-aquarium, 10.06): фикс физики раскрытия + камера-наезд + железный spotlight
Исправлены критические баги «аквариума» по разбору (видео):

1. Слипание узлов → адаптивный радиус орбиты (фикс collision). Дети раскладываются на кольце
   ringR = max(baseR + радиус_родителя, число_детей×13): не лезут на (увеличенный зумом) родитель
   и друг на друга. Проверено: мин. дистанция 125px, 0 наложений (было — все в одной точке).

2. Умный наезд камеры на КЛИК по любому узлу (раньше 1-й уровень раскрывался на месте). diveTo
   центрирует узел (offset ~0), zoom 1.7; узел и дети растут до единого видимого размера
   (HERO_VISUAL/baseScaleOf, DIVE_CHILD_VISUAL) — крупно и читаемо. Наведение остаётся лёгким превью.

3. Железный Spotlight (единый активный путь): diveTo гасит ВСЕ прежние pin/hover, затем раскрывает
   только путь к цели. Открыто → путь=1.0, остальное=0.25; переключение веток сбрасывает прежнюю;
   exitDive/тап по Ивану → ВСЕ узлы гарантированно 1.0 + камера отъезжает. (Проверено программно.)

Реальный путь /network-view не затронут (вся глубина под tier≥2/hasDeep). Бамп client.version → 1.2.147.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:12:27 +03:00
PixelandClaude Opus 4.8 7a8852f64b Связи (pixel-aquarium): Умный фокус (Smart Zoom / «аквариум») — погружение в узел + LOD
Новая ветка для безопасного отката (от pixel-web). Режим «Вселенная», только лаборатория.

1. Гибрид клика: 1-й уровень → раскрытие ветки НА МЕСТЕ (как раньше); 2-й уровень+ → ПОГРУЖЕНИЕ.

2. Dive (умный наезд камеры, «аквариум», без перестройки графа):
   - diveTo(node): пинит весь путь (предки до Ивана), ставит diveTargetId + diveZoom=1.7;
     камера в tick плавно ЛЕТИТ и ЗУМИТ, центрируя узел (DIVE_FLY_K), узел ВЫРАСТАЕТ (×2.1 ~ герой).
   - Глубина (contextTargetOf → depthScale/depthBlur/spotCur, лерп): Иван и боковые ветки
     УМЕНЬШАЮТСЯ (root ×0.55) + уходят в BLUR 3px + тускнеют до 0.25 → задний план «аквариума».
   - Нить-крошка: путь Иван→…→узел (divePathSet/onPath) горит ярким «световодом» — виден путь назад.
   - Всплытие: повтор клика по цели → exitDive (камера/зум плавно к корню); клик по Ивану →
     collapseAll (полный сброс + всплытие).

3. Pinch-to-Zoom + LOD 3-го уровня: при zoom≥1.55 видимые точки 3-го уровня дорисовываются как
   читаемые аватарки (лицо+имя; updateLod/setNodeLod — пере-рендер DOM на пороге), при отдалении —
   обратно в светящиеся точки. Узлам tier-3 добавлены фото-заглушки (pravatar) и имена.

Глубина — фейк-3D через масштаб + CSS-blur (GPU), без WebGL. Реальный путь /network-view не затронут:
dive только tier≥2 (в реале их нет), depthScale/Blur нейтральны по умолчанию, updateLod выходит при !hasDeep.
Бамп client.version → 1.2.146.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 23:32:16 +03:00
PixelandClaude Opus 4.8 f92e6c3cf1 Связи (pixel-web): фикс багов паутины — видимый 2-й уровень, удалён «прицел», toggle+spotlight
Исправлены замечания по видео (режим «Вселенная», только лаборатория):

1. «Невидимые друзья» (узлы 2-го уровня). Раньше — тусклые пустые кружки с инициалом.
   Теперь tier-2 — полноценные аватарки: фото-лицо (pravatar) + имя, DEEP2_SCALE 0.5→0.62
   (≈radius 16px), DEEP2_OPACITY 0.4→0.85; читаемый ободок и подпись (CSS).

2. Мусорный «прицел» (пунктирное кольцо у центра). Полностью удалён из движка:
   элемент .fg-reticle, updateReticle/pulseReticle и все их вызовы, CSS .fg-reticle*.
   На экране только аватарки и линии связей.

3. Логика toggle + spotlight:
   - Повторный клик по раскрытому узлу теперь СВОРАЧИВАЕТ его (isOpen = pinned || expandP>0.5
     → сброс pinned+hovered) — работает, даже если ветка была раскрыта ховером.
   - Spotlight: при закреплённой ветке остальные тускнеют до 0.25 (узлы и линии), фокус и
     закреплённая/наведённая ветка — 100%; плавно через spotCur (lerp, см. spotTargetOf).
   - Клик по центру (Иван) — collapseAll + возврат всему графу 100% яркости.

Реальный путь /network-view не затронут (deep-код под tier≥2/hasDeep). Ветка экспериментальная.
Бамп client.version → 1.2.145.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:55:10 +03:00
PixelandClaude Opus 4.8 72dc83daff Связи (pixel-web): этап 2 паутины — hover-превью + collision + zoom + камера-доводчик + синхро-пульс
Доработка режима «Интерактивная паутина» (только лаборатория, deep-режим «Вселенная»):

Взаимодействие (по запросу): наведение ≠ клик.
- Hover-превью: навёл мышь/палец на узел — его ветка ВРЕМЕННО выплывает; убрал — втягивается.
  (pointerover/out для мыши, pointerdown/up для пальца → onNodeHover → graph.setHover; флаг hovered).
- Фиксация кликом: тап/клик → graph.toggleExpand ставит pinned — ветка остаётся раскрытой и
  после ухода курсора; повторный тап снимает фиксацию. Эффект = pinned || hovered (expandTargetOf).

Этап 2 «Мегамасштаб»:
- Collision-расталкивание: раскрытая ветка усиливает отталкивание соседей 1-го уровня
  пропорционально expandP (EXPAND_REPULSION=2.4) — кластеры разъезжаются, не накладываясь.
- Свободный зум: колесо мыши (onWheel) + щипок двумя пальцами (activePointers/pinching),
  zoom 0.55–2.6 «к точке»; мир — CSS-scale, линии (SVG) пересчитываются в экранных координатах × zoom.
- Камера-доводчик: при фиксации ветки, если её веер упирается в край, камера мягко дотягивается
  (glideCameraTo → camTargetX/Y, lerp CAM_GLIDE_K в tick); любой жест отменяет доводчик.
- Синхро-пульс: сияющие/трековые «световоды» дышат толщиной/размытием 3.6с в такт ободку узла.

Реальный путь /network-view не затронут: deep-код под tier≥2/hasDeep, hover-колбэк даёт только
лаборатория. Ветка экспериментальная (отдельно от pixel-08.06/PR). Бамп client.version → 1.2.144.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:34:26 +03:00
AidarKC 5981d3f871 solana: привязать singleton pda в shine_payments 2026-06-09 23:10:20 +04:00
AidarKC fb0c5ad3f8 audit: сохранить отчёт по Solana 2026-06-09 23:09:21 +04:00
PixelandClaude Opus 4.8 04d9d588e8 Связи (pixel-web): режим «Интерактивная паутина» — раскрытие веток без смены центра
Этап 1 mind-map (только лаборатория, deep-режим «Вселенная»):
- Отмена прыжков в центр: тап по периферийному узлу больше НЕ перецентрирует — он остаётся
  на орбите, а из него раскрывается/сворачивается (toggle) его ветка дальних связей НА МЕСТЕ.
- Глобальный сброс: тап по корню (Иван) рекурсивно сворачивает все раскрытые ветки (collapseAll).
- Глубина скрыта по умолчанию; ветка плавно выплывает (expandP, ~400мс) и втягивается по повтору.
- Мерцающие звёзды 3-го уровня (CSS box-shadow/brightness, десинхрон по узлам) — «созвездие».
- Тактильный отклик navigator.vibrate(): клик при нажатии, серия импульсов на bloom-раскрытие,
  щелчок «гитарной струны» при сильном натяжении нитей свайпом.
- Движок: API toggleExpand/collapseAll; убрана press/hover-логика раскрытия (заменена тапом).

Ветка экспериментальная (отдельно от pixel-08.06/PR), бамп client.version → 1.2.143.
Ещё не сделано (следующие этапы): collision-расталкивание веток, камера-доводчик, zoom,
синхро-пульс линий к сияющим.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:04:48 +03:00
AidarKC e5fe925023 solana: защитить economy config в shine_users 2026-06-09 23:04:03 +04:00
PixelandClaude Opus 4.8 345a21a211 Связи (эксперимент pixel-web): база — микро-взаимодействия + глубина
Фиксирую накопленные черновики как точку отката перед режимом «Интерактивная паутина»:
- press/pan-bend (резиновые нити при свайпе, тактильное «вдавливание» узла);
- глубина 2-3 уровней (прототип «Вселенная», переключатель в лаборатории);
- прогрессивное раскрытие (глубина скрыта, выплывает по нажатию/наведению).

Ветка pixel-web — экспериментальная (отдельно от pixel-08.06/PR), чтобы можно было откатиться.
Бамп client.version → 1.2.142.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:51:48 +03:00
AidarKC b83e8c3979 solana: удалить неиспользуемый модуль common 2026-06-09 22:48:59 +04:00
AidarKC 0744ac3163 fix(ui): починить прямые ссылки профиля и связей 2026-06-09 22:32:02 +04:00
PixelandClaude Opus 4.8 3de992d251 Связи: вернуть лабораторию в ветку (для просмотра графа на моках)
Откат удаления из aed64e7: lab.js снова в репозитории, статический импорт в network-view.js,
строка lab.js убрана из .gitignore. Лаборатория /network-view/lab нужна для демонстрации/проверки
графа на мок-данных без бэкенда. Бамп client.version → 1.2.141.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:18 +03:00
PixelandClaude Opus 4.8 369ef61cab Связи: лаборатория только локально (исключена из репозитория)
- shine-UI/js/pages/network/lab.js убран из git (git rm --cached) и добавлен в .gitignore:
  на сервере лаборатория не нужна (там реальные данные), локально файл остаётся и работает.
- network-view.js: статический импорт lab.js заменён на ДИНАМИЧЕСКИЙ с фолбэком — если файла
  нет (прод/сервер), реальный экран «Связи» не ломается, а заход на /network-view/lab уводит
  на обычный экран. Локально лаборатория грузится как прежде.
- Документация фичи отражает, что lab.js — локальный (в .gitignore).
- Бамп client.version → 1.2.140.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:18 +03:00
PixelandClaude Opus 4.8 e6e96c4b0d Связи: чистка мёртвого кода в движке (lerp, неиспользуемые easing)
- Убран механизм lerpX/lerpY: координаты для отрисовки берутся из n.x/n.y, lerp нигде
  не читался кроме условия заморозки (lerpSettling). Удалены поля, advanceLerp(), EDGE_LERP
  и lerpSettling — граф засыпает чуть раньше (без визуальных изменений; проверено: frozen=true).
- Удалены неиспользуемые cubicBezier() и EASE_BLOOM (easing теперь делает CSS); easeOutCubic
  оставлен (нужен в stepTween для фолбэк-центрирования).
- Документация фичи актуализирована (убрана заметка про lerp как кандидата на чистку).
- Бамп client.version → 1.2.139.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:18 +03:00
PixelandClaude Opus 4.8 dc96033cb1 Связи: двухслойные линии-световоды, живой фон и стеклянные фильтры
- Сияющие связи — двухслойный неоновый «световод»: размытый glow (4px, blur 2px,
  opacity 0.4) + тонкий чёткий core (1.5px, #e0f7fc). Объёмное OLED-свечение,
  линия остаётся изящной. Оба слоя растут синхронно (общий dashoffset).
- Обычные линии — тоньше (1.0–1.2px) и глубокий уход в прозрачность (0.42 → 0.07),
  чтобы матовые связи не спорили с сияющими.
- Живой фон-«небула»: глубокое размытое сине-голубое облако под центром, медленная
  пульсация радиуса/яркости + переливы индиго↔ультрамарин (hue-rotate, 7с).
- Стеклянные чипы фильтров (frosted glass): rgba(255,255,255,0.03) + backdrop blur(12px)
  + граница 0.5px solid rgba(255,255,255,0.1); активный подсвечен сине-голубым.
- Бамп client.version → 1.2.138; документация фичи обновлена.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:17 +03:00
PixelandClaude Opus 4.8 9ee6bf4380 Связи: полировка карты связей (свечение, прорастание линий, CSS-фильтры)
- Линии: тонкие дуги Безье (градиент неон-центр → цвет роли); связь к «сияющему»
  монолитно светится статичной тенью drop-shadow (без бегущих импульсов).
- Прорастание новых линий из центра: stroke-dasharray/dashoffset синхронно с
  разлётом узла (кончик трекает аватарку); старые линии исчезают мгновенно.
- Ghost-слой: только аватарки (без линий), 1000мс — нет висящих «ошмётков».
- CSS-bloom разлёта на компоновщике (устойчив к троттлингу rAF; завершение по таймеру).
- Сияющие узлы: мягкая медленная пульсация 3.6с (многослойная box-shadow + SVG-ореол);
  тестовые фото-аватарки.
- Фильтры слоёв в лаборатории + фикс перехвата click сценой (stopPropagation на чипах);
  фейд скрываемых на месте (opacity 0 + scale 0.8, 300мс), фиксация без физики (ноль тряски).
- Бамп client.version → 1.2.137; обновлена документация фичи.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:17 +03:00
PixelandClaude Opus 4.8 e0f0726e68 Связи: интерактивная карта связей (force-directed graph)
Переработка экрана «Связи» в интерактивный нод-граф с премиальными переходами.

Движок (js/pages/network/force-graph.js):
- diffing-переходы: общие узлы перелетают, новые расцветают каскадом, исчезнувшие — Ghost-слой (800мс, на месте);
- мягкая радиальная пружина + отталкивание (органичная орбита), упругий влёт фокуса;
- динамическая вязкость на старте (трение 0.92→0.82, отталкивание ослаблено) — мягкий разлёт без тряски;
- жёсткая заморозка (kill-switch) при затухании — нет «треска», экономия батареи;
- линии — SVG <path> Безье (изогнутые нити), прорастание; жесты pan с инерцией;
- хард-лимит DOM-аватарок (остальное — SVG-точки).

Интеграция и UX:
- adapter.js: getUserConnectionsGraph → модель движка (сервер не трогаем, read-only);
- фильтры (Все/Семья/Друзья/Сияющие), контекстное меню (node-menu.js), нижний сниппет, профиль;
- прицел в центре, дыхание фокуса, свечение сияющих;
- лаборатория network-view/lab на мок-данных (networkGraphUsers) для тестов без бэкенда.

Документация: shine-UI/Dev_Docs/features/interactive-network-graph.md.
Бамп client.version 1.2.135 -> 1.2.136.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:23:16 +03:00
AidarKC 105a56499d merge(ui): влить pixel-08.06 в main 2026-06-09 22:20:49 +04:00
AidarKCandClaude Opus 4.8 2bd27cd73b Связи: вернуть лабораторию в ветку (для просмотра графа на моках)
Откат удаления из aed64e7: lab.js снова в репозитории, статический импорт в network-view.js,
строка lab.js убрана из .gitignore. Лаборатория /network-view/lab нужна для демонстрации/проверки
графа на мок-данных без бэкенда. Бамп client.version → 1.2.141.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:23:40 +03:00
AidarKCandClaude Opus 4.8 aed64e76a7 Связи: лаборатория только локально (исключена из репозитория)
- shine-UI/js/pages/network/lab.js убран из git (git rm --cached) и добавлен в .gitignore:
  на сервере лаборатория не нужна (там реальные данные), локально файл остаётся и работает.
- network-view.js: статический импорт lab.js заменён на ДИНАМИЧЕСКИЙ с фолбэком — если файла
  нет (прод/сервер), реальный экран «Связи» не ломается, а заход на /network-view/lab уводит
  на обычный экран. Локально лаборатория грузится как прежде.
- Документация фичи отражает, что lab.js — локальный (в .gitignore).
- Бамп client.version → 1.2.140.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:12:48 +03:00
AidarKCandClaude Opus 4.8 abfd073de8 Связи: чистка мёртвого кода в движке (lerp, неиспользуемые easing)
- Убран механизм lerpX/lerpY: координаты для отрисовки берутся из n.x/n.y, lerp нигде
  не читался кроме условия заморозки (lerpSettling). Удалены поля, advanceLerp(), EDGE_LERP
  и lerpSettling — граф засыпает чуть раньше (без визуальных изменений; проверено: frozen=true).
- Удалены неиспользуемые cubicBezier() и EASE_BLOOM (easing теперь делает CSS); easeOutCubic
  оставлен (нужен в stepTween для фолбэк-центрирования).
- Документация фичи актуализирована (убрана заметка про lerp как кандидата на чистку).
- Бамп client.version → 1.2.139.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 18:20:00 +03:00
AidarKCandClaude Opus 4.8 41edd1423c Связи: двухслойные линии-световоды, живой фон и стеклянные фильтры
- Сияющие связи — двухслойный неоновый «световод»: размытый glow (4px, blur 2px,
  opacity 0.4) + тонкий чёткий core (1.5px, #e0f7fc). Объёмное OLED-свечение,
  линия остаётся изящной. Оба слоя растут синхронно (общий dashoffset).
- Обычные линии — тоньше (1.0–1.2px) и глубокий уход в прозрачность (0.42 → 0.07),
  чтобы матовые связи не спорили с сияющими.
- Живой фон-«небула»: глубокое размытое сине-голубое облако под центром, медленная
  пульсация радиуса/яркости + переливы индиго↔ультрамарин (hue-rotate, 7с).
- Стеклянные чипы фильтров (frosted glass): rgba(255,255,255,0.03) + backdrop blur(12px)
  + граница 0.5px solid rgba(255,255,255,0.1); активный подсвечен сине-голубым.
- Бамп client.version → 1.2.138; документация фичи обновлена.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 18:01:36 +03:00
AidarKC 471fde78c1 ESP32: ужесточить touch UX и обновить инструкции 2026-06-09 18:30:12 +04:00
AidarKCandClaude Opus 4.8 3e4759a0c9 Связи: полировка карты связей (свечение, прорастание линий, CSS-фильтры)
- Линии: тонкие дуги Безье (градиент неон-центр → цвет роли); связь к «сияющему»
  монолитно светится статичной тенью drop-shadow (без бегущих импульсов).
- Прорастание новых линий из центра: stroke-dasharray/dashoffset синхронно с
  разлётом узла (кончик трекает аватарку); старые линии исчезают мгновенно.
- Ghost-слой: только аватарки (без линий), 1000мс — нет висящих «ошмётков».
- CSS-bloom разлёта на компоновщике (устойчив к троттлингу rAF; завершение по таймеру).
- Сияющие узлы: мягкая медленная пульсация 3.6с (многослойная box-shadow + SVG-ореол);
  тестовые фото-аватарки.
- Фильтры слоёв в лаборатории + фикс перехвата click сценой (stopPropagation на чипах);
  фейд скрываемых на месте (opacity 0 + scale 0.8, 300мс), фиксация без физики (ноль тряски).
- Бамп client.version → 1.2.137; обновлена документация фичи.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 17:06:45 +03:00
AidarKC b5276890fb ESP32: обновить подпись и выровнять индикаторы HOME 2026-06-09 17:52:33 +04:00
AidarKC 1488bc3d6d ESP32: доработать HOME экран и поток секретов 2026-06-09 17:44:56 +04:00
AidarKC f4e7210a40 ESP32: добавить NAV v8 с account и Wi-Fi reconnect 2026-06-09 16:14:24 +04:00
AidarKC e385bb6bf9 ESP32: зафиксировать промежуточный NAV v6 UI прототип 2026-06-09 15:22:45 +04:00
AidarKCandClaude Opus 4.8 f56e531384 Связи: интерактивная карта связей (force-directed graph)
Переработка экрана «Связи» в интерактивный нод-граф с премиальными переходами.

Движок (js/pages/network/force-graph.js):
- diffing-переходы: общие узлы перелетают, новые расцветают каскадом, исчезнувшие — Ghost-слой (800мс, на месте);
- мягкая радиальная пружина + отталкивание (органичная орбита), упругий влёт фокуса;
- динамическая вязкость на старте (трение 0.92→0.82, отталкивание ослаблено) — мягкий разлёт без тряски;
- жёсткая заморозка (kill-switch) при затухании — нет «треска», экономия батареи;
- линии — SVG <path> Безье (изогнутые нити), прорастание; жесты pan с инерцией;
- хард-лимит DOM-аватарок (остальное — SVG-точки).

Интеграция и UX:
- adapter.js: getUserConnectionsGraph → модель движка (сервер не трогаем, read-only);
- фильтры (Все/Семья/Друзья/Сияющие), контекстное меню (node-menu.js), нижний сниппет, профиль;
- прицел в центре, дыхание фокуса, свечение сияющих;
- лаборатория network-view/lab на мок-данных (networkGraphUsers) для тестов без бэкенда.

Документация: shine-UI/Dev_Docs/features/interactive-network-graph.md.
Бамп client.version 1.2.135 -> 1.2.136.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:43:56 +03:00
AidarKC 32606fe1c2 ESP32: зафиксировать рабочий LVGL nav prototype и тесты 2026-06-08 18:39:11 +04:00
AidarKC a8734846a0 ESP32: добавить LVGL тесты и отметить рабочий hybrid touch test 2026-06-08 16:56:00 +04:00
AidarKC ad0edf3c88 ESP32: зафиксировать тесты и нерабочий LVGL/UI вариант 2026-06-08 15:35:27 +04:00
AidarKC 66975862f7 ESP32: временно отключить PIN-вход в subserver UI 2026-06-08 12:29:47 +04:00
AidarKC b9185e761b ESP32: добавить UI сабсервера, PIN-ввод пока не работает 2026-06-08 12:25:54 +04:00
AidarKC 885cf463a7 Уточнить финальный экран devnet topup 2026-06-07 11:26:09 +04:00
AidarKC 689f35fea2 Доработать клиентский UI Solana-регистрации 2026-06-07 11:19:58 +04:00
AidarKC 6bf5d1d5ed Исправить клиентскую Solana-регистрацию после ухода от Anchor 2026-06-07 10:58:06 +04:00
AidarKC b0b8c7a445 Перевести shine_payments на новый program id и подтвердить e2e 2026-06-07 10:00:39 +04:00
AidarKC d25c19cdaa Исправить чтение state в shine_payments и описать e2e тест 2026-06-06 17:12:26 +04:00
AidarKC 89d06d317b Переписать shine_payments и обновить тестовый UI с известным багом state 2026-06-06 16:58:57 +04:00
AidarKC c5ec32f87a Обновить Telegram-бота, документацию и связанные доработки 2026-06-06 13:45:02 +04:00
AidarKC ce5c348023 Убрать v2 из economy seed shine_users 2026-06-05 11:42:21 +04:00
AidarKC 832eea5889 Переписать shine_users и shine_login_guard на чистый Rust 2026-06-04 23:05:45 +04:00
AidarKC 60049442f1 Зафиксировать все текущие изменения проекта 2026-06-04 22:27:09 +04:00
AidarKC 624557ebfd Удалить старый путь документа формата PDA 2026-06-04 22:17:33 +04:00
AidarKC 6b0379bfdc Добавить спецификации Solana программ и вынести формат PDA 2026-06-04 22:17:17 +04:00
AidarKC a9510a6d36 Обновить lazy-import Solana PDA под новый формат 2026-06-04 14:33:42 +04:00
AidarKC 59e4156bb9 Удалить obsolete server UI и подчистить ссылки 2026-06-04 14:25:59 +04:00
AidarKC de9606519a Починить native Ed25519 update_user_pda без OOM 2026-06-04 13:47:47 +04:00
AidarKC eeb115584d Добавить диагностику server PDA и баланс device (не проверено) 2026-06-03 16:12:40 +04:00
AidarKC ee3721dfa4 Исправить DEVNET topup и автоподстановку пароля 2026-06-03 15:57:49 +04:00
AidarKC 239cc231ea Снимок состояния перед фиксом DEVNET topup и автоподстановки пароля 2026-06-03 15:56:17 +04:00
AidarKC 4bd4df7b09 Добавить переход в server UI и DEVNET topup 2026-06-03 15:21:55 +04:00
AidarKC d12371b84f Перенести server UI в shine-UI и объединить PDA-модуль 2026-06-03 15:11:26 +04:00
AidarKC c97b3e3ec3 Снимок состояния до переноса серверного UI 2026-06-03 14:49:03 +04:00
AidarKC 2c2aad1355 Убраны непроверенные готовые фичи и перенесён QR-план 2026-06-03 14:20:45 +04:00
AidarKC 9949935bcc Добавить обработку длинных voice/audio в агент-боте 2026-06-03 00:18:30 +04:00
AidarKC 35fc6ebf62 Сделана компактная кнопка показа ключей в регистрации 2026-06-02 16:43:38 +04:00
AidarKC d2205648e6 Сделана компактная кнопка скрытия ключей в UI 2026-06-02 16:40:58 +04:00
AidarKC 68ed93dd24 Переведены ключи UI в base58 и обновлены deploy defaults 2026-06-02 16:34:37 +04:00
AidarKC a06b76b800 Обновлён server UI и приватные ключи переведены в base58 2026-06-02 15:52:22 +04:00
AidarKC 67f882b9bc Добавлен контур server-backup для shineup.me и регламент обновляемых бэкапов 2026-06-01 13:05:54 +04:00
AidarKC 17dc4981c6 Поправить Solana-программу регистрации пользователей
Шаг 1 — Rust (users.rs)

- Убран server_key: Pubkey из UserMutableFields и UserRecord.

- Добавлены address_format_type: u8 и address_format_version: u8 в соответствующие структуры.

- Добавлена константа BLOCK_VERSION_1: u8 = 1.

- Обновлен write_server_profile_block: версия блока = 1, убраны 32 байта server_key, добавлены 2 байта формата адреса перед server_address.

- Обновлен deserialize_record_from_pda для BLOCK_TYPE_SERVER_PROFILE: ожидается BLOCK_VERSION_1, чтение server_key убрано, добавлено чтение type/version формата адреса.

- Обновлены конструкторы UserRecord под новые поля.

- Обновлена документация формата: shine-solana/shine/doc/SHiNE-user-format-v.1.0.md.

- Синхронизированы связанные изменения UI/доков и VERSION.properties (client 1.2.109, server 1.2.101).
2026-05-31 22:25:33 +04:00
AidarKC 0179b25d12 Исправить дефолты деплоя на shineup.me
- deployServer: remoteHost по умолчанию = shineup.me.

- deployServer: путь localJar по умолчанию = build/libs/shine-server.jar.

- deployUI: REMOTE_HOST по умолчанию = player@shineup.me.

- VERSION.properties: client 1.2.107, server 1.2.100.
2026-05-31 20:36:34 +04:00
AidarKC e3c1cbf1c0 Обновить UI каналов, логаут DM и документацию
- Исправлена вкладка Каналы: стабильные режимы Все/Мои, корректные кнопки и навигация назад.

- Зафиксирована доработка по личным сообщениям: при logout очищается локальная база/кеш DM на устройстве.

- Обновлены AGENTS/CLAUDE и документация Personal_Messages.

- Обновлены версии в VERSION.properties (client 1.2.106, server 1.2.99).
2026-05-31 20:30:31 +04:00
AidarKC 5899bd2f77 Убрал long-press меню каналов и обновил deploy-проверку sudo 2026-05-31 19:30:36 +04:00
AidarKC 1b0e1cf1d4 refactor: перенос серверных модулей в папку SHiNE-server 2026-05-30 17:12:15 +04:00
AidarKC 134e877b7c chore: состояние проекта перед переносом сервера в SHiNE-server 2026-05-30 17:10:21 +04:00
AidarKC b75ac46781 Кошелёк Сияния: фактический расход с сервера, доработка UI и topup через compute budget 2026-05-30 11:53:36 +04:00
AidarKC edc94d3700 WIP: кошелек запускается, но есть ошибки 2026-05-30 11:24:59 +04:00
AidarKC b13efa92fd Добавить адаптацию голосовых ответов бота 2026-05-30 00:16:39 +04:00
AidarKC 6f796c98f7 Уточнить голосовые ответы агента 2026-05-29 23:53:59 +04:00
AidarKC 3a5856c7f0 Добавить кошелек блокчейна и озвучивание агента 2026-05-29 23:48:44 +04:00
AidarKC 775b655aac Логин guard: корректный precheck, company приоритет, hp в trademarks; подробные ошибки UI 2026-05-27 22:15:54 +04:00
AidarKC 101fd2eaa4 Solana-first регистрация: lazy-import пользователя при входе, AddUser отключен, UI ожидание 15с 2026-05-27 18:38:45 +04:00
AidarKC 6f0bb01b61 Промежуточный коммит: состояние до нормальной Solana-first регистрации 2026-05-27 18:33:26 +04:00
AidarKC b345900459 docs: убрать выполненную задачу про кнопку создания канала из Future_Features 2026-05-26 00:46:58 +03:00
AidarKC f1cfe9b6aa UI: обновлена шапка каналов, закрыты pending-задачи и обновлены версии 2026-05-26 00:30:49 +03:00
AidarKC 8941582d54 chore: зафиксированы все текущие изменения проекта 2026-05-25 23:46:54 +03:00
AidarKC 8c5de781ea API: задокументирован rawBlockB64 в GetMessageThread и обновлены версии 2026-05-25 23:43:43 +03:00
AidarKC baef264bd0 Обновить формат Solana user PDA 2026-05-24 19:41:13 +03:00
AidarKC 74df7e2645 Добавить документацию Solana PDA и ESP32-подпроект 2026-05-24 19:29:42 +03:00
AidarKC 56cd90a197 Отключить репосты и добавить Solana-модуль 2026-05-24 12:16:39 +03:00
AidarKC abdce05136 Удалить Java-реализацию агента-кодера 2026-05-24 09:30:25 +03:00
AidarKC 35565845ca Добавить канальный режим агента-кодера 2026-05-24 09:25:25 +03:00
AidarKC a83ec2c971 Обновить сервис агента-кодера 2026-05-24 09:21:50 +03:00
AidarKC 4b371e142d Документировать API и сервис агента-кодера 2026-05-24 08:04:44 +03:00
AidarKC aa2644d812 fix: считать шаг линии через lineStep перед AddBlock 2026-05-21 16:57:31 +03:00
AidarKC fd99250882 feat: добавить репосты сообщений в каналах и тредах 2026-05-21 16:16:26 +03:00
AidarKC 5344c42ceb chore: очистить Pending_Features и обновить версии 2026-05-21 15:03:28 +03:00
AidarKC 21413268f3 Добавил гостевой режим, единые shine-ссылки и пометку о нестабильности мнений 2026-05-20 16:14:59 +03:00
AidarKC aa35d87885 Добавить opinion-связи и обновить UI связей в профиле 2026-05-20 13:13:50 +03:00
AidarKC a53444b863 Перенести UI-деплой в /home/player/SHiNE/shine-ui 2026-05-19 22:10:19 +03:00
AidarKC 4b0031fb08 Исправить UI-деплой в /var/www и права rsync через sudo 2026-05-19 21:55:21 +03:00
AidarKC 62b8534769 Исправить устойчивость UI-деплоя и проверку Caddy 2026-05-19 21:50:05 +03:00
AidarKC f3262c2d64 Исправить edit/delete сообщений, упростить вкладки каналов и улучшить автоскролл DM 2026-05-19 21:00:29 +03:00
AidarKC 7986184111 chore(deploy): новый прод-контур 93.170.12.154, единые deploy task и docs 2026-05-19 16:27:09 +03:00
AidarKC 9c35567389 fix(dm): Ctrl+Enter перенос, время в списке и выравнивание карточек 2026-05-19 16:13:44 +03:00
AidarKC 8325cbec84 UI: DM список метаданных и Enter/Ctrl+Enter в чате 2026-05-19 15:50:42 +03:00
AidarKC c6d310184b UI: упростить профиль и обновить UX чатов/шапок 2026-05-19 15:34:46 +03:00
AidarKC 83892d5093 UI: поднята фиксированная шапка в канале и треде 2026-05-19 15:05:29 +03:00
AidarKC 1e1cdd9e76 UI: шапка channel owner/name и унификация карточек треда 2026-05-19 14:22:28 +03:00
AidarKC 3e62a2a01c UI: компактная тёмная плитка автора и корректный переход в профиль 2026-05-19 14:18:20 +03:00
AidarKC 90d10086d7 UI: карточка автора в канале, профиль user и назад по истории 2026-05-19 13:58:49 +03:00
AidarKC db2d9a666b UI: переход на history-router без # и короткие ссылки тредов 2026-05-19 10:15:15 +03:00
AidarKC 3a0899bcfe feat(ui): короткий роут m для тредов и восстановление заголовка канала 2026-05-19 01:05:25 +03:00
AidarKC d13c60fca1 fix(thread): открывать ответ через channel-thread-view с hash 2026-05-19 00:49:22 +03:00
AidarKC 580bd6fbeb feat(thread): переход в тред ответа и явная история сверху 2026-05-19 00:47:12 +03:00
AidarKC 49ebf1605a fix(ui-thread): корректные поля channel для GetChannelMessages 2026-05-19 00:42:00 +03:00
AidarKC f1fbb35296 fix(ui): открыть вкладку каналов по умолчанию и исправить резолв owner/channel 2026-05-19 00:41:19 +03:00
AidarKC b85643ca33 docs(blockchain): актуализировать MVP-формат и правила изменения протокола 2026-05-19 00:23:10 +03:00
AidarKC c27da63a3e chore: зафиксированы оставшиеся локальные изменения 2026-05-19 00:07:49 +03:00
AidarKC a332ddc828 UI: отключение вкладки чатов и исправление загрузки каналов 2026-05-19 00:04:34 +03:00
AidarKC ab31ccf6d8 UI: каналы 1..32, публичный type=1 и актуальный prevLine перед записью 2026-05-14 17:58:16 +03:00
AidarKC 01b38952e5 UI: исправить каналы и добавить MCP-док по чтению/дозаписи 2026-05-14 17:35:54 +03:00
AidarKC 0fdb5b245c Откат мультисессии: возвращен один активный сеанс 2026-05-14 17:11:24 +03:00
AidarKC 94263a46bd UI: мультиаккаунты профиля и улучшенный поиск каналов 2026-05-14 16:28:17 +03:00
AidarKC 56a69ab683 UI: отправка UI-ошибок, персональный публичный чат, русские pending-файлы 2026-05-14 14:16:03 +03:00
AidarKC e73e103ac4 UI: заглушки уведомлений и правило intake в AGENTS 2026-05-14 13:32:53 +03:00
AidarKC b8b33696ec Docs: добавить правило push через GITEA_TOKEN 2026-05-14 12:49:10 +03:00
AidarKC c0b0c99f53 UI: обновить thread/counters, вкладку Каналы и сценарий просмотра+подписки 2026-05-14 12:46:22 +03:00
AidarKC a2954071bd Channels/CreateChannel: выровнена версия формата на v3 + legacy fallback 2026-05-13 02:47:43 +03:00
AidarKC 76e4a6cba0 UI/Channels: вкладки по тапу + CreateChannel fallback для legacy формата 2026-05-13 02:42:57 +03:00
AidarKC b55fd1571e Auth/UI: Argon2id derivation для login/register + блок Расширенные 2026-05-13 02:10:42 +03:00
AidarKC 8de4e95c6a UI: голосовой ввод/STT, TTS через OpenAI, настройки инструментов + учёт недопроверенных фич 2026-05-13 02:01:51 +03:00
AidarKC ddeaf82bfd CreateChannel: оставить единый актуальный формат, убрать legacy v2/v3 2026-05-13 01:22:07 +03:00
AidarKC e95f65ac78 Каналы: типы 0/1/100/200, CreateChannel v3, state для chat200, новые API и деплой на prod 2026-05-13 01:17:23 +03:00
AidarKC 97d37a2eb6 12-05-2026
Перенёс репо
2026-05-12 23:26:27 +03:00
AidarKC a23d090bc1 Канал root 0 переименован в news в API и документации 2026-05-08 20:10:02 +03:00
AidarKC 4956ba7352 Ужесточение имен каналов и удаление legacy USER_PARAM для описания 2026-05-08 19:06:58 +03:00
AidarKC acdd6c928b Каналы: новый роутинг, поиск, вход-возврат, удаление просмотров и документация 2026-05-08 01:15:54 +03:00
AidarKC 6774c26ea1 Звонки: preflight сессии перед вызовом и retry; таймаут вынесен в настройки 2026-05-05 18:11:55 +03:00
AidarKC ef0cd2cb7d deployServer: проверка sudo -n только для systemctl сервиса 2026-05-02 19:06:31 +03:00
AidarKC e921b06826 Звонки: дольше показывать статус занято; deployServer проверяет sudo -n 2026-05-02 19:05:54 +03:00
AidarKC c0c29b74ab Звонки: WebPush incoming/stop, actions и TTL; обновлена логика 2026-05-02 18:25:44 +03:00
AidarKC 310863faec Checkpoint: первая рабочая версия звонков, сигналинг будет переделан 2026-05-02 18:13:22 +03:00
AidarKC b05da86197 Удалён AckIncomingMessage и обновлена документация доставки 2026-05-02 17:02:57 +03:00
AidarKC c44d755ce0 Добавлен TODO по будущим доработкам доставки почты 2026-05-02 16:52:38 +03:00
AidarKC b7e6cf7437 Почта v2: ReceiveOutcomingMessage без авторизации и атомарная вставка пары 2026-05-02 16:46:22 +03:00
AidarKC e73328461e Звонки: выбор одной callee-сессии и авто-закрытие входящего на других устройствах 2026-05-01 19:26:32 +03:00
AidarKC db93eace30 WebRTC: строгая обработка сигналов (ICE до PC в очередь, ANSWER только для outgoing) 2026-05-01 18:16:01 +03:00
AidarKC c5dfa47903 WebRTC: очередь ранних ICE, игнор дублирующего ANSWER и деплой UI 1.2.30 2026-05-01 18:08:43 +03:00
AidarKC d96985303d Звонки/UI: освобождение микрофона после звонка, детальнее failed и возврат в Настройки после принудительного обновления 2026-05-01 18:05:39 +03:00
AidarKC 27bd47dbe0 Звонки: фиксы session fallback/registry, аналитика ICE/TURN, авточек UI-версии и перенос кнопки разработчика 2026-05-01 17:42:51 +03:00
AidarKC e3377a48b3 Звонки: расширенная диагностика + экран настроек разработчика + обновление TURN-конфига 2026-05-01 16:39:03 +03:00
AidarKC a2ed41514d Деплой UI: обязательная подстановка client.version из VERSION.properties 2026-05-01 16:09:01 +03:00
AidarKC 78ee5a60fa UI: кнопка помощи при проблеме обновления клиента + инкремент версий 2026-05-01 15:36:04 +03:00
AidarKC bff403ea04 CallDeliveryReport: универсальный формат type/value и расширенные отчёты по звонкам 2026-05-01 15:09:20 +03:00
AidarKC 3061bf3d1e Добавлен SHiNE-promo-solana-devnet в основной репозиторий без вложенного git 2026-05-01 13:58:59 +03:00
AidarKC 9b03273055 Промо devnet: вечный код 0000, сообщение после пополнения и деплой-задачи 2026-05-01 13:55:29 +03:00
AidarKC bdcab5ee05 Регистрация: wallet.key работает и с пустым паролем 2026-04-27 02:20:02 +03:00
AidarKC 14cc3be620 Регистрация: проверка реального баланса wallet.key перед продолжением 2026-04-27 01:50:41 +03:00
AidarKC 50da3e868d Регистрация Solana: промо-topup URL с wallet(base58) и порог 0.01 SOL 2026-04-27 01:44:07 +03:00
AidarKC 2c68dedea2 UI: фикс шапки связей и правки профиля (работает) 2026-04-26 20:34:12 +03:00
AidarKC da12521517 UI: обновлены профиль/связи, статусы отношений и фикс верхней панели (работает) 2026-04-26 19:13:08 +03:00
AidarKC 28bbdb8b7c style(ui): уплотнить профиль и растянуть экран связей 2026-04-26 18:57:26 +03:00
AidarKC 1e8e2915f9 feat(network): поиск, help, история центра и fullscreen PWA 2026-04-26 18:50:36 +03:00
AidarKC 2350745e61 feat(profile): разделить просмотр и редактирование профиля 2026-04-26 18:31:38 +03:00
AidarKC 1fec6c7b54 feat(relations): spouse 40/41 и новый UX вкладки Связи (проверено) 2026-04-26 18:24:30 +03:00
AidarKC 3e10407afd fix(auth): вход сразу по логину, регистрацию отправлять при любом балансе (проверено) 2026-04-26 18:13:37 +03:00
AidarKC 3d5b5b2214 fix(ui): восстановить показ аватаров и корректный fallback 2026-04-26 09:31:56 +03:00
AidarKC c094af920e fix(ui): исправить наложение fallback-букв на аватарах 2026-04-26 09:24:27 +03:00
AidarKC dafdae5276 Настройки: блок Для разработчиков и ручная загрузка аватара в Arweave 2026-04-26 02:46:18 +03:00
AidarKC df7f38bd0a Аватары: убрать инициалы при наличии txId и усилить загрузку старых файлов 2026-04-26 02:39:21 +03:00
AidarKC 4c1aeeeac8 Аватары: общий компонент, кэш txId и avatar.ar в графе связей 2026-04-26 02:27:41 +03:00
AidarKC 667c5310bf Добавить аватар профиля через Arweave и мастер загрузки 2026-04-26 01:41:09 +03:00
AidarKC 126cf2f5c3 Добавить SAWD-v1 и Arweave-кошелек в UI 2026-04-26 01:19:46 +03:00
AidarKC c8fa4a01a1 Добавить версионирование схемы БД и заблокировать прод-очистку БД 2026-04-26 00:32:10 +03:00
AidarKC e764a713c4 chore(deploy): перевести UI-деплой на белый IP 194.87.0.247 2026-04-23 20:16:12 +03:00
AidarKC 93aef6e18b UI: исправить автообновление и хронологию диалогов; обновить деплой-цели 2026-04-23 18:04:14 +03:00
AidarKC f213e9aa43 feat(update): server push-команда на массовое обновление UI и формат версий 2026-04-23 16:19:00 +03:00
AidarKC 630ba30c27 chore(version): bump client/server до 1.2.1 после merge каналов 2026-04-23 15:47:48 +03:00
AidarKC b2d3061796 Merge remote-tracking branch 'origin/user/channels-ui-read-unread-views-2026-04-23' 2026-04-23 15:46:44 +03:00
AidarKC ecb018b32c feat(version): единый файл версий и отображение client/server в настройках 2026-04-23 15:37:39 +03:00
AidarKC e9f58ca004 fix(ui): чат метаданные сообщений и корректная раскладка backlog type2 2026-04-23 13:41:13 +03:00
DrygMira c0dfa6c7ab Channels UI + read/unread + unique views + style polish 2026-04-23 13:36:01 +03:00
AidarKC 78d6124f2a feat(update): проверка версии UI через Ping без периодических опросов 2026-04-22 19:57:59 +03:00
AidarKC 1a8d1c70fd feat(ui): зелёная кнопка ответа и автообновление PWA 2026-04-22 19:49:32 +03:00
AidarKC e0333a9c32 chore(docs): восстановить случайно удалённый README 2026-04-22 19:26:05 +03:00
AidarKC 9a3bc9e488 fix(call): корректный mute, статус прямое/TURN и реавторизация WS 2026-04-22 19:25:52 +03:00
AidarKC a905822515 feat(call): серверная выдача ICE/TURN и подключение в WebRTC 2026-04-22 18:11:47 +03:00
AidarKC d7c7bb3c23 fix(call): кнопки входящего и автопереподключение после обрыва 2026-04-22 17:33:57 +03:00
AidarKC 97a2bee81a feat(ui): старт с личных сообщений и бейдж непрочитанных 2026-04-22 17:23:20 +03:00
AidarKC 58bbf063ca fix(ui): обработка устаревшей сессии и корректировка индикаторов соединения 2026-04-22 17:04:16 +03:00
AidarKC 0159dd9074 feat(ui): индикатор connected в тулбаре и панель переподключения 2026-04-22 16:53:36 +03:00
AidarKC 29a07a9a8b feat(call-ui): полноценное окно звонка, статусы, звуки и тех-история вызовов 2026-04-22 15:46:45 +03:00
AidarKC c824fb5e9b fix(debug-api): починить сборку после merge и унифицировать буфер debug-логов 2026-04-22 15:03:14 +03:00
AidarKC 6a9746c17a Merge branch 'main' of https://github.com/ai5590/SHiNE-server
# Conflicts:
#	shine-UI/js/app.js
2026-04-22 14:58:28 +03:00
ai5590andGitHub af0e9e51e0 Merge pull request #17 from ai5590/codex/add-temporary-debug-api-endpoints
Добавил времеенный энд поинт для возможности легко писать и тестировать звонки. Add temporary debug HTTP API and client debug reporting; UI WS reconnect and debug call tooling
2026-04-21 20:07:45 +03:00
ai5590 5713287c84 Добавлен флаг debug.tempApi.enabled для временных debug API 2026-04-21 20:06:51 +03:00
ai5590 bd0c3dba50 Добавлен временный debug API для автотеста WebRTC и runbook 2026-04-21 19:52:25 +03:00
AidarKC e63c53a855 feat(ui): кошелек на device.key и проверки серверов (тоже пока не проверено) 2026-04-21 03:12:22 +03:00
AidarKC 8be56192cb Добавить автообновление UI и нижний статус соединения 2026-04-21 01:58:46 +03:00
AidarKC d07602b0a9 Добавить диагностику PWA/Push и endpoint тестового push 2026-04-21 01:10:56 +03:00
AidarKC 185ba5b1d3 наверное работает 2026-04-21 01:04:05 +03:00
AidarKC 2d48ae7a16 Добавить деплой без clean/тестов и доработки PWA/сессии в UI 2026-04-20 20:33:20 +03:00
AidarKC bec1d08757 Исправлен ReferenceError chatId в chat-view при отправке read-receipt 2026-04-20 12:53:46 +03:00
AidarKC cc59bd18ee WIP: новая схема сообщений и push (не проверено) 2026-04-19 20:41:58 +03:00
AidarKC f0b560ec06 Связи UI: сияние и бейдж официального
- Добавлен визуальный эффект сияния вокруг круга для аккаунтов с флагом 'Сияющий'.

- Добавлен бейдж 'ОФ' над узлом для официальных аккаунтов.
2026-04-17 23:56:11 +03:00
AidarKC ba3ee4290f Добавлено тестовое заполнение и доработки входа
- Добавлено расширенное тестовое заполнение данных через Seed_TestDataPopulation и SeedDataPopulationHelper (включая базовую схему для пользователей 1, 2, 3, их связи и профили).

- Убраны лишние проверки из тестового заполнения: сид теперь только заполняет данные.

- Исправлен WsTestClient: корректная сборка фрагментированных WS-сообщений до полного JSON.

- На странице входа по логину добавлена подсказка про основные тестовые логины 1, 2, 3 (вход без пароля).
2026-04-17 22:26:37 +03:00
AidarKC 30fcde5744 Уточнён термин close friend и улучшен UX связей
Что сделано:\n- В UI возвращён термин «Близкий друг» без пометки про «друга».\n- На графе связей добавлено меню узла с двумя действиями: «Показать информацию» и «Показать связи» (перенос узла в центр).\n- В модалке добавления связи реализован автопоиск логинов: Enter или пауза 2 секунды, до 5 подсказок, выбор кликом.\n- Добавлены стили для меню узла и списка подсказок.\n- В коде добавлены явные пояснения и alias-константы close friend (без изменения кодов 10/11 и логики):\n  CONNECTION_CLOSE_FRIEND / CONNECTION_UNCLOSE_FRIEND.\n- Обработчики чтения/записи связей переключены на alias close friend для лучшей читаемости.
2026-04-17 21:18:03 +03:00
AidarKC 4a92a7fa22 Добавлены родственные связи, расширен граф связей и улучшен локальный запуск
Что добавлено:\n- Новые типы CONNECTION для родственников: parent/child/sibling (50/51, 52/53, 54/55) в blockchain/db слоях.\n- Обновлены проверки ConnectionBody и DB-триггер connections_state для корректной записи/удаления новых связей.\n- В профиле добавлен блок "Близкие родственники" с модальным выбором типа связи и логина; добавление через AddBlock для parent/child/sibling.\n- Расширен API GetUserConnectionsGraph: out/in списки для родителей/детей/сиблингов, агрегированные списки родственников с полом, список allUsers с метками официальный/сияющий.\n- Полностью обновлен UI страницы "Связи": новое позиционирование родственников вокруг центра, отдельный цвет родственных связей, линия для взаимных связей и стрелка для односторонних, корректная геометрия линий при ресайзе.\n- Добавлена Gradle-задача startLocalWithBuild для запуска локального стека после build; сохранена отдельная startLocal без полного build.
2026-04-17 21:01:53 +03:00
AidarKC 9b188d56e9 17-04-2026
Сделал красивыми кнопки для изменения параметров на вкладке профиль
2026-04-17 17:51:03 +03:00
AidarKC c7bf8051b9 17-04-2026
Добавил вкладку пол
2026-04-17 17:39:04 +03:00
AidarKC 7591fbdace 17-04-2026
Сделал что бы все пораметры пользователя получаличь врезодним запросом ListUserParams, а не по отдельности кучей разных
2026-04-17 15:49:36 +03:00
AidarKC 5d4d451943 merge: codex/outline-call-request-workflow into main 2026-04-16 01:27:48 +03:00
AidarKC 828820b6e4 14-04-2026
Веб пуш работает. Дальше попробую звонки добавить.
2026-04-15 22:38:43 +03:00
ai5590 eaad476bf5 Add MVP call signaling API and browser call flow 2026-04-15 09:33:37 +03:00
AidarKC 0b7691bdea 14-04-2026
Веб пуш работает. Дальше попробую звонки добавить.
2026-04-15 00:50:25 +03:00
AidarKC 21fbc8ffa0 14-04-2026
Промежуточный комит версии в которой ну хоть какието тестовые уведомления приходят. Но пока ещё вебпуш не работает
2026-04-14 23:53:54 +03:00
AidarKC 24be1d0c1f merge: add-web-push into main
# Conflicts:
#	shine-UI/js/router.js
#	shine-UI/js/services/auth-service.js
#	shine-UI/js/state.js
2026-04-14 22:35:28 +03:00
AidarKC 2830f75f65 merge: drygmira/main into main 2026-04-14 22:05:18 +03:00
AidarKC c4a3ff6f68 14-04-2026
То что дела ай и то во что надо влить изменнеия
2026-04-14 21:55:57 +03:00
AidarKC cfc92beec0 14-04-2026
То что дела ай и то во что надо влить изменнеия
2026-04-14 21:51:16 +03:00
DrygMira cdfc416d02 Finalize remaining Channels updates 2026-04-14 13:49:03 +03:00
DrygMira e17a6765ec Fix channel/author subscription confirmation and follow trigger persistence 2026-04-14 13:47:59 +03:00
DrygMira 7bdb3118ae Channels UI surgical cleanup and create description save fix 2026-04-14 10:36:50 +03:00
DrygMira 126b4ba3a1 channels ux cleanup and create-flow recovery 2026-04-14 02:08:44 +03:00
DrygMira 07e57b8563 chore: merge remote main baseline 2026-04-13 23:01:27 +03:00
DrygMira a9c69e5947 feat: finalize channels fixes and runtime stability 2026-04-13 23:00:36 +03:00
DrygMiraandGitHub 1340c8e9c6 Initial commit 2026-04-13 19:51:35 +03:00
ai5590 62e55dbaec feat(dm): implement signed direct messaging with web push fallback 2026-04-12 19:34:55 +03:00
AidarKC 1ee2a1cf62 12 -04-2026
Сделал отдельную ветку для ai
2026-04-12 18:30:31 +03:00
AidarKC ad45e005f5 fix(ui): исправление мелкого бага деплоя
Исправлен скрипт deploy_shine-PWA.sh: версия сборки теперь проставляется в staged-копии и обновляется только __SHINE_BUILD_HASH__, чтобы не ломать index.html при деплое.
2026-04-07 21:29:48 +03:00
AidarKC 0c7d8fac02 07-04-2026
Сделал вкладку параметры пользователя РАБОТАЮЩЕЙ

Добавил
- Локальный запуск
- техническое задание 1 (доработать ui что бы RFYFKS работали)
2026-04-07 14:43:08 +03:00
AidarKC 0b7ad79032 ДОАВИЛ В ГРАДЛЕ ЛОКАЛЬНЫЙ ЗАПУСК 2026-04-07 14:25:49 +03:00
AidarKC e2a9caa07d Внёс изменения что бы постоянно не обновляло версию каждого JS файла и не создавало кучу шума 2026-04-07 14:01:29 +03:00
AidarKC 619d2145f9 Закомитил промежуточную почти работающую версию ... 2026-04-07 13:57:32 +03:00
AidarKC 3016d25f73 Закомитил промежуточную почти работающую версию ... 2026-04-07 13:57:09 +03:00
ai5590 4deaedf79f Trim profile param flow to AddBlock-only write path 2026-04-07 02:18:32 +03:00
AidarKC 3a412fcd51 Merge branch 'codex/add-personal-data-to-blockchain-api'
# Conflicts:
#	shine-UI/js/pages/profile-view.js
#	shine-UI/js/services/auth-service.js
#	task/2.md
2026-04-07 01:11:54 +03:00
AidarKC d9e61e7c5b добаил автозаполнение тестовых пользователей 2026-04-07 01:05:33 +03:00
ai5590 f3e4651bd5 Профиль: рабочие переключатели official/shine и подтверждение блокчейн-записи 2026-04-07 01:03:33 +03:00
AidarKC 9cbff47194 Добавил массовое тестовое заполнение через API: A1..A10 и дружеские связи
- Переименован тест в Seed_TestDataPopulation (не IT_07_*)\n- Тест создаёт пользователей A1..A10 через AddUser\n- Дружеские связи формируются через AddBlock (CONNECTION_FRIEND)\n- Добавлен контроль количества друзей (A1=5, A2=7, A3=3)\n- Тест включён в обязательный запуск всех IT и в suite\n- Обновлена TASKS-документация по тестовым логинам
2026-04-06 10:28:22 +03:00
ai5590 525627c972 Профиль: реальные UserParam данные в правой вкладке и обновление через сервер 2026-04-05 20:39:17 +03:00
AidarKC 6d8777da83 Merge remote-tracking branch 'origin/main' 2026-04-05 14:49:25 +03:00
ai5590andGitHub 8fb428ef0d Merge pull request #11 from ai5590/codex/add-personal-data-to-blockchain-api-1vsw7s
Docs: clarify `sessionId` format, recommend USER_PARAM keys for profile, and note missing direct-session RPC
2026-04-05 14:48:40 +03:00
ai5590 1bfe742278 task: добавлен русский чеклист проверки API-доков по USER_PARAM и sessionId 2026-04-05 14:48:26 +03:00
AidarKC 76b1131d95 Merge remote-tracking branch 'origin/main' 2026-04-05 14:43:11 +03:00
ai5590andGitHub 670e7e9743 Merge pull request #10 from ai5590/codex/add-personal-data-to-blockchain-api
docs: document profile user-param keys and session-target messaging gap
2026-04-05 12:40:11 +03:00
ai5590andGitHub ff911636c7 Merge pull request #8 from ai5590/codex/add-session-closing-notifications-and-pwa-support
Add PWA / FCM push support (frontend + server) with direct messages and delivery tracking
2026-04-05 12:39:49 +03:00
ai5590 61f4c1e115 docs: clarify user params profile keys and sessionId format 2026-04-05 12:39:21 +03:00
AidarKC 3ded1da707 Merge remote-tracking branch 'origin/codex/add-session-closing-notifications-and-pwa-support' 2026-04-05 12:20:33 +03:00
AidarKC c9e4b8dfbf Промежуточная версия
в которой надо дорабоать

1. Исправить ошибки и сделать что бы работала вторая слева вкладка. ТОесть АПИ для сервера я сделал (пока они возвращают весь список сообщений целиком - всем большим списком сообщений в канал - для мвп это устраивает,и по этому только три АПИ функции добавилось)

  Там какието ошибки на клиенте ( я только сгенерил код - но гдето вылетает) по UI можешь исправлять переделывать - моешь оставить калечное как есть - мне пока не важно. Важно увидить что каналы и сообщения и публичная переписка в каналах блокчейна работает

2. потестировать и сделать корректное завершение сессии (там есть глюки при завершении сесии)
2026-04-05 12:14:17 +03:00
ai5590andGitHub c08826e848 Merge pull request #9 from ai5590/codex/add-session-closing-notifications-and-pwa-support-7qeofz
Add PWA/FCM push, direct messaging and connections (client + server)
2026-04-05 12:13:17 +03:00
ai5590 bc8f4a0582 Add handoff task document for PWA, chats, and connections 2026-04-05 12:13:05 +03:00
ai5590 ad323d17a2 Add handoff task document for PWA, chats, and connections 2026-04-05 12:12:59 +03:00
ai5590 09566fdfde Add close-friend flow on network tab with server API 2026-04-05 12:12:55 +03:00
ai5590 91ed444c90 Allow first DM to any user and show real login in profile 2026-04-05 12:12:46 +03:00
ai5590 32c046233b Add WS push events, PWA/FCM scaffolding, and direct messaging MVP 2026-04-04 18:10:25 +03:00
AidarKC cf5460c5c7 Промежуточная версия
в которой надо дорабоать

1. Исправить ошибки и сделать что бы работала вторая слева вкладка. ТОесть АПИ для сервера я сделал (пока они возвращают весь список сообщений целиком - всем большим списком сообщений в канал - для мвп это устраивает,и по этому только три АПИ функции добавилось)

  Там какието ошибки на клиенте ( я только сгенерил код - но гдето вылетает) по UI можешь исправлять переделывать - моешь оставить калечное как есть - мне пока не важно. Важно увидить что каналы и сообщения и публичная переписка в каналах блокчейна работает

2. потестировать и сделать корректное завершение сессии (там есть глюки при завершении сесии)
2026-04-03 11:45:42 +03:00
ai5590andGitHub c25393e3b6 Задание которое надо доделать 2026-04-03 11:06:52 +03:00
AidarKC 8a83ac85d9 Промежуточная версия
в которой надо дорабоать

1. Исправить ошибки и сделать что бы работала вторая слева вкладка. ТОесть АПИ для сервера я сделал (пока они возвращают весь список сообщений целиком - всем большим списком сообщений в канал - для мвп это устраивает,и по этому только три АПИ функции добавилось)

  Там какието ошибки на клиенте ( я только сгенерил код - но гдето вылетает) по UI можешь исправлять переделывать - моешь оставить калечное как есть - мне пока не важно. Важно увидить что каналы и сообщения и публичная переписка в каналах блокчейна работает

2. потестировать и сделать корректное завершение сессии (там есть глюки при завершении сесии)
2026-04-03 11:04:59 +03:00
AidarKC 78e62997d1 Промежуточный комит для отдачи задания брату 2026-04-03 10:50:44 +03:00
ai5590 c0fba4af94 Add channels IT coverage, live UI loading, and runbook 2026-03-30 16:06:28 +03:00
ai5590 9723696b2c Start server-side channel read RPC handlers and simplify API spec 2026-03-30 14:32:15 +03:00
AidarKC eb5593c7be 30 03 25
Сделал адекватное отображение ключей / и при регистрации ключи спрашивают какие сохранять

(что то работает что то сложно)
2026-03-30 03:11:09 +03:00
ai5590andGitHub 089146a137 Merge pull request #5 from ai5590/codex/connect-ui-client-to-server-for-authentication-8njihj
Add AuthService, WS client and key-vault; implement session-based auth flow and update auth UI/pages
2026-03-30 02:25:59 +03:00
ai5590andGitHub d4c8201a88 Merge branch 'plus_ui' into codex/connect-ui-client-to-server-for-authentication-8njihj 2026-03-30 02:25:51 +03:00
ai5590 538ec8ec73 Auto-resume active session on app reload 2026-03-30 02:25:14 +03:00
ai5590andGitHub 52fa631733 Merge pull request #4 from ai5590/codex/connect-ui-client-to-server-for-authentication-xqvn1u
Add AuthService + crypto/key-vault + WS client and integrate real auth/session flows into UI
2026-03-30 02:16:35 +03:00
ai5590andGitHub 284e962910 Merge branch 'plus_ui' into codex/connect-ui-client-to-server-for-authentication-xqvn1u 2026-03-30 02:16:20 +03:00
ai5590 6ba7a54921 Restore registration step flow and create new session on login 2026-03-30 02:09:44 +03:00
ai5590andGitHub ecd059ced2 Merge pull request #3 from ai5590/codex/connect-ui-client-to-server-for-authentication
Implement authentication backend (AuthService, WS client, crypto, key vault) and integrate session flows in UI
2026-03-30 01:52:31 +03:00
ai5590 4f825e2a86 Derive root/device/blockchain keys from password SHA-256 2026-03-30 01:52:12 +03:00
AidarKC 1bf1c768dd 30 03 25
Удалил наверное устаревшую  документацию
2026-03-30 00:53:45 +03:00
AidarKC b33fa4aeaa 30 03 25
Добавил сайт с UI прямо сюда
2026-03-30 00:43:49 +03:00
ai5590andGitHub 889ce0d921 Merge pull request #2 from ai5590/codex/analyze-block-addition-functionality-and-create-api-docs
Документация: добавление API `AddBlock` и раздела Blockchain в Dev_Docs
2026-03-30 00:38:39 +03:00
ai5590andGitHub 1c9841b4a6 Merge branch 'main' into codex/analyze-block-addition-functionality-and-create-api-docs 2026-03-30 00:36:29 +03:00
AidarKC 99cf000f24 30 03 25
Добавил АПИ функцию которая возвращает информацию о версии сервера и о том что он работает
2026-03-30 00:34:16 +03:00
ai5590 3d780a2605 Split blockchain block-format docs by block type 2026-03-28 11:11:16 +03:00
AidarKC 1aabcf4d80 27 03 25
Доделал API функции для авторификации и работы с сессиями сервер и документ для разработчиков по

Авторификациии и серверам

Всё работает
2026-03-27 22:06:19 +03:00
AidarKC 51de9779e3 27 03 25
Доделал сервер и документ для разработчиков по

Авторификациии и серверам

Всё работает
2026-03-27 16:29:19 +03:00
AidarKC 2f9cf2bff1 27 03 25
Добавил документ для разработчиков (про сессии но не закончил) и исправил мекую ошибку с несопостовлениеминдексов 2
2026-03-27 14:59:52 +03:00
AidarKC 6d3719ba71 27 03 25
Добавил документ для разработчиков (про сессии но не закончил) и исправил мекую ошибку с несопостовлениеминдексов
2026-03-27 14:44:01 +03:00
ai5590andGitHub dabda362e6 Merge pull request #1 from ai5590/codex/create-dev_docs-folder-with-documentation
Добавил документацию от кодекса
2026-03-26 15:29:22 +03:00
ai5590 b23ecdfdf2 Add Dev_Docs with protocol, blockchain, and API design analysis 2026-03-26 15:27:59 +03:00
AidarKC 18bf5d65d7 Initial commit 2026-03-18 22:28:13 +03:00
AidarKC 37c36ffdba 19 02 25
сделал единый формат протокола в случае ошибок (Наверное сделал удобнее)
2026-02-19 18:06:03 +03:00
AidarKC c7440e2b5c 19 02 25
Лобавил пинг
2026-02-19 17:33:58 +03:00
AidarKC 6949fd8a2f 06 02 25
Сделал отдельную команду что бы всё на сервер заливать
2026-02-06 16:51:18 +03:00
AidarKC ef72719502 06 02 25
Сделал отдельную команду что бы всё на сервер заливать
2026-02-06 16:49:58 +03:00
AidarKC a647091a3f 06 02 25
Сделал отдельную команду что бы всё на сервер заливать
2026-02-06 16:47:28 +03:00
AidarKC b5706d3ed5 29 01 25
Зделал всё только на база64 без всяких урл сафе

Вроде всё работает
тест весь проходит
2026-01-29 19:29:08 +03:00
AidarKC 0b2bee0a3d 29 01 25
Удалил старые черновики
тест весь проходит
2026-01-29 18:30:55 +03:00
AidarKC 4cee326a25 28 01 25
додела запрос связи с друзьями
тест весь проходит
2026-01-29 17:33:30 +03:00
AidarKC bf4cecde05 28 01 25
додела запрос связи с друзьями
тест весь проходит
2026-01-29 17:28:50 +03:00
AidarKC 84bef3365e 28 01 25
доработки по запросу связи друг
тест пока не весь проходит
2026-01-29 17:26:54 +03:00
AidarKC 922c18db4b 28 01 25
Добавил запрос связей пользователя - друзей - для построения графа друзей

И тест добавил.

но тест пока не весь проходит
2026-01-28 22:31:20 +03:00
AidarKC 22fb35d1d4 28 01 25
Добавил запрос поиска пользователей по начаоу логина.
И тест добавил.

Все тесты проходят.
2026-01-28 21:23:01 +03:00
AidarKC ebf7c9f18e 28 01 25
Добавил запрос проверить есть ли в системе такой пользователь и получить его данные.

И тесты добавил.

Все тесты проходят
2026-01-28 20:33:06 +03:00
AidarKC 43b0efb4d3 23 01 25
Ура прошли все тесты новой версии авторификации!!

Осталось что то доделать поправить с лишними закрытиями сервака
2026-01-23 22:10:14 +03:00
AidarKC 4430615117 23 01 25
Промежуточный комит.
Ужас как устал сегодня, узнал что и запросы у меня постоянно закрывают сессию. Надо переделать
2026-01-23 21:52:45 +03:00
AidarKC e84c63c3d1 23 01 25
Сделал авторификацию новую через sessionKey

(Но пока тесты сессии падают)
2026-01-23 20:50:58 +03:00
AidarKC 580695b486 23 01 25
Сделал ещё более два поля в общем формате блоков блокчейна (перед самим блоком данных) и перед его цп

(все тесты проходят)
2026-01-23 17:49:13 +03:00
AidarKC 9f1ca37977 23 01 25
Сделал более понятный названия у интерфейса  BodyHasLine

(всё работает)
2026-01-23 13:05:29 +03:00
AidarKC c1964adb58 22 01 25
перенёс класс у вдругую папку
Вроде всё работает и тесты проходят.

И блоки добавляются все что надо для MVP
2026-01-22 02:19:25 +03:00
AidarKC ad5525d88b 22 01 25
увеличел количество тестовых запросов добавления блоков

И вроде всё работает и тесты проходят.

И блоки добавляются все что надо для MVP
2026-01-22 02:18:12 +03:00
AidarKC 98d478531b 22 01 25
добавил комент
Да вроде всё работает и тесты проходят.

И блоки добавляются все что надо для MVP
2026-01-22 02:09:28 +03:00
AidarKC a2495afa44 22 01 25
Да вроде всё работает и тесты проходят.

И блоки добавляются все что надо для MVP
2026-01-22 01:59:49 +03:00
AidarKC 3f5f94a53f 22 01 25
Да вроде всё работает и тесты проходят.

И блоки добавляются все что надо для MVP
2026-01-22 01:57:02 +03:00
AidarKC 97840a45d6 22 01 25
Счас попробую новое просто добавить от гпт
Патч работает добавление линий - ситуация сложная

тест падает
2026-01-22 01:20:51 +03:00
AidarKC 69cd33479b 15 01 25
Потч работает добавление линий - ситуация сложная

тест падает
2026-01-21 18:37:05 +03:00
AidarKC 376d42cd79 15 01 25
Доделал типы сообщений посты в линии и едиты на них.ответы на них
И ответы в другие блокчейны

(Все тесты тесты проходят)
2026-01-15 18:55:03 +03:00
AidarKC b69075cbac 15 01 25
Добавил мелких доп проверок

(Все тесты тесты проходят)
2026-01-15 15:24:25 +03:00
AidarKC bbca821dcd 15 01 25
Исправил что бы в интерфейсе BodyHasTarget не требывалось хранить в блоках  BodyHasTarget
и в блоках коннекстин не зранилась поле тоЛогин в байты блока.

(Все тесты тесты проходят)
2026-01-15 15:13:29 +03:00
AidarKC d9fe1f02b8 15 01 25
Исправил что бы в интерфейсе BodyHasTarget не требывалось хранить в блоках  BodyHasTarget
и в блоках коннекстин не зранилась поле тоЛогин в байты блока.

(Все тесты тесты проходят)
2026-01-15 15:09:45 +03:00
AidarKC 5fe41c7656 13 01 25
мелкие исправления. Убрал оставшиеся странные связи линии
2026-01-13 17:54:10 +03:00
AidarKC 9cf6fabe64 13 01 25
мелкие исправления 2
2026-01-13 17:46:23 +03:00
AidarKC cd0352f904 13 01 25
мелкие исправления
2026-01-13 17:34:30 +03:00
AidarKC fa30bd2a49 13 01 25
Перевёл блокчен на новый формат!
Все тесты проходят!!

Может в каких то деталях/мелочах ещё что то не так (не смотрел подробно), но в общем выглядит всё хорошо.

И главное работает!
2026-01-13 16:28:30 +03:00
AidarKC e9e05c1192 13 01 25
Переписал код кучи классов перешёл на новый надеюсь теперь подходящий формат блоков

и тесты переделал.

Но пока остались баги и тесты не проходят (в частности пользователи не создаются - ошибка в бд)
2026-01-13 16:18:38 +03:00
AidarKC b7025dde59 13 01 25
Запрос подписок, но это версия уже не актуальна тк дальше буду переделывать блоки под новый формат
2026-01-13 12:44:45 +03:00
AidarKC 973a632b85 09 01 25
На этом с форматом разобрались и отложили всё на праздничные выходные
2026-01-10 01:47:00 +03:00
AidarKC 9d0da4b39f 08 01 25
Список каналов возвращает - хотя сырое всё как то - но всё работает :)
2026-01-09 00:56:05 +03:00
AidarKC aba86fc687 08 01 25
ещё помелочи
2026-01-09 00:03:40 +03:00
AidarKC 4c87207129 08 01 25
Помелоги поменял
2026-01-08 23:32:17 +03:00
AidarKC 7a167b470a 08 01 25
Сделал что бы при создании пользователя передавались три ключа пользователя. И имя блокчейна сделал через "-"
2026-01-08 15:02:01 +03:00
AidarKC 1ea5390771 08 01 25
Переименовал везде loginKey в solanaKey
2026-01-08 14:44:47 +03:00
AidarKC a218f6586d 08 01 25
Навёл порядок в тестах. и доки дописал.
Всё красиво и работает!
2026-01-08 14:25:37 +03:00
AidarKC 4753b83831 08 01 25
Навёл порядок в тестах.
Всё красиво и работает!
2026-01-08 14:21:55 +03:00
AidarKC a2626dfdd0 08 01 25
Добавил счётчик сколько раз изменялось сообщение
2026-01-08 14:13:44 +03:00
AidarKC 8e19486cf5 08 01 25
Тесты почти переделал
2026-01-08 14:12:16 +03:00
AidarKC e2b89da2fa 08 01 25
Вынес константы, начал переделаывать тесты
2026-01-08 13:24:55 +03:00
AidarKC f1af2bd4d4 07 01 25
вынес константы SHiNe
2026-01-08 00:02:43 +03:00
AidarKC 1c94bb25a6 07 01 25
сделал тест и он работает на то что бы изменить тект сообщения
2026-01-07 23:50:16 +03:00
AidarKC 06c77b1c1f 07 01 25
refactor: перевели хэши на BLOB и добавили поля block_hash / block_signature / edited_by_block_global_number

и главное добавили тип блока изменение сообщение и сслку на последнее изменение в табл блокс
2026-01-07 19:58:50 +03:00
AidarKC 8bcaa192c5 05 01 25
Добавил текст описание форматов блокчейна
2026-01-07 18:16:14 +03:00
AidarKC 8fd7f4676b 05 01 25
поменял все названия таблиц и полей в таблицах на стиль только маленькие буквы и разделение через "_"  . Все тесты проходят норм
2026-01-06 01:49:26 +03:00
AidarKC 93c007b2b9 05 01 25
добавил таблицу message_state и тригеры который считает в ней актуальное количество всех лайков и ответов на сообщения! (и всё рабоатет - тесты проходят)
2026-01-06 01:11:29 +03:00
AidarKC eb922d918b 05 01 25
добавил таблицу connections_state и тригер который ведёт в ней актуальное состояние всех связей! (и всё рабоатет - тесты проходят)
2026-01-06 00:30:37 +03:00
AidarKC 7ba333bf6c 05 01 25
добавил новые типы связи - тоесть возможность добавлять убирать друга, контакт или подписку  (и тесты и всё работает)
2026-01-06 00:24:24 +03:00
AidarKC 94777c58c6 05 01 25
Документы: Описание БД и всех запросов актуализировал!
2026-01-05 17:17:39 +03:00
AidarKC a6a5089379 05 01 25
Запрос для работы с параметрами пользователя работают!! И тесты на них проходят!!
2026-01-05 16:45:37 +03:00
AidarKC eb122456ab 05 01 25
Дабавил и два запроса на получение параметров, но пока не работает
2026-01-05 16:42:32 +03:00
AidarKC 55d34e2a87 05 01 25
Дабавил и два запроса на получение параметров, но пока не проверял
2026-01-05 16:14:14 +03:00
AidarKC bfffe44c4a 05 01 25
Дабавил соранение параметра пользователя. (бд и запрос на добавление), но пока не проверял
2026-01-05 16:11:54 +03:00
AidarKC dd49c4de00 02 01 25
Сделал что бы в базу писался msgSubType и поля to (to login, toBlockchainName и т.д.)
2026-01-02 20:15:59 +03:00
AidarKC eef760d776 02 01 25
улучшил скрипт склейки файлов (что бы он сразу в буфер кидал результат)
2026-01-02 19:52:53 +03:00
AidarKC fa019bcb4f 02 01 25
ОГО доделал и тесты (теперь два пользователя добавляется и меж ними есть связи) вроде всё работает
2026-01-02 19:09:56 +03:00
AidarKC 432b574592 02 01 25
ОГО доделал и тесты (теперь два пользователя добавляется и меж ними есть связи) вроде всё работает
2026-01-02 19:09:17 +03:00
AidarKC c3d20ba338 02 01 25
Доделал тесты и названия линий сделал в константы

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2026-01-02 18:52:19 +03:00
AidarKC be7a3ab7a6 02 01 25
Добавил боди для параметров пользователя

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2026-01-02 18:44:35 +03:00
AidarKC 272d7ca1be 02 01 25
Добавил боди для связей

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2026-01-02 18:16:59 +03:00
AidarKC 05a4714fb1 02 01 25
Добавил боди для связей

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2026-01-02 17:18:13 +03:00
AidarKC 59e5df0dd3 02 01 25
Сделал тесты с ответами на сообщения

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2026-01-02 17:06:07 +03:00
AidarKC 771758c831 02 01 25
Переделал тест добавления блоков в новый формат ( стло удобнее)

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2026-01-02 16:54:13 +03:00
AidarKC ca55bfca93 02 01 25
Добавил поле subType и исправил мелкие баги (все тесты работают)

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2026-01-02 16:42:15 +03:00
AidarKC 71f1a6179c 31 12 25
Стабильная версия сервера  0.2
Пакует блокченй в файл и бд, проверяет форматы.

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
2025-12-31 21:50:16 +03:00
AidarKC c13940216b 31 12 25
тест
2025-12-31 21:41:58 +03:00
AidarKC f17d077f25 31 12 25
Сделал что бы запускалось. Поправил мелкие ошибки
2025-12-31 21:10:05 +03:00
AidarKC 62ea49d1fc 30 12 25
Ура работает всё под новую таблицу. И все тесты проходят!!
2025-12-30 13:03:03 +03:00
AidarKC f653689112 30 12 25
Ура работает всё под новую таблицу 2
2025-12-30 12:55:21 +03:00
AidarKC df03f3f4ba 30 12 25
Ну типо переделал Всё под короткую таблицу солана юзерс, но теперь не надо поправить баги
2025-12-30 12:41:06 +03:00
AidarKC 34e8640e78 30 12 25
Ну типо переделал Всё под короткую таблицу солана юзерс, но теперь не надо поправить баги
2025-12-30 12:39:55 +03:00
AidarKC b6b50557a7 29 12 25
добавил toString для классов Body
2025-12-29 15:31:20 +03:00
AidarKC 08d90b6e8e 29 12 25
исправил микро баг
2025-12-29 15:10:14 +03:00
AidarKC 43a26007d6 29 12 25
Тесты работают - запускаются из специальной кнопки - возле билд.

Не совсем всё по стандарту но и это очень хорошо и удобно!
2025-12-29 15:03:42 +03:00
AidarKC 7fdc890a85 29 12 25
В общем тесты работают. Только запускать их надо из специального класса :)
2025-12-29 14:51:13 +03:00
AidarKC ae3838ccf2 29 12 25
Ура линии работают тесты прошли! Третий тест в лог выводиться!
2025-12-29 14:14:53 +03:00
AidarKC 783b5b08e3 29 12 25
Ура линии работают тесты прошли! 2
2025-12-29 14:07:20 +03:00
AidarKC 015caec01c 29 12 25
Ура линии работают тесты прошли!
2025-12-29 13:53:22 +03:00
AidarKC 526e2d9cc4 28 12 25
Всё ещё не работает проверка линий.
Переделываю тесты понял что нетак в сервере. Дальше буду исправлять сервак.
2025-12-29 13:33:26 +03:00
AidarKC 3f374f48e1 28 12 25
Всё ещё не работает проверка линий.
Переделал первые два теста! Третий (АддБлокс) ещё не работает
2025-12-29 13:16:00 +03:00
AidarKC 795341dd8d 28 12 25
Всё ещё не работает проверка линий. Залил старые тесты какие есть - каке есть
2025-12-29 12:13:12 +03:00
AidarKC c523816cdf 28 12 25
Вроде как сделал работу с линиями :) но ещё не тестил
2025-12-28 20:11:31 +03:00
AidarKC b26e09904a 28 12 25
Старая версия. Вроде там тесты доделал что бы работали
2025-12-28 19:48:23 +03:00
AidarKC 1526392ca5 28 12 25
Старая версия. Вроде там тесты доделал что бы работали
2025-12-28 18:39:13 +03:00
AidarKC 809d897da6 26 12 25
Исправил баг что пир повторном подключении по одной сессии всё будет норм работать
2025-12-26 13:12:53 +03:00
AidarKC eeb8ee9069 25 12 25
Сделал три теста на общий формат
2025-12-25 17:16:15 +03:00
AidarKC 25aa57dc5e 25 12 25
Добавил восстановление на случай застрявших темп файлов.  Оно работает!  И уведомление админа о критических ощибках, если файлы блокчейна поврежденны
2025-12-25 17:08:08 +03:00
AidarKC 6c2449f623 25 12 25
Добавил восстановление на случай застрявших темп файлов. Оно работает!
2025-12-25 16:42:40 +03:00
AidarKC d8057807a3 25 12 25
Вроде заработало!!
2025-12-25 16:10:33 +03:00
AidarKC e532401a75 25 12 25
Добавил логгер в настройки.2
2025-12-25 16:05:25 +03:00
AidarKC f8cc12560e 25 12 25
Добавил логгер в настройки.2
2025-12-25 16:00:57 +03:00
AidarKC c8ee9925a1 25 12 25
Добавил логгер в настройки.Омталось созранение стате в бд поправить
2025-12-25 14:53:08 +03:00
AidarKC d460ea2952 25 12 25
Дорабатываю добавление блоков. Промежуточный комит2.Омталось созранение стате в бд поправить
2025-12-25 14:32:58 +03:00
AidarKC e1b2c62231 25 12 25
Дорабатываю добавление блоков. Промежуточный комит
2025-12-25 14:15:26 +03:00
AidarKC bead78b372 24 12 25
Дорабатываю добавление блоков.
2025-12-24 17:29:50 +03:00
AidarKC 4e14f300f9 24 12 25
Дорабатываю добавление блоков.
Убрал лишние старые классы
2025-12-24 17:11:29 +03:00
AidarKC 4759521176 24 12 25
Дорабатываю добавление блоков.
Улучшил класс записыватель в БД.
2025-12-24 17:01:10 +03:00
AidarKC a309b6f3ef 24 12 25
Дорабатываю добавление блоков. Ура добавилось.
Объеденил в один Хэндлер и сделал атомарную запись в БД.
2025-12-24 16:42:26 +03:00
AidarKC 834cf98ef9 24 12 25
Дорабатываю добавление блоков. Ура добавилось. Осталось порядок навести и добавление файлов сделать и откат.
2025-12-24 14:55:30 +03:00
AidarKC 80ea016687 24 12 25
Дорабатываю добавление блоков. Промежуточный исправления (не работают)
2025-12-24 14:22:50 +03:00
AidarKC 5ecaf67bcb 24 12 25
Дорабатываю добавление блоков. Поставил todo что доделать
2025-12-24 14:08:40 +03:00
AidarKC 33635886e0 23 12 25
Дорабатываю добавление блоков! Вроде всё.  (но ещё не проверял и тестов нету)
2025-12-24 11:05:25 +03:00
AidarKC bba4b7fb41 23 12 25
Дорабатываю добавление блоков! Вроде всё.  (но ещё не проверял и тестов нету)
2025-12-23 16:14:25 +03:00
AidarKC 26afcb892a 23 12 25
Дорабатываю добавление блоков! Вроде всё.Осталось ещё размер уточнить что без хэш и пподписи  2
2025-12-23 16:12:36 +03:00
AidarKC 9633e3528d 23 12 25
Дорабатываю добавление блоков! Вроде всё.Осталось ещё размер уточнить что без хэш и пподписи
2025-12-23 15:58:54 +03:00
AidarKC 62e4338e88 23 12 25
Дорабатываю добавление блоков!
2025-12-23 15:48:23 +03:00
AidarKC d949895fec 23 12 25
Сессии работают пользователи добавляются. И тесты красиво выводият коменты. Старый класс теста больше не нужен
2025-12-23 14:55:18 +03:00
AidarKC b5fa05a660 23 12 25
Сессии работают пользователи добавляются. И тесты красиво выводият коменты. Старый класс теста больше не нужен
2025-12-23 14:00:08 +03:00
AidarKC c515d5287e 23 12 25
Сессии работают пользователи добавляются. Плюс сделал автоматические тесты как положенно
2025-12-23 13:51:20 +03:00
AidarKC ae63a653c8 23 12 25
Прошли тесты на создание сессии - посути всё работает (но добавление блоков пока не работает)
2025-12-23 12:59:12 +03:00
AidarKC 03b6ff3c32 22 12 25
Не работающая версия в странном состоянии
2025-12-23 11:32:26 +03:00
AidarKC 935ffecbb0 19 12 25
Переделал и запросы и тесты (но ещё не тетил)
2025-12-22 13:16:17 +03:00
AidarKC c140e3aae4 19 12 25
Переделал и запросы и тесты (но ещё не тетил)
2025-12-22 13:15:39 +03:00
AidarKC 7f92dc5f51 19 12 25
Ещё поправил мелкие детали в библиотеке работы с БД
2025-12-22 12:47:17 +03:00
AidarKC 627321d4ae 19 12 25
Передел библиотеку работы с БД под только login и bchName
2025-12-19 14:08:05 +03:00
AidarKC 0c49cae055 19 12 25
Минисально переименовал классы (убрал лишнее _new)
2025-12-19 13:56:18 +03:00
AidarKC 3cafd29ee5 19 12 25
В тот день Вроде дописал добавление но так и не заработало
2025-12-19 11:06:25 +03:00
AidarKC 6c4d8cd51b 19 12 25
Добавил таблицу для хранения блоков
2025-12-18 17:24:22 +03:00
AidarKC d6d2bfeb73 19 12 25
Все ДАО получили перезагруженный метод для того что бы вызываться с передачей соединения и без передачи соединия
2025-12-18 15:58:43 +03:00
AidarKC 1b1da19d3d 17 12 25
Заработало
2025-12-18 15:37:28 +03:00
AidarKC 4fb6b10a97 17 12 25
Заработало блок добавляется в файл и в статус блокчена в БД!!
Но ещё надо сделать таблицу с записями :)    2
2025-12-17 19:17:10 +03:00
AidarKC e9c11d6b75 17 12 25
Заработало блок добавляется в файл и в статус блокчена в БД!!
Но ещё надо сделать таблицу с записями :)
2025-12-17 18:35:34 +03:00
AidarKC 2037ebaa8b 17 12 25
Ещё промежуточный комит верии - не работает :)   4
2025-12-17 18:17:21 +03:00
AidarKC 45a862b11f 17 12 25
Ещё промежуточный комит верии - не работает :)   3
2025-12-17 17:21:10 +03:00
AidarKC 29c6e5a0f6 17 12 25
Ещё промежуточный комит верии - не работает :)   2
2025-12-17 17:15:52 +03:00
AidarKC aa2caf1f10 17 12 25
Ещё промежуточный комит верии - не работает :)
2025-12-17 15:57:05 +03:00
AidarKC 8188b91f86 17 12 25
Промежуточный комит верии которая может быть работает :)
2025-12-17 13:32:54 +03:00
AidarKC eb37b43de4 17 12 25
Промежуточный комит верии которая может быть работает :)
2025-12-17 13:32:06 +03:00
AidarKC eaf1affb27 17 12 25
Промежуточная версия
2025-12-17 13:06:08 +03:00
AidarKC ab44cc5282 16 12 25
Промежуточная версия и ТУДУ на чём остановился
2025-12-16 17:56:36 +03:00
AidarKC 19c4fd6cd1 11 12 25
Вроде как то заработали
2025-12-11 17:52:13 +03:00
AidarKC 096246542d 11 12 25
Добавил Вывсти список активных сессий!!
2025-12-11 17:19:17 +03:00
AidarKC a6be7b75aa 11 12 25
Добавил Закрытие сессии
2025-12-11 16:56:53 +03:00
AidarKC 80ffba545a 11 12 25
Добавил поле authNonce(Вместо SessionPWD) при запросе авторизации
2025-12-11 16:22:12 +03:00
AidarKC dbf1f22bac 11 12 25
Сделал закрытие сесси сервером если пароль не верный
2025-12-11 16:03:46 +03:00
AidarKC 7f91c60d26 10 12 25
Переименовал классы в один стиль
2025-12-11 15:29:10 +03:00
AidarKC 7072882b0b 10 12 25
Вроде всё доделал. Авторификацию. (осталось закрытие сессии и получение списка активных сессии)
2025-12-10 16:18:32 +03:00
AidarKC 00fc9e3926 10 12 25
промежуточный не рабочий комит
2025-12-10 16:15:36 +03:00
AidarKC 95ec6ba037 10 12 25
доработал получение инфы о клиенте из соединения
2025-12-10 15:28:29 +03:00
AidarKC 87da6efbfb 10 12 25
Добавил таблицы для геолокации
2025-12-10 13:54:15 +03:00
AidarKC 2ab1bbc02c 10 12 25
Всё работает. Плюс чть улучшил тест работы геолокации
2025-12-10 13:20:24 +03:00
AidarKC 47c53c1a14 10 12 25
Попереименовывал классы и запросы для авторификации.
Вроде хоршо и наверное всё работает :) пока не тестил
2025-12-10 12:58:16 +03:00
AidarKC 888bb1595f 09 12 25
Авторификация работает и тест авторификации проходит.

(создание пользователя, два этапа создания сессии и рефреш сессии)
2025-12-09 20:04:18 +03:00
AidarKC 2ed4f6d666 09 12 25
В черновую переделал авторификацию
2025-12-09 19:12:37 +03:00
AidarKC 2b5fa16824 09 12 25
Исправил что бы во входящих запросах тоже был  payload
2025-12-09 18:34:17 +03:00
AidarKC 199769cac0 04 12 25 Версия авторификации где сервер выдовал сессион Ид 2025-12-05 17:36:02 +03:00
AidarKC c9bfa2d01a 04 12 25 Версия авторификации где сервер выдовал сессион Ид 2025-12-05 17:35:58 +03:00
AidarKC fc748a744c 04 12 25 2025-12-05 10:56:34 +03:00
AidarKC 5d8dd86c96 обновляю сетевые хэндлеры 2025-12-04 13:34:04 +03:00
906 changed files with 172229 additions and 3305 deletions
+70 -1
View File
@@ -1,4 +1,11 @@
## папки с данными создавайемыми при работе сервера
data/
logs/
logs
.understand-anything/
.gradle
.gradle-home/
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
@@ -40,4 +47,66 @@ bin/
.vscode/
### Mac OS ###
.DS_Store
.DS_Store
# временный debug token
.debug-token
# Локальные артефакты и секреты Solana-модуля
shine-solana/.git/
shine-solana/.git-local-backup/
shine-solana/.idea/
shine-solana/shine/.idea/
shine-solana/shine/.gradle/
shine-solana/shine/.anchor/
shine-solana/shine/.yarn/
shine-solana/shine/.vendor/
shine-solana/shine/node_modules/
shine-solana/shine/target/
shine-solana/shine/test-ledger/
shine-solana/shine/old_vers/
shine-solana/shine/program-keypair.json
shine-solana/shine/keys/
shine-solana/shine/validator.log
shine-solana/shine/doc/КОШЕЛЬКИ_DEVNET_ТЕСТ.md
shine-solana/shine/scripts/del/
shine-solana/shine/scripts/**/keypairs/
shine-solana/shine/scripts/**/runs/
shine-solana/shine/scripts/**/*.env
shine-solana/shine/scripts/**/TEMP_*.md
# Локальные артефакты и внешние материалы ESP32-подпроекта
ESP32/esp32-config-tool/
ESP32/**/.git/
ESP32/**/.idea/
ESP32-wallet/.idea/
ESP32/**/.arduino-build/
ESP32/**/official-demo/
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/**
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/lv_conf.h
ESP32/**/original-firmware/*.bin
ESP32/**/original-firmware/*.bin.sha256
ESP32/**/*.elf
ESP32/**/*.map
ESP32/**/*.merged.bin
ESP32/**/*.uf2
ESP32/**/*.o
ESP32/**/*.d
ESP32/**/*.a
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
server-backup/archive/**
!server-backup/archive/.gitkeep
# Локальная дев-обвязка Claude (дев-сервер shine-UI, сессии, планы) — не коммитим
.claude/
# Рабочие бэкапы/превью-ассеты UI — не для репозитория
*.bak.png
shine-UI/assets/navbar_preview.png
Generated
+1
View File
@@ -0,0 +1 @@
shine-server-server
+2
View File
@@ -13,6 +13,8 @@
<option value="$PROJECT_DIR$/shine-server-config" />
<option value="$PROJECT_DIR$/shine-server-crypto" />
<option value="$PROJECT_DIR$/shine-server-db" />
<option value="$PROJECT_DIR$/shine-server-geo" />
<option value="$PROJECT_DIR$/shine-server-log" />
<option value="$PROJECT_DIR$/shine-server-net-protocol" />
<option value="$PROJECT_DIR$/shine-server-net-server" />
</set>
Generated
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
+160
View File
@@ -0,0 +1,160 @@
# AGENTS
## Язык проекта
- По умолчанию использовать русский язык во всех пользовательских текстах и технических пояснениях.
- Пояснения к коммитам, PR и merge-запросам писать на русском языке.
- Комментарии в коде, встроенные справки, документацию и инструкции писать по возможности на русском языке.
## Примечание
- Если внешний инструмент/интеграция требует английский формат, допускается английский, но рядом желательно дать краткое пояснение на русском.
## Структура проекта (кратко)
- Серверный код SHiNE находится в папке `SHiNE-server/`.
- Код клиентского UI SHiNE находится в папке `shine-UI/`.
- Веб-панель администратора сервера (управление Solana PDA сервера) находится в `shine-UI/`:
- точка входа `shine-UI/server-ui.html`;
- остальные файлы серверного UI — в `shine-UI/server-ui/`.
- Локальный Telegram-бот агента-кодера находится в папке `SHiNE-agent-bot-coder/` и не является кодом основного серверного приложения.
- Solana/Anchor-модуль находится в папке `shine-solana/shine/` и ведётся отдельно от основного server/UI деплоя.
## Сервис агента-кодера
- В проекте есть локальный Telegram-бот-сервис агента-кодера в папке `SHiNE-agent-bot-coder/`.
- Сервис принимает сообщения из Telegram, ведёт историю диалога, ставит задачи в очередь и вызывает Codex CLI для обработки запросов по проекту.
- Автоматически читаемые инструкции для Codex внутри сервиса держать в `SHiNE-agent-bot-coder/AGENTS.md`.
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
## ESP32 UI homeserver
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
- Для нового ESP32 UI-прототипа homeserver использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
## Solana-модуль
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
- Модуль логически связан с SHiNE, но не должен автоматически подключаться к сборке или деплою основного сервера без отдельного решения.
- В Solana-модуле действуют локальные инструкции `shine-solana/shine/AGENTS.md`; при изменениях внутри модуля сначала читать их.
- В git добавлять исходники, lock-файлы, настройки проекта и документацию Solana-модуля, но не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги.
- Для Solana deploy/push использовать правила из локального `shine-solana/shine/AGENTS.md`; не смешивать deploy Solana-модуля с `deployServer`/`deployUI` основного проекта.
- Для регистрации пользователей в Solana (программа `shine_users`) единая актуальная инструкция по деплою/инициализации, адресам программ, и куда их прописывать в UI/сервере находится в:
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
- Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте.
- Актуальная архитектурная справка по устройству Solana-программ, PDA-счетам, ролям DAO и движению средств находится в:
- `Dev_Docs/Solana_Architecture/README.md`
- Документ формата пользовательской PDA-записи `shine_users` находится в:
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
## Документация блокчейна
- Актуальная документация по форматам блокчейна находится в `Dev_Docs/Blockchain/README.md`.
- Это точка входа (оглавление), рядом расположены детальные файлы по форматам, типам каналов и командным сообщениям.
- При любом изменении кода, связанного с блокчейном (формат блока, типы каналов, правила чтения/записи, команды), обязательно обновлять соответствующие документы в `Dev_Docs/Blockchain/`.
- Дополнительно обязательно вести `Dev_Docs/Blockchain/CHANGELOG.md`: дописывать изменения построчно с указанием даты/времени и хэша коммита, после которого внесено изменение.
- Перед любым изменением формата блокчейна обязательно заранее предупреждать пользователя, что формат будет изменён.
- Изменять формат блокчейна можно только после явного подтверждения пользователя (без подтверждения формат не менять).
- Добавление любых данных в блокчейн выполнять только через операцию `AddBlock`.
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
## Документация личных сообщений (DM)
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`.
- Точный байтовый формат DM находится в `Dev_Docs/Personal_Messages/Формат_DM_v1.md`.
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа:
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
- Логика личных сообщений в коде должна всегда соответствовать этим документам.
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
## Документация API сервера
- Актуальная документация по публичному JSON/WebSocket API сервера находится в `Dev_Docs/API/`.
- При любом изменении серверного API/эндпоинтов/операций `op` обязательно обновлять соответствующие документы в `Dev_Docs/API/`.
- Перед изменением самого серверного API обязательно явно предупредить пользователя, какие операции, поля запросов/ответов или коды ошибок будут изменены, и запросить отдельное подтверждение.
- Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду.
- Если добавляется новая операция `op`, нужно обновить общий список операций в `Dev_Docs/API/09_Operations_Index.md` или создать его, если файла ещё нет.
## Документация Figma
- Актуальная документация по переносу экранов SHiNE в Figma и обратному переносу из Figma в код находится в `Dev_Docs/Figma/`.
- Точка входа: `Dev_Docs/Figma/README.md`.
- Подробный рабочий регламент: `Dev_Docs/Figma/TRANSFER_UI_SCREENS.md`.
- Для экранов регистрации, входа и других чувствительных UI-flow по умолчанию переносить экраны в Figma по одному, а не пачкой, если пользователь отдельно не подтвердил иной способ.
## Версионирование
- Единый файл версий проекта: `VERSION.properties` (в корне репозитория).
- Перед каждым новым коммитом обязательно увеличивать версии в `VERSION.properties`:
- `client.version` — версия клиентского UI.
- `server.version` — версия серверной части.
- Базовое правило инкремента: `+1` по последнему числовому сегменту (patch), если не оговорено иное.
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
## Deploy
- Все документы и заметки по деплою хранить в папке `Deploy/`.
- Production-хост SHiNE: `player@shineup.me` (`178.208.64.62`).
- Второй production-хост SHiNE: `player@193.8.215.70` (`server2.shineup.me`).
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
- Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя.
- Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `server2.shineup.me`.
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`.
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`.
- Production server deploy: `./gradlew deployServerProduction`.
- Production UI deploy: `./gradlew deployUIProduction`.
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
- Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`.
- Для продовой настройки тестового Arweave-кошелька JWK-файл нужно хранить только на сервере, например: `/home/player/SHiNE/secrets/test-free-avatar-wallet.json`.
- Для этой временной фичи на проде должны быть заданы параметры `test.freeAvatar.walletJwkPath` и `test.freeAvatar.walletAddress` через серверный override-конфиг/секреты на хосте.
- После изменения продовых значений `test.freeAvatar.*` нужно заново выполнить серверный деплой или перезапуск сервера, чтобы настройки были перечитаны приложением.
- При таких изменениях в git допускается коммитить только документацию и код чтения настроек, но не сам JWK, не содержимое секрета и не реальные приватные ключи.
## Логи звонков (установка соединения)
- Специальный поток диагностики установки звонков идёт через `CallDeliveryReport` (клиент → сервер).
- На проде специальный файл для звонков:
- `/home/player/SHiNE/shine-server/logs/call-delivery-events.log`
- Общий серверный лог (и ротации) на проде:
- `/home/player/SHiNE/shine-server/logs/app.log`
- `/home/player/SHiNE/shine-server/logs/app.YYYY-MM-DD.log`
- Для анализа причин недозвона в первую очередь фильтровать записи по ключам:
- `CallDeliveryReport`
- `call_connected`
- `outgoing_failed`
- `incoming_failed`
- `call_busy`
- `call_declined`
- `unknown_error`
- В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
## Недопроверенные фичи (обязательно)
- Папка для учёта недопроверенных фич: `Dev_Docs/Pending_Features/`.
- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `Dev_Docs/Pending_Features/`.
- Рекомендуемый формат имени файла: `YYYY-MM-DD_HHMM_<short-feature-name>.md`.
- Имена новых файлов и краткие описания фич по возможности писать на русском языке.
- Внутри файла обязательно указывать:
- краткое описание фичи;
- что именно проверять;
- ожидаемый результат;
- статус (например: `pending`, `in_progress`, `done`).
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
- В `Dev_Docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
## Будущие фичи / TODO
- Папка для задач, сознательно отложенных на будущее: `TODO/`.
- Точка входа по планам: `TODO/README.md`.
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/` и тематическим подпапкам.
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `TODO/README.md`, затем при необходимости конкретные файлы из подпапок.
- Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя.
- Старую папку `Dev_Docs/Future_Features/` считать выведенной из использования и больше не использовать для новых записей.
- Если часть кода временно отключена или закомментирована, либо удалена как временная заглушка, в TODO-файле подробно описывать:
- какие файлы и участки отключены;
- что осталось в коде как заготовка;
- какие документы нужно обновить при возврате;
- с какого сценария продолжать разработку;
- из какого коммита брать последнюю полную реализацию.
## Коммуникация по новым задачам (обязательно)
- При получении нового задания сначала кратко пересказать задачу своими словами.
- До начала реализации задать недостающие уточняющие вопросы (если они есть).
- Если есть уместные идеи/улучшения — кратко предложить их; если полезных идей нет, ничего дополнительно не предлагать.
- Добавлять краткую оценку фичи (насколько это полезно/удачно по мнению исполнителя).
- После этого обязательно запросить подтверждение от пользователя, что задача понята верно, и только после подтверждения переходить к реализации.
- Если вопросов нет, явно написать в формате: «Я всё понял, начинаю делать?» и ждать подтверждения.
- Без подтверждения пользователя реализацию не начинать.
+18
View File
@@ -0,0 +1,18 @@
# Runbook для агента: тест сетевого соединения
## Быстрый цикл
1. Убедись, что сервер запущен.
2. Скажи пользователю: «Запусти двух клиентов и напиши “продолжай”».
3. По команде «продолжай»:
- вызови `GET /debug/ws/clients`,
- выбери 2 активные сессии (предпочтительно разных логинов),
- вызови `POST /debug/ws/connect`,
- получи `runId`.
4. Читай `GET /debug/ws/logs?limit=200&runId=<runId>` и сообщай прогресс.
5. Если неуспех — покажи ошибки, предложи перезапуск 2 клиентов и повтори.
## Формат взаимодействия с пользователем
- Старт: «Сервер готов. Запусти двух клиентов и скажи “продолжай”.»
- После старта run: «Тест запущен, runId=..., проверяю логи.»
- Успех: «Соединение установлено, вижу connected.»
- Неуспех: «Соединение не поднялось, причины: ... Предлагаю перезапустить клиентов и повторить.»
+11
View File
@@ -0,0 +1,11 @@
@AGENTS.md
@AGENT_DEBUG_RUNBOOK.md
## Обязательно читать при работе с UI
@shine-UI/AGENTS.md
## Справка по подпроектам
- При работе внутри `SHiNE-agent-bot-coder/` — читать `SHiNE-agent-bot-coder/AGENTS.md` и `SHiNE-agent-bot-coder/AGENT.md`.
- При работе внутри `shine-solana/shine/` — читать `shine-solana/shine/AGENTS.md`.
- При работе внутри `shine-UI/server-ui/` — читать `shine-UI/AGENTS.md`.
- При работе внутри `SHiNE-server/` — читать `SHiNE-server/AGENTS.md`.
+93
View File
@@ -0,0 +1,93 @@
# Production-серверы SHiNE
## Короткий ответ
По текущим данным репозитория у SHiNE описаны **два production-контура**:
- `player@shineup.me`
- домен `shineup.me`
- IP `178.208.64.62`
и
- `player@193.8.215.70`
- домен `server2.shineup.me`
- IP `193.8.215.70`
## 1. Основной production-хост
- SSH: `player@shineup.me`
- домен: `shineup.me`
- IP: `178.208.64.62`
- пользователь: `player`
- базовый путь: `/home/player`
Основные каталоги:
- проект SHiNE: `/home/player/SHiNE`
- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
- UI: `/home/player/SHiNE/shine-ui`
- данные: `/home/player/SHiNE/shine-server/data/`
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
Сервисы:
- `shine-server.service`
- `caddy.service`
Caddy:
- активный конфиг: `/etc/caddy/Caddyfile`
- UI root: `/home/player/SHiNE/shine-ui`
- `/ws` проксируется на `127.0.0.1:7070`
Deploy:
- `./gradlew deployServerProduction`
- `./gradlew deployUIProduction`
Правило:
- любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
## 2. Второй production-сервер
- SSH: `player@193.8.215.70`
- домен: `server2.shineup.me`
- IP: `193.8.215.70`
- пользователь: `player`
- базовый путь: `/home/player`
Роль:
- второй production-контур SHiNE;
- использовать как production-сервер, несмотря на исторические имена deploy-задач
`deployServerTest2` / `deployUITest2`.
Основные каталоги:
- проект SHiNE: `/home/player/SHiNE`
- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
- UI: `/home/player/SHiNE/shine-ui`
- данные: `/home/player/SHiNE/shine-server/data/`
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
## 3. Связанные публичные production-публикации на том же хосте
На этом же production-хосте есть отдельная публикация для `shine_payments`:
- каталог: `/home/player/sites/test-solana-tickets.shineup.me`
- домены:
- `https://test-solana-tickets.shineup.me`
- `https://test-solana-tickets.shiningpeople.ru`
Это не второй production-хост SHiNE, а отдельный сайт на том же сервере.
## 4. Какие серверы не считать production
Не production:
- `t1.shineup.me`
- `t2.shineup.me`
- `t3.shineup.me`
- `t4.shineup.me`
+35
View File
@@ -0,0 +1,35 @@
# Deploy
Подробности о том, где что задеплоено в SHiNE, нужно искать в папке `Deploy/`.
Эта папка служит краткой картой окружений:
- [TEST_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/TEST_SERVERS.md) — тестовые стенды;
- [PRODUCTION_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/PRODUCTION_SERVERS.md) — production-контур и связанные публичные публикации.
Ниже краткая сводка.
## Основные публичные контуры
- Production SHiNE:
- `player@shineup.me`
- домен `shineup.me`
- IP `178.208.64.62`
- Второй production SHiNE:
- `player@193.8.215.70`
- домен `server2.shineup.me`
- IP `193.8.215.70`
## Отдельный quad-devnet стенд
На отдельном VPS `178.208.90.249` подняты 4 независимых test/devnet-инстанса:
- `t1.shineup.me`
- `t2.shineup.me`
- `t3.shineup.me`
- `t4.shineup.me`
## Важно
- Production-контура SHiNE сейчас два: `shineup.me` и `server2.shineup.me`.
- `t1..t4.shineup.me` — это отдельные тестовые/devnet-контуры, не production.
- Любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
+124
View File
@@ -0,0 +1,124 @@
# Тестовые серверы SHiNE
Этот файл описывает тестовые стенды, которые сейчас фигурируют в проекте.
## 1. Исторический `test2`, теперь второй production-сервер
- SSH: `player@193.8.215.70`
- Домен: `server2.shineup.me`
- IP: `193.8.215.70`
- Назначение: второй production-контур SHiNE
Структура:
- каталог SHiNE: `/home/player/SHiNE`
- сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
- UI: `/home/player/SHiNE/shine-ui`
- данные: `/home/player/SHiNE/shine-server/data/`
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
Сервисы:
- `shine-server.service`
- `caddy.service`
Deploy:
- `./gradlew deployServer`
- `./gradlew deployServerTest2`
- `./gradlew deployUI`
- `./gradlew deployUITest2`
Примечания:
- этот хост больше не считать test-контуром;
- исторические имена deploy-задач `deployServerTest2` / `deployUITest2` сохранены, но сам хост считать production;
- задача `deployUITest2` по умолчанию выкладывает UI на `server2.shineup.me`, а не на `t2.shineup.me`;
- при описании окружений перечислять его как второй production-сервер.
## 2. Отдельный quad-devnet стенд `t1..t4`
- VPS: `178.208.90.249`
- пользователь: `player`
- назначение: 4 независимых SHiNE-инстанса на Solana `devnet`
Домены и логины:
- `server_t1` -> `https://t1.shineup.me`
- `server_t2` -> `https://t2.shineup.me`
- `server_t3` -> `https://t3.shineup.me`
- `server_t4` -> `https://t4.shineup.me`
Каталоги:
- `/home/player/t1/server`
- `/home/player/t1/UI`
- `/home/player/t2/server`
- `/home/player/t2/UI`
- `/home/player/t3/server`
- `/home/player/t3/UI`
- `/home/player/t4/server`
- `/home/player/t4/UI`
Подробная памятка на самом VPS:
- `/home/player/Agents.md`
Порты и systemd:
- `t1` -> `7101` -> `shine-t1.service`
- `t2` -> `7102` -> `shine-t2.service`
- `t3` -> `7103` -> `shine-t3.service`
- `t4` -> `7104` -> `shine-t4.service`
Что важно по конфигу каждого инстанса:
- отдельный `/home/player/tX/server/application.properties`
- `server.port=710X`
- `server.SHiNE.login=server_tX`
- `db.path=data/shine.sqlite`
- `solana.cluster=devnet`
- `solana.rpcUrl=https://api.devnet.solana.com`
- `server.ui.indexPath=/home/player/tX/UI/index.html`
- `server.info.url=https://tX.shineup.me`
UI каждого инстанса:
- живёт в отдельной копии `shine-UI`;
- использует свой `js/deploy-config.js`;
- по умолчанию смотрит именно на свой `tX.shineup.me`.
Caddy на стенде:
- конфиг: `/etc/caddy/Caddyfile`
- статика: `/home/player/tX/UI`
- `/ws` проксируется на `127.0.0.1:710X`
Operational-нюанс:
- при одновременных рестартах возможны `HTTP 429` от `api.devnet.solana.com`;
- поэтому сервисы `shine-t1..shine-t4` лучше перезапускать по одному, с паузой.
## 3. Что проверять первым делом
Для любого test-контура полезны такие быстрые проверки:
```bash
curl -I https://server2.shineup.me
curl -I https://t1.shineup.me
curl -I https://t2.shineup.me
curl -I https://t3.shineup.me
curl -I https://t4.shineup.me
```
Для quad-devnet VPS:
```bash
sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
```
Для второго production-контура:
```bash
sudo systemctl --no-pager --full status shine-server caddy
```
+28
View File
@@ -0,0 +1,28 @@
# AGENTS for ESP32
## Язык UI
- Для ESP32-скетчей и экранного UI использовать английский язык.
- Русский текст на экране ESP32 пока не поддерживается корректно: шрифтовой путь для кириллицы не считается рабочим.
- Если меняется UI-скетч, все пользовательские строки на экране должны оставаться английскими, пока ограничение не снято отдельной задачей.
## Синхронизация со спецификацией
- При изменении экранов, кнопок, переходов, статусов или текстов обязательно обновлять соответствующую спецификацию в `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/`.
## Сборка ESP32
- Основной способ проверки и прошивки скетчей для `ESP32-S3-Touch-AMOLED-2.16` - `main-device/burn.sh`.
- Не собирать эти скетчи напрямую через `arduino-cli compile` без `burn.sh`, потому что скрипт добавляет нужные локальные библиотеки и конфиги из `official-demo/examples/Arduino-v3.3.5/libraries`.
- Если сборка падает по `lv_conf.h` или `TouchDrvCSTXXX.hpp`, сначала проверять именно `burn.sh` и его `--library` пути, а не считать, что файл пропал из репозитория.
## Диагностика ESP32
- Последнюю сохранённую ошибку или диагностическую запись читать с устройства через USB serial monitor на `115200`.
- Базовая команда:
`arduino-cli monitor -p /dev/ttyACM0 --config baudrate=115200`
- После подключения отправлять одну из команд:
`last_error`, `last_diag` или `reg_diag`
- Для очистки сохранённой диагностики использовать:
`clear_error` или `clear_diag`
- При падениях в ветках регистрации и обновления PDA сначала читать именно `last_error`: запись хранится в NVS и может пережить перезагрузку устройства.
+119
View File
@@ -0,0 +1,119 @@
# ESP32-S3-Touch-AMOLED-2.16 Codex Guide
Этот файл переносится в другие проекты как готовая инструкция для Codex по этой плате.
## 1) Что это за плата
- Модель: `Waveshare ESP32-S3-Touch-AMOLED-2.16`
- MCU: `ESP32-S3` (flash 16MB, PSRAM 8MB)
- Экран: AMOLED, физически 480x480, углы скруглены (часть крайних пикселей может быть невидима)
- Touch: CST92xx
- IMU: QMI8658
- Аудио:
- DAC/вывод (динамик): ES8311
- ADC/вход (микрофоны): ES7210
## 2) Что уже установлено в этой среде
- Ubuntu
- `arduino-cli 1.4.0`
- `esp32:esp32` core `3.3.5`
- `esptool` из `~/.arduino15/packages/esp32/tools/esptool_py/5.1.0/esptool`
- USB порт платы: обычно `/dev/ttyACM0`
Проверка:
```bash
arduino-cli version
arduino-cli core list
arduino-cli board list
ls -l /dev/ttyACM0
```
## 3) Структура подпроекта (эталон)
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
- `original-firmware/` — backup/restore заводской прошивки
- `main-device/` — прошивки и `burn.sh`
- `reference/` — заметки и ссылки
## 4) Бэкап перед любыми экспериментами
```bash
cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
./backup_factory.sh
```
Ожидаемый результат:
- `factory-full-16mb.bin`
- `factory-full-16mb.bin.sha256`
Восстановление:
```bash
./restore_factory_backup.sh
```
## 5) Деплой (прошивка) — стандарт
Главный скрипт:
```bash
cd ESP32-S3-Touch-AMOLED-2.16/main-device
./burn.sh <mode>
```
Режимы:
- `hello` — базовый экран
- `widgets` — экран+touch+IMU (официальный пример)
- `audio` — тест аудио тракта
- `simple` — кастомный интеграционный тест (экран, touch, запись/воспроизведение, VU, tilt)
## 6) Как писать код под эту плату (важно)
1. **Экран**
- Рабочее разрешение использовать `480x480`.
- Не рисовать критичный текст/кнопки впритык к краю; держать safe margin (`~20px+`) из-за скругленных углов.
- Не делать полный `fillScreen` в каждом loop: только частичные обновления (`fillRect`/локальные перерисовки), иначе мерцание.
2. **Touch**
- Настройка CST:
- `setMaxCoordinates(480, 480)`
- `setSwapXY(true)`
- `setMirrorXY(true, false)`
- Обрабатывать touch по IRQ + `getPoint`.
- После смещения UI обязательно пересчитывать hitbox кнопок.
3. **Аудио**
- Для динамика инициализировать `ES8311`.
- Для микрофона обязательно инициализировать `ES7210`; без этого запись может быть пустой.
- Для отладки записи показывать VU/peak на экране во время `RECORD`.
- Для быстрой проверки тракта всегда держать кнопку `BEEP` (тон), чтобы отделить проблему динамика от проблемы микрофона.
4. **IMU**
- QMI8658 обновлять с ограниченной частотой (например 80–150 мс для UI-строки), чтобы не шуметь перерисовками.
5. **Стабильность UI**
- Статика: рисуется один раз в setup.
- Динамика: отдельная зона, перерисовывать только по изменению данных.
## 7) Рекомендуемый workflow для Codex
1. Проверить порт и инструменты.
2. Если новая плата/первый запуск — сделать backup flash.
3. Собрать и залить `simple`.
4. Пройти ручной чек:
- экран отображает текст без обрезки,
- touch срабатывает по кнопкам,
- `BEEP` слышно,
- VU двигается во время записи,
- `PLAY` воспроизводит записанное,
- `Tilt` меняется при повороте.
5. Только после этого усложнять приложение.
## 8) Ссылки
- Product page: https://www.waveshare.com/product/arduino/boards-kits/esp32-s3/esp32-s3-touch-amoled-2.16.htm
- Docs: https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16
- Arduino setup: https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16/Development-Environment-Setup-Arduino
- Official examples: https://github.com/waveshareteam/ESP32-S3-Touch-AMOLED-2.16
@@ -0,0 +1,119 @@
# ESP32-S3-Touch-AMOLED-2.16 Codex Guide
Этот файл переносится в другие проекты как готовая инструкция для Codex по этой плате.
## 1) Что это за плата
- Модель: `Waveshare ESP32-S3-Touch-AMOLED-2.16`
- MCU: `ESP32-S3` (flash 16MB, PSRAM 8MB)
- Экран: AMOLED, физически 480x480, углы скруглены (часть крайних пикселей может быть невидима)
- Touch: CST92xx
- IMU: QMI8658
- Аудио:
- DAC/вывод (динамик): ES8311
- ADC/вход (микрофоны): ES7210
## 2) Что уже установлено в этой среде
- Ubuntu
- `arduino-cli 1.4.0`
- `esp32:esp32` core `3.3.5`
- `esptool` из `~/.arduino15/packages/esp32/tools/esptool_py/5.1.0/esptool`
- USB порт платы: обычно `/dev/ttyACM0`
Проверка:
```bash
arduino-cli version
arduino-cli core list
arduino-cli board list
ls -l /dev/ttyACM0
```
## 3) Структура подпроекта (эталон)
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
- `original-firmware/` — backup/restore заводской прошивки
- `main-device/` — прошивки и `burn.sh`
- `reference/` — заметки и ссылки
## 4) Бэкап перед любыми экспериментами
```bash
cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
./backup_factory.sh
```
Ожидаемый результат:
- `factory-full-16mb.bin`
- `factory-full-16mb.bin.sha256`
Восстановление:
```bash
./restore_factory_backup.sh
```
## 5) Деплой (прошивка) — стандарт
Главный скрипт:
```bash
cd ESP32-S3-Touch-AMOLED-2.16/main-device
./burn.sh <mode>
```
Режимы:
- `hello` — базовый экран
- `widgets` — экран+touch+IMU (официальный пример)
- `audio` — тест аудио тракта
- `simple` — кастомный интеграционный тест (экран, touch, запись/воспроизведение, VU, tilt)
## 6) Как писать код под эту плату (важно)
1. **Экран**
- Рабочее разрешение использовать `480x480`.
- Не рисовать критичный текст/кнопки впритык к краю; держать safe margin (`~20px+`) из-за скругленных углов.
- Не делать полный `fillScreen` в каждом loop: только частичные обновления (`fillRect`/локальные перерисовки), иначе мерцание.
2. **Touch**
- Настройка CST:
- `setMaxCoordinates(480, 480)`
- `setSwapXY(true)`
- `setMirrorXY(true, false)`
- Обрабатывать touch по IRQ + `getPoint`.
- После смещения UI обязательно пересчитывать hitbox кнопок.
3. **Аудио**
- Для динамика инициализировать `ES8311`.
- Для микрофона обязательно инициализировать `ES7210`; без этого запись может быть пустой.
- Для отладки записи показывать VU/peak на экране во время `RECORD`.
- Для быстрой проверки тракта всегда держать кнопку `BEEP` (тон), чтобы отделить проблему динамика от проблемы микрофона.
4. **IMU**
- QMI8658 обновлять с ограниченной частотой (например 80–150 мс для UI-строки), чтобы не шуметь перерисовками.
5. **Стабильность UI**
- Статика: рисуется один раз в setup.
- Динамика: отдельная зона, перерисовывать только по изменению данных.
## 7) Рекомендуемый workflow для Codex
1. Проверить порт и инструменты.
2. Если новая плата/первый запуск — сделать backup flash.
3. Собрать и залить `simple`.
4. Пройти ручной чек:
- экран отображает текст без обрезки,
- touch срабатывает по кнопкам,
- `BEEP` слышно,
- VU двигается во время записи,
- `PLAY` воспроизводит записанное,
- `Tilt` меняется при повороте.
5. Только после этого усложнять приложение.
## 8) Ссылки
- Product page: https://www.waveshare.com/product/arduino/boards-kits/esp32-s3/esp32-s3-touch-amoled-2.16.htm
- Docs: https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16
- Arduino setup: https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16/Development-Environment-Setup-Arduino
- Official examples: https://github.com/waveshareteam/ESP32-S3-Touch-AMOLED-2.16
@@ -0,0 +1,28 @@
# ESP32-S3-Touch-AMOLED-2.16
Подпроект для Waveshare `ESP32-S3-Touch-AMOLED-2.16`.
Структура:
- `official-demo/` — официальный репозиторий примеров Waveshare
- `original-firmware/` — резервная копия заводской прошивки
- `main-device/` — скрипты быстрой проверки устройства и основной скетч `shine_homeserver_main/`
- `reference/` — локальные заметки по документации и железу
- `main-device/shine_homeserver_main/` — основной рабочий скетч ESP32-проекта `SHiNE`
Примечание по git:
- `official-demo/` держать как локальный внешний checkout из `https://github.com/waveshareteam/ESP32-S3-Touch-AMOLED-2.16`, в основной git его не добавлять.
- `original-firmware/*.bin` — локальный дамп конкретной платы, в git не добавлять.
- `.arduino-build/` и готовые `.bin/.elf/.map` — сборочные артефакты, в git не добавлять.
Быстрый старт:
1. Сделать backup текущей прошивки:
- `cd original-firmware && ./backup_factory.sh`
2. Залить тест экрана/тача:
- `cd ../main-device && ./burn.sh widgets`
3. Залить тест динамика:
- `cd ../main-device && ./burn.sh audio`
4. Залить основной UI:
- `cd ../main-device && ./burn.sh shine-homeserver-main`
@@ -0,0 +1,52 @@
# Main Device
Основной скетч homeserver и старые тестовые скетчи для быстрой проверки платы.
`burn.sh` теперь:
- сам пытается найти USB-порт ESP32;
- сначала делает быструю инкрементальную сборку;
- если быстрая сборка не удалась, автоматически повторяет полную `clean`-сборку.
Для режимов `widgets`, `audio` и `hello` рядом должен лежать локальный checkout `official-demo/` из официального репозитория Waveshare. В основной git он не добавляется, потому что это большой внешний набор примеров, библиотек, прошивок и артефактов.
Режимы:
- `widgets` — экран + touch + IMU (пример `05_LVGL_Widgets`)
- `audio` — динамик/аудио-кодек (пример `07_ES8311`)
- `hello` — базовый тест экрана (пример `01_HelloWorld`)
- `simple` — простой кастомный тест: экран + touch + запись/проигрывание + наклон (IMU)
- `argon2` — генерация masterSecret через Argon2id с SD-картой как памятью (тест скорости)
- `homeserver-ui` — совместимый алиас, указывает на `shine_homeserver_main/`
- `shine-homeserver-main` — основной скетч проекта `SHiNE` для ESP32, текущая рабочая версия UI
- `shine-homeserver-ui-main` — старое имя основного скетча, оставлено как совместимый алиас
- `legacy-homeserver-ui` — старый UI-прототип `shine_homeserver_ui/`, оставлен как тестовый и не является основным
- `text-test` — диагностический экран рендера текста: default font, U8g2 ASCII, U8g2 кириллица, кнопки с подписями
- `gfx-text-test` — тот же тест рендера текста, но уже внутри новой папки `test_sketches/`
- `gfx-layout-test` — тест геометрии и нижних рядов кнопок
- `lvgl-basic-test` — минимальный экран на `LVGL` с текстом и кнопками
- `lvgl-interaction-test` — экран на `LVGL` с большим числом кнопок и сообщением о нажатой кнопке
- `lvgl-touch-debug-test` — точечная диагностика touch: сырые координаты, маркер точки и большая тест-кнопка `LVGL`
- `lvgl-official-based-test` — наш минимальный экран, но на максимально близкой к официальному `LVGL_Widgets` инициализации
- `lvgl-subserver-touch-test` — старый гибридный тест: `LVGL`-интерфейс, но display/touch init и raw touch-read взяты из старого `shine_homeserver_ui`; подтверждено на устройстве, touch работает, зелёных линий по краям нет
- `lvgl-russian-font-test` — тест кастомного `LVGL`-шрифта с кириллицей: русские кнопки, длинные подписи и статусы
- `lvgl-nav-minimal-test` — старое имя основного скетча, теперь ведёт на `shine_homeserver_main/` для совместимости
Запуск:
- `./burn.sh widgets`
- `./burn.sh audio`
- `./burn.sh hello`
- `./burn.sh simple`
- `./burn.sh homeserver-ui`
- `./burn.sh shine-homeserver-main`
- `./burn.sh shine-homeserver-ui-main`
- `./burn.sh legacy-homeserver-ui`
- `./burn.sh text-test`
- `./burn.sh gfx-text-test`
- `./burn.sh gfx-layout-test`
- `./burn.sh lvgl-basic-test`
- `./burn.sh lvgl-interaction-test`
- `./burn.sh lvgl-touch-debug-test`
- `./burn.sh lvgl-official-based-test`
- `./burn.sh lvgl-subserver-touch-test`
- `./burn.sh lvgl-russian-font-test`
- `./burn.sh lvgl-nav-minimal-test`
- `./flash_shine_homeserver_main.sh` - автоматически находит USB-порт и заливает `shine_homeserver_main`
@@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
// BLAKE2b state
struct B2State {
uint64_t h[8], t[2], f[2];
uint8_t buf[128];
size_t buflen, outlen;
};
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BOARD_DIR="$(cd "${ROOT_DIR}/.." && pwd)"
DEMO_BASE="${BOARD_DIR}/official-demo/examples/Arduino-v3.3.5"
MODE="${1:-widgets}"
PORT="${PORT:-}"
FQBN="${FQBN:-esp32:esp32:esp32s3:USBMode=hwcdc,CDCOnBoot=cdc,UploadSpeed=921600,CPUFreq=240,FlashMode=dio,FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,PSRAM=opi}"
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/.arduino-build/build-${MODE}}"
OUT_DIR="${OUT_DIR:-${ROOT_DIR}/.arduino-build/out-${MODE}}"
detect_port() {
local detected
detected="$(arduino-cli board list 2>/dev/null | awk '/\/dev\/tty(ACM|USB)/ {print $1; exit}')"
if [[ -n "${detected}" ]]; then
echo "${detected}"
return 0
fi
for candidate in /dev/ttyACM* /dev/ttyUSB*; do
if [[ -e "${candidate}" ]]; then
echo "${candidate}"
return 0
fi
done
return 1
}
case "${MODE}" in
hello) SKETCH_DIR="${DEMO_BASE}/examples/01_HelloWorld" ;;
widgets) SKETCH_DIR="${DEMO_BASE}/examples/05_LVGL_Widgets" ;;
audio) SKETCH_DIR="${DEMO_BASE}/examples/07_ES8311" ;;
simple) SKETCH_DIR="${ROOT_DIR}/simple_av_test" ;;
argon2) SKETCH_DIR="${ROOT_DIR}/argon2_sd_test" ;;
homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
shine-homeserver-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
shine-homeserver-ui-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
legacy-homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_ui" ;;
text-test) SKETCH_DIR="${ROOT_DIR}/text_render_test" ;;
gfx-text-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_text_render_test" ;;
gfx-layout-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_button_layout_test" ;;
lvgl-basic-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_basic_test" ;;
lvgl-interaction-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_interaction_test" ;;
lvgl-touch-debug-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_touch_debug_test" ;;
lvgl-official-based-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_official_based_test" ;;
lvgl-subserver-touch-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_subserver_touch_test" ;;
lvgl-russian-font-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_russian_font_test" ;;
lvgl-nav-minimal-test) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
*)
echo "Unknown mode: ${MODE}" >&2
echo "Use one of: hello, widgets, audio, simple, argon2, homeserver-ui, shine-homeserver-main, shine-homeserver-ui-main, legacy-homeserver-ui, text-test, gfx-text-test, gfx-layout-test, lvgl-basic-test, lvgl-interaction-test, lvgl-touch-debug-test, lvgl-official-based-test, lvgl-subserver-touch-test, lvgl-russian-font-test" >&2
exit 2
;;
esac
if [[ -z "${PORT}" ]]; then
if ! PORT="$(detect_port)"; then
echo "Failed to auto-detect ESP32 port. Set PORT=/dev/ttyACM0 ./burn.sh ${MODE}" >&2
exit 3
fi
fi
echo "== Mode: ${MODE}"
echo "== Sketch: ${SKETCH_DIR}"
echo "== Port: ${PORT}"
echo "== FQBN: ${FQBN}"
mkdir -p "${BUILD_DIR}" "${OUT_DIR}"
compile_args=(
--fqbn "${FQBN}"
--build-path "${BUILD_DIR}"
--output-dir "${OUT_DIR}"
--library "${DEMO_BASE}/libraries/GFX_Library_for_Arduino"
--library "${DEMO_BASE}/libraries/SensorLib"
--library "${DEMO_BASE}/libraries/XPowersLib"
--library "${DEMO_BASE}/libraries/lvgl"
--library "${DEMO_BASE}/libraries/Mylibrary"
"${SKETCH_DIR}"
)
echo "== Compile: fast incremental build"
if ! arduino-cli compile "${compile_args[@]}"; then
echo "== Compile: fast build failed, retrying clean build"
arduino-cli compile --clean "${compile_args[@]}"
fi
arduino-cli upload \
-p "${PORT}" \
--fqbn "${FQBN}" \
--input-dir "${OUT_DIR}" \
"${SKETCH_DIR}"
echo
echo "== Done."
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
detect_port_from_arduino_cli() {
local line
while IFS= read -r line; do
[[ -z "${line}" ]] && continue
[[ "${line}" == Port* ]] && continue
if [[ "${line}" == /dev/* ]]; then
awk '{print $1}' <<<"${line}"
return 0
fi
done < <(arduino-cli board list 2>/dev/null || true)
return 1
}
detect_port_from_dev() {
local candidates=()
local path
for path in /dev/ttyACM* /dev/ttyUSB*; do
[[ -e "${path}" ]] || continue
candidates+=("${path}")
done
if [[ "${#candidates[@]}" -eq 1 ]]; then
printf '%s\n' "${candidates[0]}"
return 0
fi
return 1
}
PORT="${PORT:-}"
if [[ -z "${PORT}" ]]; then
PORT="$(detect_port_from_arduino_cli || true)"
fi
if [[ -z "${PORT}" ]]; then
PORT="$(detect_port_from_dev || true)"
fi
if [[ -z "${PORT}" ]]; then
echo "Не удалось автоматически найти USB-порт ESP32." >&2
echo "Подключите плату и проверьте 'arduino-cli board list'." >&2
echo "Либо укажите порт вручную: PORT=/dev/ttyACM0 ./flash_shine_homeserver_main.sh" >&2
exit 1
fi
echo "== Найден порт: ${PORT}"
PORT="${PORT}" "${ROOT_DIR}/burn.sh" shine-homeserver-main
@@ -0,0 +1,14 @@
# SHiNE Homeserver UI Main
Это основной рабочий скетч ESP32-проекта `SHiNE`.
Текущая каноническая точка запуска:
- `./burn.sh shine-homeserver-main`
- `./burn.sh homeserver-ui`
Историческое имя этого скетча:
- `lvgl-nav-minimal-test`
Прежние тестовые варианты для этой платы остаются в `main-device/test_sketches/` и должны восприниматься как старые диагностические сборки, а не как основной UI.
@@ -0,0 +1,591 @@
#include "shine_secret_generation.h"
#include <SD_MMC.h>
#include <mbedtls/sha256.h>
#include <mbedtls/base64.h>
#include <string.h>
#include <stdint.h>
#include <driver/gpio.h>
#define PIN_SD_CLK 2
#define PIN_SD_CMD 1
#define PIN_SD_D0 3
#define SD_MEM_FILE "/argon2.bin"
#define A2_T 2u
#define A2_M 65536u
#define A2_P 1u
#define A2_DKLEN 32u
#define A2_SYNC 4u
#define A2_SEG (A2_M / A2_SYNC)
#define A2_VERSION 0x13u
#define A2_TYPE 2u
#define A2_BLKSZ 1024u
#define TOTAL_FILLS (A2_T * A2_M - 2u)
struct B2State {
uint64_t h[8], t[2], f[2];
uint8_t buf[128];
size_t buflen, outlen;
};
static bool gSdReady = false;
static bool gRunning = false;
static bool gDone = false;
static bool gError = false;
static char gMessage[96] = {};
static uint8_t gSecret[32] = {};
static char gSecretB58[64] = {};
static uint32_t gDoneBlocks = 0;
static uint32_t gStartMs = 0;
static uint32_t gCurPass = 0;
static uint32_t gCurBlock = 2;
static bool gInitDone = false;
static File gSdFile;
static uint8_t *gBufPrev = nullptr;
static uint8_t *gBufRef = nullptr;
static uint8_t *gBufOut = nullptr;
static uint8_t *gBufZero = nullptr;
static uint8_t *gBufAddr = nullptr;
static uint32_t *gJ1Seg = nullptr;
static uint8_t gH0[64] = {};
static B2State gB2S;
#define ROTR64(x,n) (((x)>>(n))|((x)<<(64-(n))))
static const uint64_t B2IV[8] = {
0x6A09E667F3BCC908ULL,0xBB67AE8584CAA73BULL,
0x3C6EF372FE94F82BULL,0xA54FF53A5F1D36F1ULL,
0x510E527FADE682D1ULL,0x9B05688C2B3E6C1FULL,
0x1F83D9ABFB41BD6BULL,0x5BE0CD19137E2179ULL
};
static const uint8_t B2SIGMA[12][16] = {
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
{11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
{7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
{9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
{2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
{12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
{13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3}
};
static const char B58_ALPHA[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
static void setMessage(const char *message) {
snprintf(gMessage, sizeof(gMessage), "%s", message ? message : "");
}
static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32);
static void finishSecretFromBytes(const uint8_t secret32[32], const char *message) {
memcpy(gSecret, secret32, 32);
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
gDone = true;
gRunning = false;
gError = false;
gInitDone = false;
gDoneBlocks = TOTAL_FILLS;
setMessage(message ? message : "Secret generated");
}
static void b2_compress(B2State *S, const uint8_t *blk) {
uint64_t m[16], v[16];
for (int i = 0; i < 16; i++) m[i] = ((const uint64_t *)blk)[i];
for (int i = 0; i < 8; i++) v[i] = S->h[i];
v[8]=B2IV[0];v[9]=B2IV[1];v[10]=B2IV[2];v[11]=B2IV[3];
v[12]=B2IV[4]^S->t[0];v[13]=B2IV[5]^S->t[1];
v[14]=B2IV[6]^S->f[0];v[15]=B2IV[7]^S->f[1];
#define BG(r,i,a,b,c,d) \
v[a]+=v[b]+m[B2SIGMA[r][2*(i)]]; v[d]=ROTR64(v[d]^v[a],32); \
v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],24); \
v[a]+=v[b]+m[B2SIGMA[r][2*(i)+1]]; v[d]=ROTR64(v[d]^v[a],16); \
v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],63)
for (int r = 0; r < 12; r++) {
BG(r,0,0,4,8,12);BG(r,1,1,5,9,13);BG(r,2,2,6,10,14);BG(r,3,3,7,11,15);
BG(r,4,0,5,10,15);BG(r,5,1,6,11,12);BG(r,6,2,7,8,13);BG(r,7,3,4,9,14);
}
#undef BG
for (int i = 0; i < 8; i++) S->h[i] ^= v[i] ^ v[i + 8];
}
static void b2_init(B2State *S, size_t outlen) {
memset(S, 0, sizeof(*S));
memcpy(S->h, B2IV, 64);
S->h[0] ^= 0x01010000ULL | outlen;
S->outlen = outlen;
}
static void b2_update(B2State *S, const uint8_t *in, size_t inlen) {
while (inlen > 0) {
size_t fill = 128 - S->buflen;
size_t use = inlen < fill ? inlen : fill;
memcpy(S->buf + S->buflen, in, use);
S->buflen += use;
in += use;
inlen -= use;
if (S->buflen == 128 && inlen > 0) {
S->t[0] += 128;
if (S->t[0] < 128) S->t[1]++;
b2_compress(S, S->buf);
S->buflen = 0;
}
}
}
static void b2_final(B2State *S, uint8_t *digest) {
S->f[0] = ~0ULL;
S->t[0] += S->buflen;
if (S->t[0] < S->buflen) S->t[1]++;
memset(S->buf + S->buflen, 0, 128 - S->buflen);
b2_compress(S, S->buf);
uint8_t tmp[64];
for (int i = 0; i < 8; i++) {
uint64_t w = S->h[i];
for (int j = 0; j < 8; j++) tmp[i * 8 + j] = (uint8_t)(w >> (j * 8));
}
memcpy(digest, tmp, S->outlen);
}
static void b2hash(const uint8_t *in, size_t inlen, uint8_t *digest, size_t outlen) {
b2_init(&gB2S, outlen);
b2_update(&gB2S, in, inlen);
b2_final(&gB2S, digest);
}
static void b2long(const uint8_t *in, size_t inlen, uint8_t *digest, uint32_t outlen) {
uint8_t lenle[4] = {(uint8_t)outlen, (uint8_t)(outlen >> 8), (uint8_t)(outlen >> 16), (uint8_t)(outlen >> 24)};
if (outlen <= 64) {
b2_init(&gB2S, outlen);
b2_update(&gB2S, lenle, 4);
b2_update(&gB2S, in, inlen);
b2_final(&gB2S, digest);
return;
}
uint8_t tmp[64];
b2_init(&gB2S, 64);
b2_update(&gB2S, lenle, 4);
b2_update(&gB2S, in, inlen);
b2_final(&gB2S, tmp);
memcpy(digest, tmp, 32);
digest += 32;
uint32_t rem = outlen - 32;
while (rem > 64) {
b2hash(tmp, 64, tmp, 64);
memcpy(digest, tmp, 32);
digest += 32;
rem -= 32;
}
b2hash(tmp, 64, tmp, rem);
memcpy(digest, tmp, rem);
}
#define A2G(a,b,c,d) do{ \
(a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
(d)=ROTR64((d)^(a),32); \
(c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
(b)=ROTR64((b)^(c),24); \
(a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
(d)=ROTR64((d)^(a),16); \
(c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
(b)=ROTR64((b)^(c),63); \
}while(0)
#define A2P(v) do{ \
A2G((v)[0],(v)[4],(v)[8],(v)[12]);A2G((v)[1],(v)[5],(v)[9],(v)[13]); \
A2G((v)[2],(v)[6],(v)[10],(v)[14]);A2G((v)[3],(v)[7],(v)[11],(v)[15]); \
A2G((v)[0],(v)[5],(v)[10],(v)[15]);A2G((v)[1],(v)[6],(v)[11],(v)[12]); \
A2G((v)[2],(v)[7],(v)[8],(v)[13]);A2G((v)[3],(v)[4],(v)[9],(v)[14]); \
}while(0)
static void fillBlock(uint32_t prevIdx, uint32_t refIdx, uint32_t outIdx, bool xorMode) {
gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ);
gSdFile.seek((uint64_t)refIdx * A2_BLKSZ); gSdFile.read(gBufRef, A2_BLKSZ);
uint64_t *R = (uint64_t *)gBufOut, *P = (uint64_t *)gBufPrev, *F = (uint64_t *)gBufRef;
for (int i = 0; i < 128; i++) R[i] = P[i] ^ F[i];
uint64_t *Q = (uint64_t *)gBufRef; memcpy(Q, R, A2_BLKSZ);
for (int j = 0; j < 8; j++) A2P(&Q[16 * j]);
uint64_t row[16];
for (int i = 0; i < 8; i++) {
for (int k = 0; k < 8; k++) { row[2 * k] = Q[2 * i + 16 * k]; row[2 * k + 1] = Q[2 * i + 16 * k + 1]; }
A2P(row);
for (int k = 0; k < 8; k++) { Q[2 * i + 16 * k] = row[2 * k]; Q[2 * i + 16 * k + 1] = row[2 * k + 1]; }
}
for (int i = 0; i < 128; i++) R[i] = Q[i] ^ R[i];
if (xorMode) {
gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ);
uint64_t *O = (uint64_t *)gBufPrev; for (int i = 0; i < 128; i++) R[i] ^= O[i];
}
gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ);
}
static void generateAddresses(uint32_t pass, uint32_t slice) {
memset(gBufZero, 0, A2_BLKSZ);
uint8_t inputBlk[A2_BLKSZ]; memset(inputBlk, 0, A2_BLKSZ);
uint64_t *iv = (uint64_t *)inputBlk;
iv[0]=pass;iv[1]=0;iv[2]=slice;iv[3]=A2_M;iv[4]=A2_T;iv[5]=A2_TYPE;
uint32_t count = 0;
for (uint32_t b = 0; b < A2_SEG; b += 128) {
iv[6] = ++count;
{
uint64_t *R=(uint64_t*)gBufAddr,*Z=(uint64_t*)gBufZero,*I=iv;
for(int i=0;i<128;i++)R[i]=Z[i]^I[i];
uint64_t *Q=R,row[16];
for(int j=0;j<8;j++)A2P(&Q[16*j]);
for(int i=0;i<8;i++){
for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
A2P(row);
for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
}
for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^I[i]);
}
{
uint64_t *R=(uint64_t*)gBufPrev,*Z=(uint64_t*)gBufZero,*A=(uint64_t*)gBufAddr;
for(int i=0;i<128;i++)R[i]=Z[i]^A[i];
uint64_t *Q=R,row[16];
for(int j=0;j<8;j++)A2P(&Q[16*j]);
for(int i=0;i<8;i++){
for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
A2P(row);
for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
}
for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^A[i]);
memcpy(gBufAddr,gBufPrev,A2_BLKSZ);
}
uint64_t *addr=(uint64_t*)gBufAddr;
uint32_t cnt=(b+128<=A2_SEG)?128:(A2_SEG-b);
for(uint32_t j=0;j<cnt;j++)gJ1Seg[b+j]=(uint32_t)(addr[j]&0xFFFFFFFFULL);
}
}
static uint32_t indexAlpha(uint32_t pass, uint32_t slice, uint32_t posInSlice, uint32_t J1) {
uint32_t refAreaSize;
if (pass == 0) {
if (slice == 0) {
uint32_t pos = posInSlice;
refAreaSize = (pos < 2) ? 0 : pos - 1;
} else {
refAreaSize = slice * A2_SEG + posInSlice - 1;
}
} else {
refAreaSize = A2_M - A2_SEG + posInSlice - 1;
}
if (refAreaSize == 0) refAreaSize = 1;
uint64_t relPos = (uint64_t)J1 * J1 >> 32;
relPos = refAreaSize - 1 - ((uint64_t)refAreaSize * relPos >> 32);
uint32_t startPos = 0;
if (pass != 0 && slice != A2_SYNC - 1) startPos = (slice + 1) * A2_SEG;
return (startPos + relPos) % A2_M;
}
static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32) {
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0);
mbedtls_sha256_update(&ctx, in, len);
mbedtls_sha256_finish(&ctx, out32);
mbedtls_sha256_free(&ctx);
}
static void trimInPlace(char *text) {
if (!text) return;
size_t len = strlen(text);
size_t start = 0;
while (start < len && (text[start] == ' ' || text[start] == '\t' || text[start] == '\n' || text[start] == '\r')) start++;
size_t end = len;
while (end > start && (text[end - 1] == ' ' || text[end - 1] == '\t' || text[end - 1] == '\n' || text[end - 1] == '\r')) end--;
if (start > 0 && end > start) memmove(text, text + start, end - start);
if (end <= start) { text[0] = '\0'; return; }
text[end - start] = '\0';
}
static void lowercaseAsciiInPlace(char *text) {
if (!text) return;
for (int i = 0; text[i]; i++) if (text[i] >= 'A' && text[i] <= 'Z') text[i] += 32;
}
static void normalizeLoginInPlace(char *text) {
trimInPlace(text);
lowercaseAsciiInPlace(text);
}
static void bytesToBase64Std(const uint8_t *data, size_t len, char *out, size_t outSz) {
size_t b64len = 0;
if (outSz == 0) return;
if (mbedtls_base64_encode((uint8_t*)out, outSz, &b64len, data, len) != 0) {
out[0] = '\0';
return;
}
if (b64len >= outSz) b64len = outSz - 1;
out[b64len] = '\0';
}
static void deriveLegacyMasterSecret(const char *password, uint8_t *out32) {
uint8_t baseHash[32];
sha256calc((const uint8_t*)password, strlen(password), baseHash);
char baseB64[64];
bytesToBase64Std(baseHash, 32, baseB64, sizeof(baseB64));
char material[96];
snprintf(material, sizeof(material), "%smaster.secret", baseB64);
sha256calc((const uint8_t*)material, strlen(material), out32);
}
void shineSecretBase58Encode(const uint8_t *data, size_t len, char *out, size_t outSz) {
int zeros = 0;
while (zeros < (int)len && data[zeros] == 0) zeros++;
uint8_t tmp[128] = {};
int tmpLen = 0;
for (int i = zeros; i < (int)len; i++) {
int carry = data[i];
for (int j = 0; j < tmpLen; j++) { carry += 256 * tmp[j]; tmp[j] = carry % 58; carry /= 58; }
while (carry) { tmp[tmpLen++] = carry % 58; carry /= 58; }
}
int n = 0;
for (int i = 0; i < zeros && n < (int)outSz - 1; i++) out[n++] = '1';
for (int i = tmpLen - 1; i >= 0 && n < (int)outSz - 1; i--) out[n++] = B58_ALPHA[(int)tmp[i]];
out[n] = '\0';
}
bool shineSecretBase58Decode(const char *input, uint8_t *out, size_t *outLen, size_t maxOutLen, String &error) {
error = "";
if (!input || !*input) {
error = "secret too short";
return false;
}
size_t inLen = strlen(input);
uint8_t tmp[256] = {};
size_t tmpLen = 0;
size_t zeros = 0;
while (zeros < inLen && input[zeros] == '1') zeros++;
for (size_t i = zeros; i < inLen; ++i) {
const char *pos = strchr(B58_ALPHA, input[i]);
if (!pos) {
error = "invalid base58";
return false;
}
int carry = (int)(pos - B58_ALPHA);
for (size_t j = 0; j < tmpLen; ++j) {
carry += 58 * tmp[j];
tmp[j] = carry & 0xFF;
carry >>= 8;
}
while (carry > 0) {
tmp[tmpLen++] = carry & 0xFF;
carry >>= 8;
if (tmpLen >= sizeof(tmp)) {
error = "secret too long";
return false;
}
}
}
size_t decodedLen = zeros + tmpLen;
if (decodedLen > maxOutLen) {
error = "secret too long";
return false;
}
memset(out, 0, maxOutLen);
size_t offset = 0;
for (size_t i = 0; i < zeros; ++i) out[offset++] = 0;
for (size_t i = 0; i < tmpLen; ++i) out[offset + i] = tmp[tmpLen - 1 - i];
*outLen = decodedLen;
return true;
}
static void argon2Init(const char *login, const char *password) {
char saltSrc[256];
snprintf(saltSrc, sizeof(saltSrc), "shine-auth-v2|login=%s|suffix=master.secret", login);
uint8_t saltHash[32]; sha256calc((uint8_t*)saltSrc, strlen(saltSrc), saltHash);
uint8_t salt[16]; memcpy(salt, saltHash, 16);
char passBytes[128];
snprintf(passBytes, sizeof(passBytes), "%s\n%s", login, password);
uint32_t passLen = strlen(passBytes), saltLen = 16;
b2_init(&gB2S, 64);
uint8_t le4[4];
#define B2LE32(val) do{uint32_t _v=(val);le4[0]=_v;le4[1]=_v>>8;le4[2]=_v>>16;le4[3]=_v>>24;b2_update(&gB2S,le4,4);}while(0)
B2LE32(A2_P);B2LE32(A2_DKLEN);B2LE32(A2_M);B2LE32(A2_T);
B2LE32(A2_VERSION);B2LE32(A2_TYPE);
B2LE32(passLen);b2_update(&gB2S,(const uint8_t*)passBytes,passLen);
B2LE32(saltLen);b2_update(&gB2S,salt,saltLen);
B2LE32(0);B2LE32(0);
#undef B2LE32
b2_final(&gB2S, gH0);
uint8_t input[72]; memcpy(input, gH0, 64);
for (uint32_t i = 0; i < 2; i++) {
input[64]=i;input[65]=0;input[66]=0;input[67]=0;
input[68]=0;input[69]=0;input[70]=0;input[71]=0;
b2long(input, 72, gBufOut, A2_BLKSZ);
gSdFile.seek((uint64_t)i * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ);
}
gCurPass = 0;
gCurBlock = 2;
gDoneBlocks = 0;
gStartMs = millis();
gInitDone = true;
generateAddresses(0, 0);
}
bool shineSecretInitSd(String &error) {
error = "";
if (gSdReady) return true;
gpio_reset_pin(GPIO_NUM_2);
pinMode(PIN_SD_CMD, INPUT_PULLUP);
pinMode(PIN_SD_D0, INPUT_PULLUP);
delay(20);
SD_MMC.setPins(PIN_SD_CLK, PIN_SD_CMD, PIN_SD_D0);
gSdReady = SD_MMC.begin("/sdcard", true);
if (!gSdReady) {
error = "SD card error";
return false;
}
if (!gBufPrev) {
gBufPrev=(uint8_t*)ps_malloc(A2_BLKSZ);
gBufRef=(uint8_t*)ps_malloc(A2_BLKSZ);
gBufOut=(uint8_t*)ps_malloc(A2_BLKSZ);
gBufZero=(uint8_t*)ps_calloc(1, A2_BLKSZ);
gBufAddr=(uint8_t*)ps_malloc(A2_BLKSZ);
gJ1Seg=(uint32_t*)ps_malloc(A2_SEG * sizeof(uint32_t));
}
if (!gBufPrev || !gBufRef || !gBufOut || !gBufZero || !gBufAddr || !gJ1Seg) {
error = "PSRAM alloc failed";
return false;
}
return true;
}
bool shineSecretStart(const char *normalizedLogin, const char *password, String &error) {
error = "";
if (!normalizedLogin || !*normalizedLogin) {
error = "login not set";
return false;
}
char loginBuf[64];
snprintf(loginBuf, sizeof(loginBuf), "%s", normalizedLogin);
normalizeLoginInPlace(loginBuf);
if (!loginBuf[0]) {
error = "login not set";
return false;
}
if (!shineSecretInitSd(error)) return false;
if (gSdFile) gSdFile.close();
SD_MMC.remove(SD_MEM_FILE);
gSdFile = SD_MMC.open(SD_MEM_FILE, "w+");
if (!gSdFile) {
error = "SD open failed";
return false;
}
memset(gSecret, 0, sizeof(gSecret));
gSecretB58[0] = '\0';
gDone = false;
gError = false;
gRunning = true;
gInitDone = false;
setMessage("Generating secret...");
if (!password || !password[0]) {
deriveLegacyMasterSecret(password ? password : "", gSecret);
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
gDone = true;
gRunning = false;
setMessage("Secret generated");
gSdFile.close();
SD_MMC.remove(SD_MEM_FILE);
return true;
}
argon2Init(loginBuf, password);
return true;
}
bool shineSecretStep(uint32_t blocksPerTick) {
if (!gRunning || !gInitDone) return gDone;
bool done = false;
for (uint32_t i = 0; i < blocksPerTick && !done; i++) {
if (gCurPass >= A2_T) {
done = true;
break;
}
uint32_t blk = gCurBlock;
uint32_t slice = blk / A2_SEG;
uint32_t posInSlice = blk - slice * A2_SEG;
bool xorMode = (gCurPass > 0);
uint32_t J1;
bool useArgon2i = (gCurPass == 0 && slice < 2);
if (useArgon2i) {
J1 = gJ1Seg[posInSlice];
} else {
uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1;
gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ);
gSdFile.read(gBufRef, 8);
J1 = ((uint32_t*)gBufRef)[0];
}
uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1;
uint32_t refIdx = indexAlpha(gCurPass, slice, posInSlice, J1);
fillBlock(prevIdx, refIdx, blk, xorMode);
gDoneBlocks++;
gCurBlock++;
if (gCurBlock >= A2_M) {
gCurBlock = 0;
gCurPass++;
} else {
uint32_t newSlice = gCurBlock / A2_SEG;
if (newSlice != slice && gCurPass == 0 && newSlice < 2) generateAddresses(gCurPass, newSlice);
}
done = (gCurPass >= A2_T);
}
if (done) {
gSdFile.seek((uint64_t)(A2_M - 1) * A2_BLKSZ);
gSdFile.read(gBufOut, A2_BLKSZ);
b2long(gBufOut, A2_BLKSZ, gSecret, A2_DKLEN);
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
gDone = true;
gRunning = false;
setMessage("Secret generated");
gSdFile.close();
SD_MMC.remove(SD_MEM_FILE);
}
return gDone;
}
void shineSecretAbort() {
gRunning = false;
gDone = false;
gError = false;
gInitDone = false;
gDoneBlocks = 0;
if (gSdFile) gSdFile.close();
if (gSdReady) SD_MMC.remove(SD_MEM_FILE);
setMessage("Generation cancelled");
}
ShineSecretGenerationStatus shineSecretStatus() {
ShineSecretGenerationStatus status = {};
status.running = gRunning;
status.done = gDone;
status.error = gError;
status.doneBlocks = gDoneBlocks;
status.totalBlocks = TOTAL_FILLS;
status.elapsedSec = gStartMs == 0 ? 0 : (millis() - gStartMs) / 1000;
snprintf(status.message, sizeof(status.message), "%s", gMessage);
snprintf(status.secretBase58, sizeof(status.secretBase58), "%s", gSecretB58);
return status;
}
const uint8_t *shineSecretBytes() {
return gSecret;
}
const char *shineSecretBase58() {
return gSecretB58;
}
@@ -0,0 +1,26 @@
#pragma once
#include <Arduino.h>
#include <stdint.h>
#include <stddef.h>
struct ShineSecretGenerationStatus {
bool running;
bool done;
bool error;
uint32_t doneBlocks;
uint32_t totalBlocks;
uint32_t elapsedSec;
char message[96];
char secretBase58[64];
};
bool shineSecretInitSd(String &error);
bool shineSecretStart(const char *normalizedLogin, const char *password, String &error);
bool shineSecretStep(uint32_t blocksPerTick = 4);
void shineSecretAbort();
ShineSecretGenerationStatus shineSecretStatus();
const uint8_t *shineSecretBytes();
const char *shineSecretBase58();
void shineSecretBase58Encode(const uint8_t *data, size_t len, char *out, size_t outSz);
bool shineSecretBase58Decode(const char *input, uint8_t *out, size_t *outLen, size_t maxOutLen, String &error);
@@ -0,0 +1,6 @@
# SHiNE Homeserver UI Legacy
Это старый тестовый вариант UI для ESP32-платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Не использовать как основной скетч проекта.
Основной рабочий скетч сейчас лежит в `../shine_homeserver_main/`.
@@ -0,0 +1 @@
#include "../../official-demo/examples/Arduino-v3.3.5/libraries/lvgl/src/extra/libs/qrcode/qrcodegen.c"
@@ -0,0 +1,136 @@
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef _AUDIO_HAL_H_
#define _AUDIO_HAL_H_
#define AUDIO_HAL_VOL_DEFAULT 60
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Select media hal codec mode
*/
typedef enum {
AUDIO_HAL_CODEC_MODE_ENCODE = 1, /*!< select adc */
AUDIO_HAL_CODEC_MODE_DECODE, /*!< select dac */
AUDIO_HAL_CODEC_MODE_BOTH, /*!< select both adc and dac */
AUDIO_HAL_CODEC_MODE_LINE_IN, /*!< set adc channel */
} audio_hal_codec_mode_t;
/**
* @brief Select adc channel for input mic signal
*/
typedef enum {
AUDIO_HAL_ADC_INPUT_LINE1 = 0x00, /*!< mic input to adc channel 1 */
AUDIO_HAL_ADC_INPUT_LINE2, /*!< mic input to adc channel 2 */
AUDIO_HAL_ADC_INPUT_ALL, /*!< mic input to both channels of adc */
AUDIO_HAL_ADC_INPUT_DIFFERENCE, /*!< mic input to adc difference channel */
} audio_hal_adc_input_t;
/**
* @brief Select channel for dac output
*/
typedef enum {
AUDIO_HAL_DAC_OUTPUT_LINE1 = 0x00, /*!< dac output signal to channel 1 */
AUDIO_HAL_DAC_OUTPUT_LINE2, /*!< dac output signal to channel 2 */
AUDIO_HAL_DAC_OUTPUT_ALL, /*!< dac output signal to both channels */
} audio_hal_dac_output_t;
/**
* @brief Select operating mode i.e. start or stop for audio codec chip
*/
typedef enum {
AUDIO_HAL_CTRL_STOP = 0x00, /*!< set stop mode */
AUDIO_HAL_CTRL_START = 0x01, /*!< set start mode */
} audio_hal_ctrl_t;
/**
* @brief Select I2S interface operating mode i.e. master or slave for audio codec chip
*/
typedef enum {
AUDIO_HAL_MODE_SLAVE = 0x00, /*!< set slave mode */
AUDIO_HAL_MODE_MASTER = 0x01, /*!< set master mode */
} audio_hal_iface_mode_t;
/**
* @brief Select I2S interface samples per second
*/
typedef enum {
AUDIO_HAL_08K_SAMPLES, /*!< set to 8k samples per second */
AUDIO_HAL_11K_SAMPLES, /*!< set to 11.025k samples per second */
AUDIO_HAL_16K_SAMPLES, /*!< set to 16k samples in per second */
AUDIO_HAL_22K_SAMPLES, /*!< set to 22.050k samples per second */
AUDIO_HAL_24K_SAMPLES, /*!< set to 24k samples in per second */
AUDIO_HAL_32K_SAMPLES, /*!< set to 32k samples in per second */
AUDIO_HAL_44K_SAMPLES, /*!< set to 44.1k samples per second */
AUDIO_HAL_48K_SAMPLES, /*!< set to 48k samples per second */
} audio_hal_iface_samples_t;
/**
* @brief Select I2S interface number of bits per sample
*/
typedef enum {
AUDIO_HAL_BIT_LENGTH_16BITS = 1, /*!< set 16 bits per sample */
AUDIO_HAL_BIT_LENGTH_24BITS, /*!< set 24 bits per sample */
AUDIO_HAL_BIT_LENGTH_32BITS, /*!< set 32 bits per sample */
} audio_hal_iface_bits_t;
/**
* @brief Select I2S interface format for audio codec chip
*/
typedef enum {
AUDIO_HAL_I2S_NORMAL = 0, /*!< set normal I2S format */
AUDIO_HAL_I2S_LEFT, /*!< set all left format */
AUDIO_HAL_I2S_RIGHT, /*!< set all right format */
AUDIO_HAL_I2S_DSP, /*!< set dsp/pcm format */
} audio_hal_iface_format_t;
/**
* @brief I2s interface configuration for audio codec chip
*/
typedef struct {
audio_hal_iface_mode_t mode; /*!< audio codec chip mode */
audio_hal_iface_format_t fmt; /*!< I2S interface format */
audio_hal_iface_samples_t samples; /*!< I2S interface samples per second */
audio_hal_iface_bits_t bits; /*!< i2s interface number of bits per sample */
} audio_hal_codec_i2s_iface_t;
/**
* @brief Configure media hal for initialization of audio codec chip
*/
typedef struct {
audio_hal_adc_input_t adc_input; /*!< set adc channel */
audio_hal_dac_output_t dac_output; /*!< set dac channel */
audio_hal_codec_mode_t codec_mode; /*!< select codec mode: adc, dac or both */
audio_hal_codec_i2s_iface_t i2s_iface; /*!< set I2S interface configuration */
} audio_hal_codec_config_t;
#ifdef __cplusplus
}
#endif
#endif //__AUDIO_HAL_H__
@@ -0,0 +1,549 @@
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2021 <ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD>
*
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifdef ESP32
#include <Wire.h>
#include <string.h>
#include "esp_log.h"
#include "es7210.h"
#define I2S_DSP_MODE_A 0
#define MCLK_DIV_FRE 256
#define ES7210_MCLK_SOURCE FROM_CLOCK_DOUBLE_PIN /* In master mode, 0 : MCLK from pad 1 : MCLK from clock doubler */
#define FROM_PAD_PIN 0
#define FROM_CLOCK_DOUBLE_PIN 1
static TwoWire *es7210wire;
static es7210_gain_value_t gain;
/*
* Clock coefficient structer
*/
struct _coeff_div_es7210 {
uint32_t mclk; /* mclk frequency */
uint32_t lrck; /* lrck */
uint8_t ss_ds;
uint8_t adc_div; /* adcclk divider */
uint8_t dll; /* dll_bypass */
uint8_t doubler; /* doubler enable */
uint8_t osr; /* adc osr */
uint8_t mclk_src; /* select mclk source */
uint32_t lrck_h; /* The high 4 bits of lrck */
uint32_t lrck_l; /* The low 8 bits of lrck */
};
static const char *TAG = "ES7210";
static es7210_input_mics_t mic_select = (es7210_input_mics_t)(ES7210_INPUT_MIC1 | ES7210_INPUT_MIC2 | ES7210_INPUT_MIC3 | ES7210_INPUT_MIC4);
/* Codec hifi mclk clock divider coefficients
* MEMBER REG
* mclk: 0x03
* lrck: standard
* ss_ds: --
* adc_div: 0x02
* dll: 0x06
* doubler: 0x02
* osr: 0x07
* mclk_src: 0x03
* lrckh: 0x04
* lrckl: 0x05
*/
static const struct _coeff_div_es7210 coeff_div[] = {
//mclk lrck ss_ds adc_div dll doubler osr mclk_src lrckh lrckl
/* 8k */
{12288000, 8000 , 0x00, 0x03, 0x01, 0x00, 0x20, 0x00, 0x06, 0x00},
{16384000, 8000 , 0x00, 0x04, 0x01, 0x00, 0x20, 0x00, 0x08, 0x00},
{19200000, 8000 , 0x00, 0x1e, 0x00, 0x01, 0x28, 0x00, 0x09, 0x60},
{4096000, 8000 , 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00},
/* 11.025k */
{11289600, 11025, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00},
/* 12k */
{12288000, 12000, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00},
{19200000, 12000, 0x00, 0x14, 0x00, 0x01, 0x28, 0x00, 0x06, 0x40},
/* 16k */
{4096000, 16000, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00},
{19200000, 16000, 0x00, 0x0a, 0x00, 0x00, 0x1e, 0x00, 0x04, 0x80},
{16384000, 16000, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00},
{12288000, 16000, 0x00, 0x03, 0x01, 0x01, 0x20, 0x00, 0x03, 0x00},
/* 22.05k */
{11289600, 22050, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00},
/* 24k */
{12288000, 24000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00},
{19200000, 24000, 0x00, 0x0a, 0x00, 0x01, 0x28, 0x00, 0x03, 0x20},
/* 32k */
{12288000, 32000, 0x00, 0x03, 0x00, 0x00, 0x20, 0x00, 0x01, 0x80},
{16384000, 32000, 0x00, 0x01, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00},
{19200000, 32000, 0x00, 0x05, 0x00, 0x00, 0x1e, 0x00, 0x02, 0x58},
/* 44.1k */
{11289600, 44100, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00},
/* 48k */
{12288000, 48000, 0x00, 0x01, 0x01, 0x01, 0x20, 0x00, 0x01, 0x00},
{19200000, 48000, 0x00, 0x05, 0x00, 0x01, 0x28, 0x00, 0x01, 0x90},
/* 64k */
{16384000, 64000, 0x01, 0x01, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00},
{19200000, 64000, 0x00, 0x05, 0x00, 0x01, 0x1e, 0x00, 0x01, 0x2c},
/* 88.2k */
{11289600, 88200, 0x01, 0x01, 0x01, 0x01, 0x20, 0x00, 0x00, 0x80},
/* 96k */
{12288000, 96000, 0x01, 0x01, 0x01, 0x01, 0x20, 0x00, 0x00, 0x80},
{19200000, 96000, 0x01, 0x05, 0x00, 0x01, 0x28, 0x00, 0x00, 0xc8},
};
static esp_err_t es7210_write_reg(uint8_t reg_addr, uint8_t data)
{
es7210wire->beginTransmission(ES7210_ADDR);
es7210wire->write(reg_addr);
es7210wire->write(data);
return es7210wire->endTransmission();
}
static esp_err_t es7210_update_reg_bit(uint8_t reg_addr, uint8_t update_bits, uint8_t data)
{
uint8_t regv;
regv = es7210_read_reg(reg_addr);
regv = (regv & (~update_bits)) | (update_bits & data);
return es7210_write_reg(reg_addr, regv);
}
static int get_coeff(uint32_t mclk, uint32_t lrck)
{
for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) {
if (coeff_div[i].lrck == lrck && coeff_div[i].mclk == mclk)
return i;
}
return -1;
}
int8_t get_es7210_mclk_src(void)
{
return ES7210_MCLK_SOURCE;
}
int es7210_read_reg(uint8_t reg_addr)
{
uint8_t data;
es7210wire->beginTransmission(ES7210_ADDR);
es7210wire->write(reg_addr);
es7210wire->endTransmission(false);
es7210wire->requestFrom(ES7210_ADDR, (size_t)1);
data = es7210wire->read();
return (int)data;
}
esp_err_t es7210_config_sample(audio_hal_iface_samples_t sample)
{
uint8_t regv;
int coeff;
int sample_fre = 0;
int mclk_fre = 0;
esp_err_t ret = ESP_OK;
switch (sample) {
case AUDIO_HAL_08K_SAMPLES:
sample_fre = 8000;
break;
case AUDIO_HAL_11K_SAMPLES:
sample_fre = 11025;
break;
case AUDIO_HAL_16K_SAMPLES:
sample_fre = 16000;
break;
case AUDIO_HAL_22K_SAMPLES:
sample_fre = 22050;
break;
case AUDIO_HAL_24K_SAMPLES:
sample_fre = 24000;
break;
case AUDIO_HAL_32K_SAMPLES:
sample_fre = 32000;
break;
case AUDIO_HAL_44K_SAMPLES:
sample_fre = 44100;
break;
case AUDIO_HAL_48K_SAMPLES:
sample_fre = 48000;
break;
default:
ESP_LOGE(TAG, "Unable to configure sample rate %dHz", sample_fre);
break;
}
mclk_fre = sample_fre * MCLK_DIV_FRE;
coeff = get_coeff(mclk_fre, sample_fre);
if (coeff < 0) {
ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK", sample_fre, mclk_fre);
return ESP_FAIL;
}
/* Set clock parammeters */
if (coeff >= 0) {
/* Set adc_div & doubler & dll */
regv = es7210_read_reg(ES7210_MAINCLK_REG02) & 0x00;
regv |= coeff_div[coeff].adc_div;
regv |= coeff_div[coeff].doubler << 6;
regv |= coeff_div[coeff].dll << 7;
ret |= es7210_write_reg(ES7210_MAINCLK_REG02, regv);
/* Set osr */
regv = coeff_div[coeff].osr;
ret |= es7210_write_reg(ES7210_OSR_REG07, regv);
/* Set lrck */
regv = coeff_div[coeff].lrck_h;
ret |= es7210_write_reg(ES7210_LRCK_DIVH_REG04, regv);
regv = coeff_div[coeff].lrck_l;
ret |= es7210_write_reg(ES7210_LRCK_DIVL_REG05, regv);
}
return ret;
}
esp_err_t es7210_mic_select(es7210_input_mics_t mic)
{
esp_err_t ret = ESP_OK;
mic_select = mic;
if (mic_select & (ES7210_INPUT_MIC1 | ES7210_INPUT_MIC2 | ES7210_INPUT_MIC3 | ES7210_INPUT_MIC4)) {
for (int i = 0; i < 4; i++) {
ret |= es7210_update_reg_bit(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00);
}
ret |= es7210_write_reg(ES7210_MIC12_POWER_REG4B, 0xff);
ret |= es7210_write_reg(ES7210_MIC34_POWER_REG4C, 0xff);
if (mic_select & ES7210_INPUT_MIC1) {
ESP_LOGI(TAG, "Enable ES7210_INPUT_MIC1");
ret |= es7210_update_reg_bit(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00);
ret |= es7210_write_reg(ES7210_MIC12_POWER_REG4B, 0x00);
ret |= es7210_update_reg_bit(ES7210_MIC1_GAIN_REG43, 0x10, 0x10);
}
if (mic_select & ES7210_INPUT_MIC2) {
ESP_LOGI(TAG, "Enable ES7210_INPUT_MIC2");
ret |= es7210_update_reg_bit(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00);
ret |= es7210_write_reg(ES7210_MIC12_POWER_REG4B, 0x00);
ret |= es7210_update_reg_bit(ES7210_MIC2_GAIN_REG44, 0x10, 0x10);
}
if (mic_select & ES7210_INPUT_MIC3) {
ESP_LOGI(TAG, "Enable ES7210_INPUT_MIC3");
ret |= es7210_update_reg_bit(ES7210_CLOCK_OFF_REG01, 0x15, 0x00);
ret |= es7210_write_reg(ES7210_MIC34_POWER_REG4C, 0x00);
ret |= es7210_update_reg_bit(ES7210_MIC3_GAIN_REG45, 0x10, 0x10);
}
if (mic_select & ES7210_INPUT_MIC4) {
ESP_LOGI(TAG, "Enable ES7210_INPUT_MIC4");
ret |= es7210_update_reg_bit(ES7210_CLOCK_OFF_REG01, 0x15, 0x00);
ret |= es7210_write_reg(ES7210_MIC34_POWER_REG4C, 0x00);
ret |= es7210_update_reg_bit(ES7210_MIC4_GAIN_REG46, 0x10, 0x10);
}
} else {
ESP_LOGE(TAG, "Microphone selection error");
return ESP_FAIL;
}
return ret;
}
esp_err_t es7210_adc_init(TwoWire *tw, audio_hal_codec_config_t *codec_cfg)
{
esp_err_t ret = ESP_OK;
es7210wire = tw;
ret |= es7210_write_reg(ES7210_RESET_REG00, 0xff);
ret |= es7210_write_reg(ES7210_RESET_REG00, 0x41);
ret |= es7210_write_reg(ES7210_CLOCK_OFF_REG01, 0x1f);
ret |= es7210_write_reg(ES7210_TIME_CONTROL0_REG09, 0x30); /* Set chip state cycle */
ret |= es7210_write_reg(ES7210_TIME_CONTROL1_REG0A, 0x30); /* Set power on state cycle */
// ret |= es7210_write_reg(ES7210_ADC12_HPF2_REG23, 0x2a); /* Quick setup */
// ret |= es7210_write_reg(ES7210_ADC12_HPF1_REG22, 0x0a);
// ret |= es7210_write_reg(ES7210_ADC34_HPF2_REG20, 0x0a);
// ret |= es7210_write_reg(ES7210_ADC34_HPF1_REG21, 0x2a);
/* Set master/slave audio interface */
audio_hal_codec_i2s_iface_t *i2s_cfg = & (codec_cfg->i2s_iface);
switch (i2s_cfg->mode) {
case AUDIO_HAL_MODE_MASTER: /* MASTER MODE */
ESP_LOGI(TAG, "ES7210 in Master mode");
// ret |= es7210_update_reg_bit(ES7210_MODE_CONFIG_REG08, 0x01, 0x01);
ret |= es7210_write_reg(ES7210_MODE_CONFIG_REG08, 0x20);
/* Select clock source for internal mclk */
switch (get_es7210_mclk_src()) {
case FROM_PAD_PIN:
ret |= es7210_update_reg_bit(ES7210_MASTER_CLK_REG03, 0x80, 0x00);
break;
case FROM_CLOCK_DOUBLE_PIN:
ret |= es7210_update_reg_bit(ES7210_MASTER_CLK_REG03, 0x80, 0x80);
break;
default:
ret |= es7210_update_reg_bit(ES7210_MASTER_CLK_REG03, 0x80, 0x00);
break;
}
break;
case AUDIO_HAL_MODE_SLAVE: /* SLAVE MODE */
ESP_LOGI(TAG, "ES7210 in Slave mode");
break;
default:
break;
}
ret |= es7210_write_reg(ES7210_ANALOG_REG40, 0xC3); /* Select power off analog, vdda = 3.3V, close vx20ff, VMID select 5KΩ start */
ret |= es7210_write_reg(ES7210_MIC12_BIAS_REG41, 0x70); /* Select 2.87v */
ret |= es7210_write_reg(ES7210_MIC34_BIAS_REG42, 0x70); /* Select 2.87v */
ret |= es7210_write_reg(ES7210_OSR_REG07, 0x20);
ret |= es7210_write_reg(ES7210_MAINCLK_REG02, 0xc1); /* Set the frequency division coefficient and use dll except clock doubler, and need to set 0xc1 to clear the state */
ret |= es7210_config_sample(i2s_cfg->samples);
ret |= es7210_mic_select(mic_select);
ret |= es7210_adc_set_gain_all(GAIN_0DB);
return ESP_OK;
}
esp_err_t es7210_adc_deinit()
{
return ESP_OK;
}
esp_err_t es7210_config_fmt(audio_hal_iface_format_t fmt)
{
esp_err_t ret = ESP_OK;
uint8_t adc_iface = 0;
adc_iface = es7210_read_reg(ES7210_SDP_INTERFACE1_REG11);
adc_iface &= 0xfc;
switch (fmt) {
case AUDIO_HAL_I2S_NORMAL:
ESP_LOGD(TAG, "ES7210 in I2S Format");
adc_iface |= 0x00;
break;
case AUDIO_HAL_I2S_LEFT:
case AUDIO_HAL_I2S_RIGHT:
ESP_LOGD(TAG, "ES7210 in LJ Format");
adc_iface |= 0x01;
break;
case AUDIO_HAL_I2S_DSP:
if (I2S_DSP_MODE_A) {
ESP_LOGD(TAG, "ES7210 in DSP-A Format");
adc_iface |= 0x03;
} else {
ESP_LOGD(TAG, "ES7210 in DSP-B Format");
adc_iface |= 0x13;
}
break;
default:
adc_iface &= 0xfc;
break;
}
ret |= es7210_write_reg(ES7210_SDP_INTERFACE1_REG11, adc_iface);
/* Force ADC1/2 output to SDOUT1 and ADC3/4 output to SDOUT2 */
ret |= es7210_write_reg(ES7210_SDP_INTERFACE2_REG12, 0x00);
return ret;
}
esp_err_t es7210_set_bits(audio_hal_iface_bits_t bits)
{
esp_err_t ret = ESP_OK;
uint8_t adc_iface = 0;
adc_iface = es7210_read_reg(ES7210_SDP_INTERFACE1_REG11);
adc_iface &= 0x1f;
switch (bits) {
case AUDIO_HAL_BIT_LENGTH_16BITS:
adc_iface |= 0x60;
break;
case AUDIO_HAL_BIT_LENGTH_24BITS:
adc_iface |= 0x00;
break;
case AUDIO_HAL_BIT_LENGTH_32BITS:
adc_iface |= 0x80;
break;
default:
adc_iface |= 0x60;
break;
}
ret |= es7210_write_reg(ES7210_SDP_INTERFACE1_REG11, adc_iface);
return ret;
}
esp_err_t es7210_adc_config_i2s(audio_hal_codec_mode_t mode, audio_hal_codec_i2s_iface_t *iface)
{
esp_err_t ret = ESP_OK;
ret |= es7210_set_bits(iface->bits);
ret |= es7210_config_fmt(iface->fmt);
ret |= es7210_config_sample(iface->samples);
return ret;
}
esp_err_t es7210_start(uint8_t clock_reg_value)
{
esp_err_t ret = ESP_OK;
ret |= es7210_write_reg(ES7210_CLOCK_OFF_REG01, clock_reg_value);
ret |= es7210_write_reg(ES7210_POWER_DOWN_REG06, 0x00);
// ret |= es7210_write_reg(ES7210_ANALOG_REG40, 0x40);
ret |= es7210_write_reg(ES7210_MIC1_POWER_REG47, 0x00);
ret |= es7210_write_reg(ES7210_MIC2_POWER_REG48, 0x00);
ret |= es7210_write_reg(ES7210_MIC3_POWER_REG49, 0x00);
ret |= es7210_write_reg(ES7210_MIC4_POWER_REG4A, 0x00);
ret |= es7210_mic_select(mic_select);
return ret;
}
esp_err_t es7210_stop(void)
{
esp_err_t ret = ESP_OK;
ret |= es7210_write_reg(ES7210_MIC1_POWER_REG47, 0xff);
ret |= es7210_write_reg(ES7210_MIC2_POWER_REG48, 0xff);
ret |= es7210_write_reg(ES7210_MIC3_POWER_REG49, 0xff);
ret |= es7210_write_reg(ES7210_MIC4_POWER_REG4A, 0xff);
ret |= es7210_write_reg(ES7210_MIC12_POWER_REG4B,0xff);
ret |= es7210_write_reg(ES7210_MIC34_POWER_REG4C, 0xff);
// ret |= es7210_write_reg(ES7210_ANALOG_REG40, 0xc0);
ret |= es7210_write_reg(ES7210_CLOCK_OFF_REG01, 0x7f);
ret |= es7210_write_reg(ES7210_POWER_DOWN_REG06, 0x07);
return ret;
}
esp_err_t es7210_adc_ctrl_state(audio_hal_codec_mode_t mode, audio_hal_ctrl_t ctrl_state)
{
static uint8_t regv;
esp_err_t ret = ESP_OK;
// ESP_LOGW(TAG, "ES7210 only supports ADC mode");
ret = es7210_read_reg(ES7210_CLOCK_OFF_REG01);
if ((ret != 0x7f) && (ret != 0xff)) {
regv = es7210_read_reg(ES7210_CLOCK_OFF_REG01);
}
if (ctrl_state == AUDIO_HAL_CTRL_START) {
ESP_LOGI(TAG, "The ES7210_CLOCK_OFF_REG01 value before stop is %x",regv);
ret |= es7210_start(regv);
} else {
ESP_LOGW(TAG, "The codec is about to stop");
regv = es7210_read_reg(ES7210_CLOCK_OFF_REG01);
ret |= es7210_stop();
}
return ESP_OK;
}
esp_err_t es7210_adc_set_gain(es7210_input_mics_t mic_mask, es7210_gain_value_t gain)
{
esp_err_t ret_val = ESP_OK;
if (gain < GAIN_0DB) {
gain = GAIN_0DB;
}
if (gain > GAIN_37_5DB) {
gain = GAIN_37_5DB;
}
if (mic_mask & ES7210_INPUT_MIC1) {
ret_val |= es7210_update_reg_bit(ES7210_MIC1_GAIN_REG43, 0x0f, gain);
}
if (mic_mask & ES7210_INPUT_MIC2) {
ret_val |= es7210_update_reg_bit(ES7210_MIC2_GAIN_REG44, 0x0f, gain);
}
if (mic_mask & ES7210_INPUT_MIC3) {
ret_val |= es7210_update_reg_bit(ES7210_MIC3_GAIN_REG45, 0x0f, gain);
}
if (mic_mask & ES7210_INPUT_MIC4) {
ret_val |= es7210_update_reg_bit(ES7210_MIC4_GAIN_REG46, 0x0f, gain);
}
return ret_val;
}
esp_err_t es7210_adc_set_gain_all(es7210_gain_value_t gain)
{
esp_err_t ret = ESP_OK;
uint32_t max_gain_vaule = 14;
if (gain < 0) {
gain = (es7210_gain_value_t) 0;
} else if (gain > max_gain_vaule) {
gain = (es7210_gain_value_t) max_gain_vaule;
}
ESP_LOGD(TAG, "SET: gain:%d", gain);
if (mic_select & ES7210_INPUT_MIC1) {
ret |= es7210_update_reg_bit(ES7210_MIC1_GAIN_REG43, 0x0f, gain);
}
if (mic_select & ES7210_INPUT_MIC2) {
ret |= es7210_update_reg_bit(ES7210_MIC2_GAIN_REG44, 0x0f, gain);
}
if (mic_select & ES7210_INPUT_MIC3) {
ret |= es7210_update_reg_bit(ES7210_MIC3_GAIN_REG45, 0x0f, gain);
}
if (mic_select & ES7210_INPUT_MIC4) {
ret |= es7210_update_reg_bit(ES7210_MIC4_GAIN_REG46, 0x0f, gain);
}
return ret;
}
esp_err_t es7210_adc_get_gain(es7210_input_mics_t mic_mask, es7210_gain_value_t *gain)
{
int regv = 0;
uint8_t gain_value;
if (mic_mask & ES7210_INPUT_MIC1) {
regv = es7210_read_reg(ES7210_MIC1_GAIN_REG43);
} else if (mic_mask & ES7210_INPUT_MIC2) {
regv = es7210_read_reg(ES7210_MIC2_GAIN_REG44);
} else if (mic_mask & ES7210_INPUT_MIC3) {
regv = es7210_read_reg(ES7210_MIC3_GAIN_REG45);
} else if (mic_mask & ES7210_INPUT_MIC4) {
regv = es7210_read_reg(ES7210_MIC4_GAIN_REG46);
} else {
ESP_LOGE(TAG, "No MIC selected");
return ESP_FAIL;
}
if (regv == ESP_FAIL) {
return regv;
}
gain_value = (regv & 0x0f); /* Retain the last four bits for gain */
*gain = (es7210_gain_value_t) gain_value;
ESP_LOGI(TAG, "GET: gain_value:%d", gain_value);
return ESP_OK;
}
esp_err_t es7210_adc_set_volume(int volume)
{
esp_err_t ret = ESP_OK;
ESP_LOGD(TAG, "ADC can adjust gain");
return ret;
}
esp_err_t es7210_set_mute(bool enable)
{
ESP_LOGD(TAG, "ES7210 SetMute :%d", enable);
return ESP_OK;
}
void es7210_read_all(void)
{
for (int i = 0; i <= 0x4E; i++) {
uint8_t reg = es7210_read_reg(i);
ets_printf("REG:%02x, %02x\n", reg, i);
}
}
#endif
@@ -0,0 +1,260 @@
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2021 <ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD>
*
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef _ES7210_H
#define _ES7210_H
#include "audio_hal.h"
#include <Wire.h>
typedef enum {
ES7210_AD1_AD0_00 = 0x40,
ES7210_AD1_AD0_01 = 0x41,
ES7210_AD1_AD0_10 = 0x42,
ES7210_AD1_AD0_11 = 0x43,
} es7210_address_t;
/* ES7210 address*/
#define ES7210_ADDR ES7210_AD1_AD0_00
#ifdef __cplusplus
extern "C" {
#endif
#define ES7210_RESET_REG00 0x00 /* Reset control */
#define ES7210_CLOCK_OFF_REG01 0x01 /* Used to turn off the ADC clock */
#define ES7210_MAINCLK_REG02 0x02 /* Set ADC clock frequency division */
#define ES7210_MASTER_CLK_REG03 0x03 /* MCLK source $ SCLK division */
#define ES7210_LRCK_DIVH_REG04 0x04 /* lrck_divh */
#define ES7210_LRCK_DIVL_REG05 0x05 /* lrck_divl */
#define ES7210_POWER_DOWN_REG06 0x06 /* power down */
#define ES7210_OSR_REG07 0x07
#define ES7210_MODE_CONFIG_REG08 0x08 /* Set master/slave & channels */
#define ES7210_TIME_CONTROL0_REG09 0x09 /* Set Chip intial state period*/
#define ES7210_TIME_CONTROL1_REG0A 0x0A /* Set Power up state period */
#define ES7210_SDP_INTERFACE1_REG11 0x11 /* Set sample & fmt */
#define ES7210_SDP_INTERFACE2_REG12 0x12 /* Pins state */
#define ES7210_ADC_AUTOMUTE_REG13 0x13 /* Set mute */
#define ES7210_ADC34_MUTERANGE_REG14 0x14 /* Set mute range */
#define ES7210_ADC34_HPF2_REG20 0x20 /* HPF */
#define ES7210_ADC34_HPF1_REG21 0x21
#define ES7210_ADC12_HPF1_REG22 0x22
#define ES7210_ADC12_HPF2_REG23 0x23
#define ES7210_ANALOG_REG40 0x40 /* ANALOG Power */
#define ES7210_MIC12_BIAS_REG41 0x41
#define ES7210_MIC34_BIAS_REG42 0x42
#define ES7210_MIC1_GAIN_REG43 0x43
#define ES7210_MIC2_GAIN_REG44 0x44
#define ES7210_MIC3_GAIN_REG45 0x45
#define ES7210_MIC4_GAIN_REG46 0x46
#define ES7210_MIC1_POWER_REG47 0x47
#define ES7210_MIC2_POWER_REG48 0x48
#define ES7210_MIC3_POWER_REG49 0x49
#define ES7210_MIC4_POWER_REG4A 0x4A
#define ES7210_MIC12_POWER_REG4B 0x4B /* MICBias & ADC & PGA Power */
#define ES7210_MIC34_POWER_REG4C 0x4C
typedef enum {
ES7210_INPUT_MIC1 = 0x01,
ES7210_INPUT_MIC2 = 0x02,
ES7210_INPUT_MIC3 = 0x04,
ES7210_INPUT_MIC4 = 0x08
} es7210_input_mics_t;
typedef enum gain_value{
GAIN_0DB = 0,
GAIN_3DB,
GAIN_6DB,
GAIN_9DB,
GAIN_12DB,
GAIN_15DB,
GAIN_18DB,
GAIN_21DB,
GAIN_24DB,
GAIN_27DB,
GAIN_30DB,
GAIN_33DB,
GAIN_34_5DB,
GAIN_36DB,
GAIN_37_5DB,
} es7210_gain_value_t;
/*
* @brief Initialize ES7210 ADC chip
*
* @param[in] codec_cfg: configuration of ES7210
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t es7210_adc_init(TwoWire *tw, audio_hal_codec_config_t *codec_cfg);
/**
* @brief Deinitialize ES7210 ADC chip
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t es7210_adc_deinit();
/**
* @brief Configure ES7210 ADC mode and I2S interface
*
* @param[in] mode: codec mode
* @param[in] iface: I2S config
*
* @return
* - ESP_FAIL Parameter error
* - ESP_OK Success
*/
esp_err_t es7210_adc_config_i2s(audio_hal_codec_mode_t mode, audio_hal_codec_i2s_iface_t *iface);
/**
* @brief Control ES7210 ADC chip
*
* @param[in] mode: codec mode
* @param[in] ctrl_state: start or stop progress
*
* @return
* - ESP_FAIL Parameter error
* - ESP_OK Success
*/
esp_err_t es7210_adc_ctrl_state(audio_hal_codec_mode_t mode, audio_hal_ctrl_t ctrl_state);
/**
* @brief Set gain of given mask
*
* @param[in] mic_mask Mask of MIC channel
*
* @param[in] gain: gain
*
* gain : value
* GAIN_0DB : 1
* GAIN_3DB : 2
* GAIN_6DB : 3
* ·
* ·
* ·
* GAIN_30DB : 10
* GAIN_33DB : 11
* GAIN_34_5DB : 12
* GAIN_36DB : 13
* GAIN_37_5DB : 14
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t es7210_adc_set_gain(es7210_input_mics_t mic_mask, es7210_gain_value_t gain);
/**
* @brief Set gain (Note: the enabled microphone sets the same gain)
*
* @param[in] gain: gain
*
* gain : value
* GAIN_0DB : 1
* GAIN_3DB : 2
* GAIN_6DB : 3
* ·
* ·
* ·
* GAIN_30DB : 10
* GAIN_33DB : 11
* GAIN_34_5DB : 12
* GAIN_36DB : 13
* GAIN_37_5DB : 14
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t es7210_adc_set_gain_all(es7210_gain_value_t gain);
/**
* @brief Get MIC gain
*
* @param mic_mask Selected MIC
* @param gain Pointer to `es7210_gain_value_t`
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t es7210_adc_get_gain(es7210_input_mics_t mic_mask, es7210_gain_value_t *gain);
/**
* @brief Set volume
*
* @param[in] volume: volume
*
* @return
* - ESP_OK
*/
esp_err_t es7210_adc_set_volume(int volume);
/**
* @brief Set ES7210 ADC mute status
*
* @return
* - ESP_FAIL
* - ESP_OK
*/
esp_err_t es7210_set_mute(bool enable);
/**
* @brief Select ES7210 mic
*
* @param[in] mic: mics
*
* @return
* - ESP_FAIL
* - ESP_OK
*/
esp_err_t es7210_mic_select(es7210_input_mics_t mic);
/**
* @brief Read regs of ES7210
*
* @param[in] reg_addr: reg_addr
*
* @return
* - ESP_FAIL
* - ESP_OK
*/
int es7210_read_reg(uint8_t reg_addr);
/**
* @brief Read all regs of ES7210
*/
void es7210_read_all(void);
#ifdef __cplusplus
}
#endif
#endif /* _ES7210_H_ */
@@ -0,0 +1,443 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "es8311.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "es8311_reg.h"
#include "esp32-hal-i2c.h"
typedef struct {
unsigned int port;
uint16_t dev_addr;
} es8311_dev_t;
/*
* Clock coefficient structure
*/
struct _coeff_div {
uint32_t mclk; /* mclk frequency */
uint32_t rate; /* sample rate */
uint8_t pre_div; /* the pre divider with range from 1 to 8 */
uint8_t pre_multi; /* the pre multiplier with 0: 1x, 1: 2x, 2: 4x, 3: 8x selection */
uint8_t adc_div; /* adcclk divider */
uint8_t dac_div; /* dacclk divider */
uint8_t fs_mode; /* double speed or single speed, =0, ss, =1, ds */
uint8_t lrck_h; /* adclrck divider and daclrck divider */
uint8_t lrck_l;
uint8_t bclk_div; /* sclk divider */
uint8_t adc_osr; /* adc osr */
uint8_t dac_osr; /* dac osr */
};
/* codec hifi mclk clock divider coefficients */
static const struct _coeff_div coeff_div[] = {
/*!<mclk rate pre_div mult adc_div dac_div fs_mode lrch lrcl bckdiv osr */
/* 8k */
{12288000, 8000, 0x06, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{18432000, 8000, 0x03, 0x01, 0x03, 0x03, 0x00, 0x05, 0xff, 0x18, 0x10, 0x10},
{16384000, 8000, 0x08, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{8192000, 8000, 0x04, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 8000, 0x03, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{4096000, 8000, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 8000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{2048000, 8000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 8000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1024000, 8000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 11.025k */
{11289600, 11025, 0x04, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{5644800, 11025, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{2822400, 11025, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1411200, 11025, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 12k */
{12288000, 12000, 0x04, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 12000, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 12000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 12000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 16k */
{12288000, 16000, 0x03, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{18432000, 16000, 0x03, 0x01, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10},
{16384000, 16000, 0x04, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{8192000, 16000, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 16000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{4096000, 16000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 16000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{2048000, 16000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 16000, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1024000, 16000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 22.05k */
{11289600, 22050, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{5644800, 22050, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{2822400, 22050, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1411200, 22050, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{705600, 22050, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 24k */
{12288000, 24000, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{18432000, 24000, 0x03, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 24000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 24000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 24000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 32k */
{12288000, 32000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{18432000, 32000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10},
{16384000, 32000, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{8192000, 32000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 32000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{4096000, 32000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 32000, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{2048000, 32000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 32000, 0x03, 0x03, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
{1024000, 32000, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 44.1k */
{11289600, 44100, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{5644800, 44100, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{2822400, 44100, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1411200, 44100, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 48k */
{12288000, 48000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{18432000, 48000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 48000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 48000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 48000, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
/* 64k */
{12288000, 64000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{18432000, 64000, 0x03, 0x02, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10},
{16384000, 64000, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{8192000, 64000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 64000, 0x01, 0x02, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10},
{4096000, 64000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 64000, 0x01, 0x03, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10},
{2048000, 64000, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 64000, 0x01, 0x03, 0x01, 0x01, 0x01, 0x00, 0xbf, 0x03, 0x18, 0x18},
{1024000, 64000, 0x01, 0x03, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
/* 88.2k */
{11289600, 88200, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{5644800, 88200, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{2822400, 88200, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1411200, 88200, 0x01, 0x03, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
/* 96k */
{12288000, 96000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{18432000, 96000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{6144000, 96000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{3072000, 96000, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10},
{1536000, 96000, 0x01, 0x03, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10},
};
static const char *TAG = "ES8311";
static inline esp_err_t es8311_write_reg(es8311_handle_t dev, uint8_t reg_addr, uint8_t data)
{
es8311_dev_t *es = (es8311_dev_t *) dev;
const uint8_t write_buf[2] = {reg_addr, data};
return i2cWrite(es->port, es->dev_addr, write_buf, sizeof(write_buf), 1000);
}
static inline esp_err_t es8311_read_reg(es8311_handle_t dev, uint8_t reg_addr, uint8_t *reg_value)
{
es8311_dev_t *es = (es8311_dev_t *) dev;
size_t readCount = 0;
return i2cWriteReadNonStop(es->port, es->dev_addr, &reg_addr, 1, reg_value, 1, 1000, &readCount);
}
/*
* look for the coefficient in coeff_div[] table
*/
static int get_coeff(uint32_t mclk, uint32_t rate)
{
for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) {
if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) {
return i;
}
}
return -1;
}
esp_err_t es8311_sample_frequency_config(es8311_handle_t dev, int mclk_frequency, int sample_frequency)
{
uint8_t regv;
/* Get clock coefficients from coefficient table */
int coeff = get_coeff(mclk_frequency, sample_frequency);
if (coeff < 0) {
ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK", sample_frequency, mclk_frequency);
return ESP_ERR_INVALID_ARG;
}
const struct _coeff_div *const selected_coeff = &coeff_div[coeff];
/* register 0x02 */
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_CLK_MANAGER_REG02, &regv), TAG, "I2C read/write error");
regv &= 0x07;
regv |= (selected_coeff->pre_div - 1) << 5;
regv |= selected_coeff->pre_multi << 3;
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG02, regv), TAG, "I2C read/write error");
/* register 0x03 */
const uint8_t reg03 = (selected_coeff->fs_mode << 6) | selected_coeff->adc_osr;
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG03, reg03), TAG, "I2C read/write error");
/* register 0x04 */
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG04, selected_coeff->dac_osr), TAG, "I2C read/write error");
/* register 0x05 */
const uint8_t reg05 = ((selected_coeff->adc_div - 1) << 4) | (selected_coeff->dac_div - 1);
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG05, reg05), TAG, "I2C read/write error");
/* register 0x06 */
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_CLK_MANAGER_REG06, &regv), TAG, "I2C read/write error");
regv &= 0xE0;
if (selected_coeff->bclk_div < 19) {
regv |= (selected_coeff->bclk_div - 1) << 0;
} else {
regv |= (selected_coeff->bclk_div) << 0;
}
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG06, regv), TAG, "I2C read/write error");
/* register 0x07 */
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_CLK_MANAGER_REG07, &regv), TAG, "I2C read/write error");
regv &= 0xC0;
regv |= selected_coeff->lrck_h << 0;
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG07, regv), TAG, "I2C read/write error");
/* register 0x08 */
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG08, selected_coeff->lrck_l), TAG, "I2C read/write error");
return ESP_OK;
}
static esp_err_t es8311_clock_config(es8311_handle_t dev, const es8311_clock_config_t *const clk_cfg, es8311_resolution_t res)
{
uint8_t reg06;
uint8_t reg01 = 0x3F; // Enable all clocks
int mclk_hz;
/* Select clock source for internal MCLK and determine its frequency */
if (clk_cfg->mclk_from_mclk_pin) {
mclk_hz = clk_cfg->mclk_frequency;
} else {
mclk_hz = clk_cfg->sample_frequency * (int)res * 2;
reg01 |= BIT(7); // Select BCLK (a.k.a. SCK) pin
}
if (clk_cfg->mclk_inverted) {
reg01 |= BIT(6); // Invert MCLK pin
}
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG01, reg01), TAG, "I2C read/write error");
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_CLK_MANAGER_REG06, &reg06), TAG, "I2C read/write error");
if (clk_cfg->sclk_inverted) {
reg06 |= BIT(5);
} else {
reg06 &= ~BIT(5);
}
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_CLK_MANAGER_REG06, reg06), TAG, "I2C read/write error");
/* Configure clock dividers */
return es8311_sample_frequency_config(dev, mclk_hz, clk_cfg->sample_frequency);
}
static esp_err_t es8311_resolution_config(const es8311_resolution_t res, uint8_t *reg)
{
switch (res) {
case ES8311_RESOLUTION_16:
*reg |= (3 << 2);
break;
case ES8311_RESOLUTION_18:
*reg |= (2 << 2);
break;
case ES8311_RESOLUTION_20:
*reg |= (1 << 2);
break;
case ES8311_RESOLUTION_24:
*reg |= (0 << 2);
break;
case ES8311_RESOLUTION_32:
*reg |= (4 << 2);
break;
default:
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
static esp_err_t es8311_fmt_config(es8311_handle_t dev, const es8311_resolution_t res_in, const es8311_resolution_t res_out)
{
uint8_t reg09 = 0; // SDP In
uint8_t reg0a = 0; // SDP Out
ESP_LOGI(TAG, "ES8311 in Slave mode and I2S format");
uint8_t reg00;
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_RESET_REG00, &reg00), TAG, "I2C read/write error");
reg00 &= 0xBF;
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_RESET_REG00, reg00), TAG, "I2C read/write error"); // Slave serial port - default
/* Setup SDP In and Out resolution */
es8311_resolution_config(res_in, &reg09);
es8311_resolution_config(res_out, &reg0a);
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_SDPIN_REG09, reg09), TAG, "I2C read/write error");
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_SDPOUT_REG0A, reg0a), TAG, "I2C read/write error");
return ESP_OK;
}
esp_err_t es8311_microphone_config(es8311_handle_t dev, bool digital_mic)
{
uint8_t reg14 = 0x1A; // enable analog MIC and max PGA gain
/* PDM digital microphone enable or disable */
if (digital_mic) {
reg14 |= BIT(6);
}
es8311_write_reg(dev, ES8311_ADC_REG17, 0xC8); // Set ADC gain @todo move this to ADC config section
return es8311_write_reg(dev, ES8311_SYSTEM_REG14, reg14);
}
esp_err_t es8311_init(es8311_handle_t dev, const es8311_clock_config_t *const clk_cfg, const es8311_resolution_t res_in, const es8311_resolution_t res_out)
{
ESP_RETURN_ON_FALSE(
(clk_cfg->sample_frequency >= 8000) && (clk_cfg->sample_frequency <= 96000),
ESP_ERR_INVALID_ARG, TAG, "ES8311 init needs frequency in interval [8000; 96000] Hz"
);
if (!clk_cfg->mclk_from_mclk_pin) {
ESP_RETURN_ON_FALSE(res_out == res_in, ESP_ERR_INVALID_ARG, TAG, "Resolution IN/OUT must be equal if MCLK is taken from SCK pin");
}
/* Reset ES8311 to its default */
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_RESET_REG00, 0x1F), TAG, "I2C read/write error");
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_RESET_REG00, 0x00), TAG, "I2C read/write error");
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_RESET_REG00, 0x80), TAG, "I2C read/write error"); // Power-on command
/* Setup clock: source, polarity and clock dividers */
ESP_RETURN_ON_ERROR(es8311_clock_config(dev, clk_cfg, res_out), TAG, "");
/* Setup audio format (fmt): master/slave, resolution, I2S */
ESP_RETURN_ON_ERROR(es8311_fmt_config(dev, res_in, res_out), TAG, "");
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_SYSTEM_REG0D, 0x01), TAG, "I2C read/write error"); // Power up analog circuitry - NOT default
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_SYSTEM_REG0E, 0x02), TAG, "I2C read/write error"); // Enable analog PGA, enable ADC modulator - NOT default
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_SYSTEM_REG12, 0x00), TAG, "I2C read/write error"); // power-up DAC - NOT default
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_SYSTEM_REG13, 0x10), TAG, "I2C read/write error"); // Enable output to HP drive - NOT default
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_ADC_REG1C, 0x6A), TAG, "I2C read/write error"); // ADC Equalizer bypass, cancel DC offset in digital domain
ESP_RETURN_ON_ERROR(es8311_write_reg(dev, ES8311_DAC_REG37, 0x08), TAG, "I2C read/write error"); // Bypass DAC equalizer - NOT default
return ESP_OK;
}
void es8311_delete(es8311_handle_t dev)
{
free(dev);
}
esp_err_t es8311_voice_volume_set(es8311_handle_t dev, int volume, int *volume_set)
{
if (volume < 0) {
volume = 0;
} else if (volume > 100) {
volume = 100;
}
int reg32;
if (volume == 0) {
reg32 = 0;
} else {
reg32 = ((volume) * 256 / 100) - 1;
}
// provide user with real volume set
if (volume_set != NULL) {
*volume_set = volume;
}
return es8311_write_reg(dev, ES8311_DAC_REG32, reg32);
}
esp_err_t es8311_voice_volume_get(es8311_handle_t dev, int *volume)
{
uint8_t reg32;
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_DAC_REG32, &reg32), TAG, "I2C read/write error");
if (reg32 == 0) {
*volume = 0;
} else {
*volume = ((reg32 * 100) / 256) + 1;
}
return ESP_OK;
}
esp_err_t es8311_voice_mute(es8311_handle_t dev, bool mute)
{
uint8_t reg31;
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_DAC_REG31, &reg31), TAG, "I2C read/write error");
if (mute) {
reg31 |= BIT(6) | BIT(5);
} else {
reg31 &= ~(BIT(6) | BIT(5));
}
return es8311_write_reg(dev, ES8311_DAC_REG31, reg31);
}
esp_err_t es8311_microphone_gain_set(es8311_handle_t dev, es8311_mic_gain_t gain_db)
{
return es8311_write_reg(dev, ES8311_ADC_REG16, gain_db); // ADC gain scale up
}
esp_err_t es8311_voice_fade(es8311_handle_t dev, const es8311_fade_t fade)
{
uint8_t reg37;
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_DAC_REG37, &reg37), TAG, "I2C read/write error");
reg37 &= 0x0F;
reg37 |= (fade << 4);
return es8311_write_reg(dev, ES8311_DAC_REG37, reg37);
}
esp_err_t es8311_microphone_fade(es8311_handle_t dev, const es8311_fade_t fade)
{
uint8_t reg15;
ESP_RETURN_ON_ERROR(es8311_read_reg(dev, ES8311_ADC_REG15, &reg15), TAG, "I2C read/write error");
reg15 &= 0x0F;
reg15 |= (fade << 4);
return es8311_write_reg(dev, ES8311_ADC_REG15, reg15);
}
void es8311_register_dump(es8311_handle_t dev)
{
for (int reg = 0; reg < 0x4A; reg++) {
uint8_t value;
ESP_ERROR_CHECK(es8311_read_reg(dev, reg, &value));
printf("REG:%02x: %02x", reg, value);
}
}
es8311_handle_t es8311_create(const unsigned int port, const uint16_t dev_addr)
{
es8311_dev_t *sensor = (es8311_dev_t *) calloc(1, sizeof(es8311_dev_t));
sensor->port = port;
sensor->dev_addr = dev_addr;
return (es8311_handle_t) sensor;
}
@@ -0,0 +1,227 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ES8311 driver
*/
#pragma once
#include "esp_types.h"
#include "esp_err.h"
/* ES8311 address: CE pin low - 0x18, CE pin high - 0x19 */
#define ES8311_ADDRRES_0 0x18u // Leaving this here for backward compatibility
#define ES8311_ADDRESS_0 0x18u
#define ES8311_ADDRESS_1 0x19u
#ifdef __cplusplus
extern "C" {
#endif
typedef void *es8311_handle_t;
typedef enum {
ES8311_MIC_GAIN_MIN = -1,
ES8311_MIC_GAIN_0DB,
ES8311_MIC_GAIN_6DB,
ES8311_MIC_GAIN_12DB,
ES8311_MIC_GAIN_18DB,
ES8311_MIC_GAIN_24DB,
ES8311_MIC_GAIN_30DB,
ES8311_MIC_GAIN_36DB,
ES8311_MIC_GAIN_42DB,
ES8311_MIC_GAIN_MAX
} es8311_mic_gain_t;
typedef enum {
ES8311_FADE_OFF = 0,
ES8311_FADE_4LRCK, // 4LRCK means ramp 0.25dB/4LRCK
ES8311_FADE_8LRCK,
ES8311_FADE_16LRCK,
ES8311_FADE_32LRCK,
ES8311_FADE_64LRCK,
ES8311_FADE_128LRCK,
ES8311_FADE_256LRCK,
ES8311_FADE_512LRCK,
ES8311_FADE_1024LRCK,
ES8311_FADE_2048LRCK,
ES8311_FADE_4096LRCK,
ES8311_FADE_8192LRCK,
ES8311_FADE_16384LRCK,
ES8311_FADE_32768LRCK,
ES8311_FADE_65536LRCK
} es8311_fade_t;
typedef enum es8311_resolution_t {
ES8311_RESOLUTION_16 = 16,
ES8311_RESOLUTION_18 = 18,
ES8311_RESOLUTION_20 = 20,
ES8311_RESOLUTION_24 = 24,
ES8311_RESOLUTION_32 = 32
} es8311_resolution_t;
typedef struct es8311_clock_config_t {
bool mclk_inverted;
bool sclk_inverted;
bool mclk_from_mclk_pin; // true: from MCLK pin (pin no. 2), false: from SCLK pin (pin no. 6)
int mclk_frequency; // This parameter is ignored if MCLK is taken from SCLK pin
int sample_frequency; // in Hz
} es8311_clock_config_t;
/**
* @brief Initialize ES8311
*
* There are two ways of providing Master Clock (MCLK) signal to ES8311 in Slave Mode:
* 1. From MCLK pin:
* For flexible scenarios. A clock signal from I2S master is routed to MCLK pin.
* Its frequency must be defined in clk_cfg->mclk_frequency parameter.
* 2. From SCLK pin:
* For simpler scenarios. ES8311 takes its clock from SCK pin. MCLK pin does not have to be connected.
* In this case, res_in must equal res_out; clk_cfg->mclk_frequency parameter is ignored
* and MCLK is calculated as MCLK = clk_cfg->sample_frequency * res_out * 2.
* Not all sampling frequencies are supported in this mode.
*
* @param dev ES8311 handle
* @param[in] clk_cfg Clock configuration
* @param[in] res_in Input serial port resolution
* @param[in] res_out Output serial port resolution
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Sample frequency or resolution invalid
* - Else fail
*/
esp_err_t es8311_init(es8311_handle_t dev, const es8311_clock_config_t *const clk_cfg, const es8311_resolution_t res_in,
const es8311_resolution_t res_out);
/**
* @brief Set output volume
*
* Volume paramter out of <0, 100> interval will be truncated.
*
* @param dev ES8311 handle
* @param[in] volume Set volume (0 ~ 100)
* @param[out] volume_set Volume that was set. Same as volume, unless volume is outside of <0, 100> interval.
* This parameter can be set to NULL, if user does not need this information.
*
* @return
* - ESP_OK success
* - Else fail
*/
esp_err_t es8311_voice_volume_set(es8311_handle_t dev, int volume, int *volume_set);
/**
* @brief Get output volume
*
* @param dev ES8311 handle
* @param[out] volume get volume (0 ~ 100)
*
* @return
* - ESP_OK success
* - Else fail
*/
esp_err_t es8311_voice_volume_get(es8311_handle_t dev, int *volume);
/**
* @brief Print out ES8311 register content
*
* @param dev ES8311 handle
*/
void es8311_register_dump(es8311_handle_t dev);
/**
* @brief Mute ES8311 output
*
* @param dev ES8311 handle
* @param[in] enable true: mute, false: don't mute
* @return
* - ESP_OK success
* - Else fail
*/
esp_err_t es8311_voice_mute(es8311_handle_t dev, bool enable);
/**
* @brief Set Microphone gain
*
* @param dev ES8311 handle
* @param[in] gain_db Microphone gain
* @return
* - ESP_OK success
* - Else fail
*/
esp_err_t es8311_microphone_gain_set(es8311_handle_t dev, es8311_mic_gain_t gain_db);
/**
* @brief Configure microphone
*
* @param dev ES8311 handle
* @param[in] digital_mic Set to true for digital microphone
* @return
* - ESP_OK success
* - Else fail
*/
esp_err_t es8311_microphone_config(es8311_handle_t dev, bool digital_mic);
/**
* @brief Configure sampling frequency
*
* @note This function is called by es8311_init().
* Call this function explicitly only if you want to change sample frequency during runtime.
* @param dev ES8311 handle
* @param[in] mclk_frequency MCLK frequency in [Hz] (MCLK or SCLK pin, depending on bit register01[7])
* @param[in] sample_frequency Required sample frequency in [Hz], e.g. 44100, 22050...
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG cannot set clock dividers for given MCLK and sampling frequency
* - Else I2C read/write error
*/
esp_err_t es8311_sample_frequency_config(es8311_handle_t dev, int mclk_frequency, int sample_frequency);
/**
* @brief Configure fade in/out for ADC: voice
*
* @param dev ES8311 handle
* @param[in] fade Fade ramp rate
* @return
* - ESP_OK success
* - Else I2C read/write error
*/
esp_err_t es8311_voice_fade(es8311_handle_t dev, const es8311_fade_t fade);
/**
* @brief Configure fade in/out for DAC: microphone
*
* @param dev ES8311 handle
* @param[in] fade Fade ramp rate
* @return
* - ESP_OK success
* - Else I2C read/write error
*/
esp_err_t es8311_microphone_fade(es8311_handle_t dev, const es8311_fade_t fade);
/**
* @brief Create ES8311 object and return its handle
*
* @param[in] port I2C port number
* @param[in] dev_addr I2C device address of ES8311
*
* @return
* - NULL Fail
* - Others Success
*/
es8311_handle_t es8311_create(const unsigned int port, const uint16_t dev_addr);
/**
* @brief Delete ES8311 object
*
* @param dev ES8311 handle
*/
void es8311_delete(es8311_handle_t dev);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/*
* ES8311_REGISTER NAME_REG_REGISTER ADDRESS
*/
#define ES8311_RESET_REG00 0x00 /*reset digital,csm,clock manager etc.*/
/*
* Clock Scheme Register definition
*/
#define ES8311_CLK_MANAGER_REG01 0x01 /* select clk src for mclk, enable clock for codec */
#define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */
#define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */
#define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */
#define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */
#define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */
#define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */
#define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */
/*
* SDP
*/
#define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */
#define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */
/*
* SYSTEM
*/
#define ES8311_SYSTEM_REG0B 0x0B /* system */
#define ES8311_SYSTEM_REG0C 0x0C /* system */
#define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */
#define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */
#define ES8311_SYSTEM_REG0F 0x0F /* system, low power */
#define ES8311_SYSTEM_REG10 0x10 /* system */
#define ES8311_SYSTEM_REG11 0x11 /* system */
#define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */
#define ES8311_SYSTEM_REG13 0x13 /* system */
#define ES8311_SYSTEM_REG14 0x14 /* system, select DMIC, select analog pga gain */
/*
* ADC
*/
#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */
#define ES8311_ADC_REG16 0x16 /* ADC */
#define ES8311_ADC_REG17 0x17 /* ADC, volume */
#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */
#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */
#define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */
#define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */
#define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */
/*
* DAC
*/
#define ES8311_DAC_REG31 0x31 /* DAC, mute */
#define ES8311_DAC_REG32 0x32 /* DAC, volume */
#define ES8311_DAC_REG33 0x33 /* DAC, offset */
#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */
#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */
#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */
/*
*GPIO
*/
#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */
#define ES8311_GP_REG45 0x45 /* GP CONTROL */
/*
* CHIP
*/
#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */
#define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */
#define ES8311_CHVER_REGFF 0xFF /* VERSION */
#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */
#define ES8311_MAX_REGISTER 0xFF
@@ -0,0 +1,272 @@
#include <Arduino.h>
#include <Wire.h>
#include <math.h>
#include "ESP_I2S.h"
#include "esp_check.h"
#include "Arduino_GFX_Library.h"
#include "pin_config.h"
#include "TouchDrvCSTXXX.hpp"
#include "SensorQMI8658.hpp"
#include "es8311.h"
#include "es7210.h"
static const int DISP_W = 480;
static const int DISP_H = 480;
static const int SAFE = 20;
static const uint32_t SAMPLE_RATE = 16000;
static const size_t REC_SECONDS = 2;
static const size_t AUDIO_BYTES = SAMPLE_RATE * REC_SECONDS * 2 * 2;
static const int I2C_NUM = 0;
I2SClass i2s;
TouchDrvCST92xx touch;
SensorQMI8658 qmi;
IMUdata acc;
Arduino_DataBus *bus = new Arduino_ESP32QSPI(LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
Arduino_CO5300 *gfx = new Arduino_CO5300(bus, LCD_RESET, 0, DISP_W, DISP_H, 0, 0, 0, 0);
uint8_t *audioBuf = nullptr;
size_t audioLen = 0;
bool touchIrq = false;
String statusLine = "Ready";
String tiltLine = "Tilt: FLAT";
String audioLine = "Audio: empty";
uint32_t lastTiltMs = 0;
bool micReady = false;
void IRAM_ATTR onTouchInt() { touchIrq = true; }
esp_err_t codecInit() {
es8311_handle_t esHandle = es8311_create(I2C_NUM, ES8311_ADDRRES_0);
ESP_RETURN_ON_FALSE(esHandle, ESP_FAIL, "AV", "es8311 create failed");
const es8311_clock_config_t clk = {
.mclk_inverted = false,
.sclk_inverted = false,
.mclk_from_mclk_pin = true,
.mclk_frequency = SAMPLE_RATE * 256,
.sample_frequency = SAMPLE_RATE,
};
ESP_ERROR_CHECK(es8311_init(esHandle, &clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));
ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(esHandle, clk.mclk_frequency, clk.sample_frequency), "AV", "clock cfg failed");
ESP_RETURN_ON_ERROR(es8311_microphone_config(esHandle, false), "AV", "mic cfg failed");
ESP_RETURN_ON_ERROR(es8311_voice_volume_set(esHandle, 90, NULL), "AV", "volume failed");
ESP_RETURN_ON_ERROR(es8311_microphone_gain_set(esHandle, (es8311_mic_gain_t)6), "AV", "mic gain failed");
return ESP_OK;
}
esp_err_t micInit() {
audio_hal_codec_config_t cfg = {
.adc_input = AUDIO_HAL_ADC_INPUT_ALL,
.codec_mode = AUDIO_HAL_CODEC_MODE_ENCODE,
.i2s_iface = {
.mode = AUDIO_HAL_MODE_SLAVE,
.fmt = AUDIO_HAL_I2S_NORMAL,
.samples = AUDIO_HAL_16K_SAMPLES,
.bits = AUDIO_HAL_BIT_LENGTH_16BITS,
},
};
esp_err_t ret = ESP_OK;
ret |= es7210_adc_init(&Wire, &cfg);
ret |= es7210_adc_config_i2s(cfg.codec_mode, &cfg.i2s_iface);
ret |= es7210_adc_set_gain(
(es7210_input_mics_t)(ES7210_INPUT_MIC1 | ES7210_INPUT_MIC2),
(es7210_gain_value_t)GAIN_24DB);
ret |= es7210_adc_set_gain(
(es7210_input_mics_t)(ES7210_INPUT_MIC3 | ES7210_INPUT_MIC4),
(es7210_gain_value_t)GAIN_24DB);
ret |= es7210_adc_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START);
return ret;
}
void drawStaticUi() {
gfx->fillScreen(RGB565_BLACK);
gfx->setTextSize(2);
gfx->setTextColor(RGB565_WHITE);
gfx->setCursor(SAFE + 16, SAFE + 10);
gfx->println("Simple AV + Tilt Test");
gfx->drawRect(24, 76, 132, 64, RGB565_GREEN);
gfx->setCursor(44, 100);
gfx->println("RECORD");
gfx->drawRect(174, 76, 132, 64, RGB565_YELLOW);
gfx->setCursor(212, 100);
gfx->println("PLAY");
gfx->drawRect(324, 76, 132, 64, RGB565_CYAN);
gfx->setCursor(360, 100);
gfx->println("BEEP");
}
void drawDynamic() {
gfx->fillRect(20, 166, 440, 140, RGB565_BLACK);
gfx->setTextSize(2);
gfx->setTextColor(RGB565_WHITE);
gfx->setCursor(28, 178);
gfx->println(statusLine);
gfx->setTextColor(RGB565_CYAN);
gfx->setCursor(28, 212);
gfx->println(tiltLine);
gfx->setTextColor(RGB565_ORANGE);
gfx->setCursor(28, 246);
gfx->println(audioLine);
gfx->drawRect(28, 278, 300, 20, RGB565_DARKGREY);
}
void updateTilt() {
if (!qmi.getDataReady()) return;
if (!qmi.getAccelerometer(acc.x, acc.y, acc.z)) return;
String next = "Tilt: FLAT";
if (acc.x > 0.8f) next = "Tilt: RIGHT";
else if (acc.x < -0.8f) next = "Tilt: LEFT";
else if (acc.y > 0.8f) next = "Tilt: UP";
else if (acc.y < -0.8f) next = "Tilt: DOWN";
if (next != tiltLine) {
tiltLine = next;
drawDynamic();
}
}
void playBeep() {
static int16_t tone[16000];
for (int i = 0; i < 8000; ++i) {
float s = sinf(2.0f * 3.1415926f * 440.0f * ((float)i / SAMPLE_RATE));
int16_t v = (int16_t)(s * 6000.0f);
tone[i * 2] = v;
tone[i * 2 + 1] = v;
}
statusLine = "Playing test beep...";
drawDynamic();
i2s.write((uint8_t *)tone, 8000 * 2 * sizeof(int16_t));
statusLine = "Beep done";
drawDynamic();
}
void recordAudio() {
if (!audioBuf || !micReady) {
statusLine = "No audio buffer";
drawDynamic();
return;
}
statusLine = "Recording 2 sec...";
drawDynamic();
size_t total = 0;
uint16_t peakAll = 0;
while (total < AUDIO_BYTES) {
size_t chunk = min((size_t)4096, AUDIO_BYTES - total);
size_t got = i2s.readBytes((char *)(audioBuf + total), chunk);
if (!got) break;
total += got;
int16_t *s = (int16_t *)(audioBuf + total - got);
size_t n = got / sizeof(int16_t);
uint16_t peak = 0;
for (size_t i = 0; i < n; i++) {
uint16_t a = (uint16_t)abs((int)s[i]);
if (a > peak) peak = a;
}
if (peak > peakAll) peakAll = peak;
int bar = map((int)peak, 0, 20000, 0, 296);
if (bar < 0) bar = 0;
if (bar > 296) bar = 296;
gfx->fillRect(30, 280, 296, 16, RGB565_BLACK);
uint16_t c = (peak > 12000) ? RGB565_RED : ((peak > 6000) ? RGB565_YELLOW : RGB565_GREEN);
gfx->fillRect(30, 280, bar, 16, c);
}
audioLen = total;
audioLine = (total > 0) ? "Audio: recorded" : "Audio: empty";
if (total > 0) {
statusLine = String("Record done, peak=") + String((int)peakAll);
} else {
statusLine = "Record failed";
}
drawDynamic();
}
void playAudio() {
if (!audioLen) {
statusLine = "No recording. Try BEEP.";
drawDynamic();
return;
}
statusLine = "Playing recording...";
drawDynamic();
size_t wrote = i2s.write(audioBuf, audioLen);
statusLine = (wrote > 0) ? "Play done" : "Play failed";
drawDynamic();
}
void handleTouch() {
if (!touchIrq) return;
touchIrq = false;
int16_t x[1];
int16_t y[1];
uint8_t touched = touch.getPoint(x, y, 1);
if (!touched) return;
int px = x[0];
int py = y[0];
if (py < 76 || py > 140) return;
if (px >= 24 && px <= 156) recordAudio();
else if (px >= 174 && px <= 306) playAudio();
else if (px >= 324 && px <= 456) playBeep();
}
void setup() {
Serial.begin(115200);
Wire.begin(IIC_SDA, IIC_SCL);
pinMode(PA, OUTPUT);
digitalWrite(PA, HIGH);
if (!gfx->begin()) Serial.println("gfx begin failed");
bus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(200);
pinMode(TP_RST, OUTPUT);
digitalWrite(TP_RST, LOW);
delay(30);
digitalWrite(TP_RST, HIGH);
delay(50);
touch.setPins(TP_RST, TP_INT);
touch.begin(Wire, 0x5A, IIC_SDA, IIC_SCL);
touch.setMaxCoordinates(480, 480);
touch.setSwapXY(true);
touch.setMirrorXY(true, false);
attachInterrupt(TP_INT, onTouchInt, FALLING);
if (qmi.begin(Wire, QMI8658_L_SLAVE_ADDRESS, IIC_SDA, IIC_SCL)) {
qmi.configAccelerometer(SensorQMI8658::ACC_RANGE_4G, SensorQMI8658::ACC_ODR_1000Hz, SensorQMI8658::LPF_MODE_0);
qmi.enableAccelerometer();
} else {
tiltLine = "Tilt: IMU not found";
}
i2s.setPins(9, 45, 8, 10, 42);
if (!i2s.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, I2S_STD_SLOT_BOTH)) {
statusLine = "I2S init failed";
} else if (codecInit() != ESP_OK) {
statusLine = "ES8311 init failed";
} else if (micInit() != ESP_OK) {
statusLine = "ES7210 init failed";
} else {
micReady = true;
}
audioBuf = (uint8_t *)ps_malloc(AUDIO_BYTES);
if (!audioBuf) statusLine = "No PSRAM buffer";
drawStaticUi();
drawDynamic();
}
void loop() {
handleTouch();
if (millis() - lastTiltMs > 120) {
lastTiltMs = millis();
updateTilt();
}
delay(5);
}
@@ -0,0 +1,31 @@
# Test Sketches
Набор старых отдельных диагностических скетчей для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Скетчи в этой папке нужны для быстрой проверки конкретных гипотез и не являются основным UI проекта.
Основной скетч сейчас лежит в `main-device/shine_homeserver_main/`.
## Список
- `gfx_text_render_test/` - проверка рендера текста через `Arduino_GFX` и сравнение с `U8g2`
- `gfx_button_layout_test/` - проверка геометрии кнопок, особенно нижних рядов и широких кнопок
- `lvgl_basic_test/` - минимальный тест `LVGL` с заголовком, текстом и кнопками
- `lvgl_interaction_test/` - расширенный тест `LVGL` с 9 кнопками, touch-вводом и статусом нажатия
- `lvgl_touch_debug_test/` - диагностика touch: сырые координаты, точка касания и одна большая кнопка `LVGL`
- `lvgl_official_based_test/` - минимальный наш экран поверх максимально близкой к официальному `05_LVGL_Widgets` инициализации
- `lvgl_subserver_touch_test/` - старый гибридный тест: `LVGL`-экран с инициализацией дисплея и чтением touch из старого `shine_homeserver_ui`; подтверждён на реальном устройстве
- `lvgl_russian_font_test/` - тест кастомного кириллического `LVGL`-шрифта с русскими кнопками, длинными строками и рабочим touch
- `lvgl_nav_minimal_test/` - старое тестовое имя, этот скетч перенесён в `shine_homeserver_main/` и теперь является основным
## Запуск
Использовать через `burn.sh`:
- `./burn.sh gfx-text-test`
- `./burn.sh gfx-layout-test`
- `./burn.sh lvgl-basic-test`
- `./burn.sh lvgl-interaction-test`
- `./burn.sh lvgl-touch-debug-test`
- `./burn.sh lvgl-official-based-test`
- `./burn.sh lvgl-subserver-touch-test`
- `./burn.sh lvgl-russian-font-test`
@@ -0,0 +1,93 @@
#include <Arduino.h>
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#define PIN_LCD_CS 12
#define PIN_LCD_SCLK 38
#define PIN_LCD_D0 4
#define PIN_LCD_D1 5
#define PIN_LCD_D2 6
#define PIN_LCD_D3 7
#define PIN_LCD_RST 2
#define PIN_I2C_SDA 15
#define PIN_I2C_SCL 14
#define DISP_W 480
#define DISP_H 480
#define C_BG 0x0841u
#define C_PANEL 0x1082u
#define C_CARD 0x18C3u
#define C_BORDER 0x39C7u
#define C_TEXT 0xFFFFu
#define C_OK 0x3666u
#define C_WARN 0xECA0u
#define C_BUTTON 0x2145u
#define C_BUTTON2 0x3186u
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
PIN_LCD_CS, PIN_LCD_SCLK, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
static void drawPanel(int x, int y, int w, int h, uint16_t fill, uint16_t border, int radius = 10) {
gfx->fillRoundRect(x, y, w, h, radius, fill);
gfx->drawRoundRect(x, y, w, h, radius, border);
}
static void drawText(int x, int y, const char *text, uint16_t color, uint8_t size = 2) {
gfx->setFont();
gfx->setTextSize(size);
gfx->setTextColor(color);
gfx->setCursor(x, y);
gfx->print(text);
}
static void drawButton(int x, int y, int w, int h, uint16_t fill, const char *label, uint8_t size = 2) {
drawPanel(x, y, w, h, fill, C_BORDER, 12);
gfx->setFont();
gfx->setTextSize(size);
gfx->setTextColor(C_TEXT);
int textW = strlen(label) * 6 * size;
int textX = x + (w - textW) / 2;
if (textX < x + 8) {
textX = x + 8;
}
int textY = y + (h + 8 * size) / 2 - 2;
gfx->setCursor(textX, textY);
gfx->print(label);
}
static void drawScreen() {
gfx->fillScreen(C_BG);
drawPanel(12, 12, 456, 456, C_PANEL, C_BORDER, 16);
drawText(24, 42, "BUTTON LAYOUT TEST", C_TEXT, 2);
drawText(24, 70, "Check bottom and wide buttons", C_WARN, 1);
drawButton(20, 110, 136, 52, C_BUTTON, "STATUS");
drawButton(172, 110, 136, 52, C_BUTTON, "CONNECT");
drawButton(324, 110, 136, 52, C_BUTTON, "ACCOUNT");
drawButton(20, 180, 136, 52, C_BUTTON, "WALLET");
drawButton(172, 180, 136, 52, C_BUTTON, "REQUESTS");
drawButton(324, 180, 136, 52, C_BUTTON, "SETTINGS");
drawButton(20, 270, 212, 48, C_BUTTON2, "BACK");
drawButton(248, 270, 212, 48, C_OK, "REFRESH");
drawButton(20, 338, 440, 40, C_BUTTON2, "FULL WIDTH BUTTON", 2);
drawButton(20, 390, 440, 56, C_OK, "REGISTER DEVICE", 2);
}
void setup() {
Serial.begin(115200);
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
drawScreen();
}
void loop() {
delay(1000);
}
@@ -0,0 +1,104 @@
#include <Arduino.h>
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#include <U8g2lib.h>
#define PIN_LCD_CS 12
#define PIN_LCD_SCLK 38
#define PIN_LCD_D0 4
#define PIN_LCD_D1 5
#define PIN_LCD_D2 6
#define PIN_LCD_D3 7
#define PIN_LCD_RST 2
#define PIN_I2C_SDA 15
#define PIN_I2C_SCL 14
#define DISP_W 480
#define DISP_H 480
#define C_BG 0x0841u
#define C_PANEL 0x1082u
#define C_CARD 0x18C3u
#define C_BORDER 0x39C7u
#define C_TEXT 0xFFFFu
#define C_MUTE 0xBDF7u
#define C_OK 0x3666u
#define C_WARN 0xECA0u
#define C_BUTTON 0x2145u
#define C_BUTTON2 0x3186u
#define FONT_BODY u8g2_font_9x15_t_cyrillic
#define FONT_SMALL u8g2_font_6x13_t_cyrillic
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
PIN_LCD_CS, PIN_LCD_SCLK, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
static void drawPanel(int x, int y, int w, int h, uint16_t fill, uint16_t border, int radius = 10) {
gfx->fillRoundRect(x, y, w, h, radius, fill);
gfx->drawRoundRect(x, y, w, h, radius, border);
}
static void drawDefaultText(int x, int y, const char *text, uint16_t color, uint8_t size = 1) {
gfx->setFont();
gfx->setTextSize(size);
gfx->setTextColor(color);
gfx->setCursor(x, y);
gfx->print(text);
}
static void drawU8Text(int x, int y, const String &text, uint16_t color, const uint8_t *font) {
gfx->setTextSize(1);
gfx->setFont(font);
gfx->setTextColor(color);
gfx->setCursor(x, y);
gfx->print(text);
}
static void drawButton(int x, int y, int w, int h, uint16_t fill, const String &label, const uint8_t *font, bool useDefaultFont = false) {
drawPanel(x, y, w, h, fill, C_BORDER, 12);
if (useDefaultFont) {
drawDefaultText(x + 14, y + 30, label.c_str(), C_TEXT, 2);
return;
}
drawU8Text(x + 14, y + 32, label, C_TEXT, font);
}
static void drawScreen() {
gfx->fillScreen(C_BG);
drawPanel(12, 12, 456, 456, C_PANEL, C_BORDER, 16);
drawDefaultText(28, 42, "TEXT TEST 123", C_TEXT, 2);
drawU8Text(28, 72, "Default ASCII above", C_MUTE, (const uint8_t *)FONT_SMALL);
drawPanel(24, 90, 432, 72, C_CARD, C_BORDER, 12);
drawDefaultText(38, 118, "A: Default font ABC123", C_TEXT, 2);
drawDefaultText(38, 146, "Visible? then base path OK", C_WARN, 1);
drawPanel(24, 174, 432, 84, C_CARD, C_BORDER, 12);
drawU8Text(38, 204, "B: U8g2 ASCII abc123", C_TEXT, (const uint8_t *)FONT_BODY);
drawU8Text(38, 230, "C: Русский текст 123", C_OK, (const uint8_t *)FONT_BODY);
drawPanel(24, 270, 432, 84, C_CARD, C_BORDER, 12);
drawU8Text(38, 298, "D: Мелкий шрифт кнопок", C_TEXT, (const uint8_t *)FONT_SMALL);
drawU8Text(38, 320, "Если это видно, FONT_SMALL жив", C_MUTE, (const uint8_t *)FONT_SMALL);
drawButton(24, 368, 128, 72, C_BUTTON, "BTN 1", nullptr, true);
drawButton(176, 368, 128, 72, C_OK, "abc123", (const uint8_t *)FONT_BODY);
drawButton(328, 368, 128, 72, C_BUTTON2, "Русский", (const uint8_t *)FONT_BODY);
}
void setup() {
Serial.begin(115200);
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
gfx->setUTF8Print(true);
drawScreen();
}
void loop() {
delay(1000);
}
@@ -0,0 +1,125 @@
#include <Arduino.h>
#include <Wire.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include "pin_config.h"
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
static lv_disp_draw_buf_t gDrawBuf;
static lv_color_t *gBuf1 = nullptr;
static lv_color_t *gBuf2 = nullptr;
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
static void lvglRounderCb(lv_disp_drv_t *dispDrv, lv_area_t *area) {
LV_UNUSED(dispDrv);
if (area->x1 % 2 != 0) area->x1--;
if (area->y1 % 2 != 0) area->y1--;
if (area->x2 % 2 == 0) area->x2++;
if (area->y2 % 2 == 0) area->y2++;
}
static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
#if (LV_COLOR_16_SWAP != 0)
gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#else
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#endif
lv_disp_flush_ready(disp);
}
static void lvglTick(void *arg) {
LV_UNUSED(arg);
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
static void createUi() {
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x0B1320), 0);
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);
lv_obj_t *title = lv_label_create(lv_scr_act());
lv_label_set_text(title, "LVGL BASIC TEST");
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(title, &lv_font_montserrat_22, 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 18);
lv_obj_t *subtitle = lv_label_create(lv_scr_act());
lv_label_set_text(subtitle, "If this text is visible, LVGL path works.");
lv_obj_set_width(subtitle, 420);
lv_label_set_long_mode(subtitle, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_color(subtitle, lv_color_hex(0xD5DCE5), 0);
lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_16, 0);
lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 58);
const char *labels[3] = {"Button One", "Second Button", "Bottom Action"};
const lv_color_t colors[3] = {
lv_color_hex(0x2B4C7E),
lv_color_hex(0x2E8B57),
lv_color_hex(0xB45F06),
};
const lv_coord_t ys[3] = {150, 236, 340};
const lv_coord_t hs[3] = {58, 58, 64};
for (int i = 0; i < 3; i++) {
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 360, hs[i]);
lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, ys[i]);
lv_obj_set_style_radius(btn, 14, 0);
lv_obj_set_style_bg_color(btn, colors[i], 0);
lv_obj_set_style_border_width(btn, 2, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0x7F96B0), 0);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, labels[i]);
lv_obj_set_style_text_font(label, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(label);
}
}
void setup() {
Serial.begin(115200);
Wire.begin(IIC_SDA, IIC_SCL);
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
lv_init();
uint32_t screenWidth = gfx->width();
uint32_t screenHeight = gfx->height();
gBuf1 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
gBuf2 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_disp_draw_buf_init(&gDrawBuf, gBuf1, gBuf2, screenWidth * screenHeight / 4);
static lv_disp_drv_t dispDrv;
lv_disp_drv_init(&dispDrv);
dispDrv.hor_res = screenWidth;
dispDrv.ver_res = screenHeight;
dispDrv.flush_cb = lvglFlushCb;
dispDrv.rounder_cb = lvglRounderCb;
dispDrv.draw_buf = &gDrawBuf;
lv_disp_drv_register(&dispDrv);
const esp_timer_create_args_t lvglTickTimerArgs = {
.callback = &lvglTick,
.name = "lvgl_tick"};
esp_timer_handle_t lvglTickTimer = nullptr;
esp_timer_create(&lvglTickTimerArgs, &lvglTickTimer);
esp_timer_start_periodic(lvglTickTimer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
createUi();
Serial.println("LVGL basic test ready");
}
void loop() {
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,214 @@
#include <Arduino.h>
#include <Wire.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include "pin_config.h"
#include "TouchDrvCSTXXX.hpp"
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
static lv_disp_draw_buf_t gDrawBuf;
static lv_color_t *gBuf1 = nullptr;
static lv_color_t *gBuf2 = nullptr;
static lv_obj_t *gStatusLabel = nullptr;
static uint32_t gPressCount = 0;
static TouchDrvCST92xx gTouch;
static int16_t gTouchX[5];
static int16_t gTouchY[5];
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
#if (LV_COLOR_16_SWAP != 0)
gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#else
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#endif
lv_disp_flush_ready(disp);
}
static void lvglTick(void *arg) {
LV_UNUSED(arg);
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
static void lvglTouchRead(lv_indev_drv_t *indevDrv, lv_indev_data_t *data) {
LV_UNUSED(indevDrv);
uint8_t touched = gTouch.getPoint(gTouchX, gTouchY, gTouch.getSupportTouchPoint());
if (touched > 0) {
data->state = LV_INDEV_STATE_PR;
data->point.x = gTouchX[0];
data->point.y = gTouchY[0];
return;
}
data->state = LV_INDEV_STATE_REL;
}
static void buttonEventCb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
return;
}
lv_obj_t *btn = lv_event_get_target(event);
const char *name = (const char *)lv_event_get_user_data(event);
gPressCount++;
lv_obj_set_style_outline_width(btn, 4, 0);
lv_obj_set_style_outline_color(btn, lv_color_hex(0xFFF3B0), 0);
lv_obj_set_style_outline_pad(btn, 2, 0);
String status = "Pressed: ";
status += name;
status += " (#";
status += String(gPressCount);
status += ")";
lv_label_set_text(gStatusLabel, status.c_str());
Serial.println(status);
}
static void createButton(lv_obj_t *parent, const char *labelText, lv_coord_t col, lv_coord_t row, uint32_t colorHex) {
static const lv_coord_t kStartX = 22;
static const lv_coord_t kStartY = 122;
static const lv_coord_t kButtonW = 134;
static const lv_coord_t kButtonH = 64;
static const lv_coord_t kGapX = 18;
static const lv_coord_t kGapY = 16;
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_set_size(btn, kButtonW, kButtonH);
lv_obj_set_pos(btn, kStartX + col * (kButtonW + kGapX), kStartY + row * (kButtonH + kGapY));
lv_obj_set_style_radius(btn, 16, 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(colorHex), 0);
lv_obj_set_style_border_width(btn, 2, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0x7F96B0), 0);
lv_obj_set_style_shadow_width(btn, 0, 0);
lv_obj_add_event_cb(btn, buttonEventCb, LV_EVENT_CLICKED, (void *)labelText);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, labelText);
lv_obj_set_style_text_font(label, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(label);
}
static void createUi() {
lv_obj_t *screen = lv_scr_act();
lv_obj_set_style_bg_color(screen, lv_color_hex(0x0B1320), 0);
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
lv_obj_t *title = lv_label_create(screen);
lv_label_set_text(title, "LVGL INTERACTION TEST");
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(title, &lv_font_montserrat_22, 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 14);
lv_obj_t *subtitle = lv_label_create(screen);
lv_label_set_text(subtitle, "Tap buttons. Bottom label must change on every press.");
lv_obj_set_width(subtitle, 420);
lv_label_set_long_mode(subtitle, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_color(subtitle, lv_color_hex(0xD5DCE5), 0);
lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_14, 0);
lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 48);
createButton(screen, "Status", 0, 0, 0x355C7D);
createButton(screen, "Connect", 1, 0, 0x2A9D8F);
createButton(screen, "Wallet", 2, 0, 0x457B9D);
createButton(screen, "Requests", 0, 1, 0xE76F51);
createButton(screen, "Settings", 1, 1, 0x8D5A97);
createButton(screen, "Register", 2, 1, 0x6A994E);
createButton(screen, "Approve", 0, 2, 0xBC6C25);
createButton(screen, "Reject", 1, 2, 0xB56576);
createButton(screen, "Back", 2, 2, 0x6C757D);
lv_obj_t *statusPanel = lv_obj_create(screen);
lv_obj_set_size(statusPanel, 436, 56);
lv_obj_align(statusPanel, LV_ALIGN_BOTTOM_MID, 0, -16);
lv_obj_set_style_radius(statusPanel, 14, 0);
lv_obj_set_style_bg_color(statusPanel, lv_color_hex(0x162033), 0);
lv_obj_set_style_border_width(statusPanel, 2, 0);
lv_obj_set_style_border_color(statusPanel, lv_color_hex(0x415A77), 0);
lv_obj_set_style_pad_all(statusPanel, 10, 0);
gStatusLabel = lv_label_create(statusPanel);
lv_label_set_text(gStatusLabel, "Pressed: none");
lv_obj_set_style_text_font(gStatusLabel, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(gStatusLabel, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(gStatusLabel);
}
void setup() {
Serial.begin(115200);
Wire.begin(IIC_SDA, IIC_SCL);
pinMode(TP_RST, OUTPUT);
pinMode(TP_INT, INPUT);
digitalWrite(TP_RST, LOW);
delay(30);
digitalWrite(TP_RST, HIGH);
delay(50);
delay(1000);
gTouch.setPins(TP_RST, TP_INT);
bool touchOk = gTouch.begin(Wire, 0x5A, IIC_SDA, IIC_SCL);
if (!touchOk) {
Serial.println("Touch init failed");
} else {
Serial.print("Touch model: ");
Serial.println(gTouch.getModelName());
gTouch.sleep();
gTouch.reset();
gTouch.setMaxCoordinates(480, 480);
gTouch.setSwapXY(true);
gTouch.setMirrorXY(true, false);
}
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
gfx->fillScreen(0x0000);
lv_init();
uint32_t screenWidth = gfx->width();
uint32_t screenHeight = gfx->height();
gBuf1 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
gBuf2 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_disp_draw_buf_init(&gDrawBuf, gBuf1, gBuf2, screenWidth * screenHeight / 4);
static lv_disp_drv_t dispDrv;
lv_disp_drv_init(&dispDrv);
dispDrv.hor_res = screenWidth;
dispDrv.ver_res = screenHeight;
dispDrv.flush_cb = lvglFlushCb;
dispDrv.draw_buf = &gDrawBuf;
lv_disp_drv_register(&dispDrv);
static lv_indev_drv_t indevDrv;
lv_indev_drv_init(&indevDrv);
indevDrv.type = LV_INDEV_TYPE_POINTER;
indevDrv.read_cb = lvglTouchRead;
lv_indev_drv_register(&indevDrv);
const esp_timer_create_args_t lvglTickTimerArgs = {
.callback = &lvglTick,
.name = "lvgl_tick"};
esp_timer_handle_t lvglTickTimer = nullptr;
esp_timer_create(&lvglTickTimerArgs, &lvglTickTimer);
esp_timer_start_periodic(lvglTickTimer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
createUi();
Serial.println("LVGL interaction test ready");
}
void loop() {
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,252 @@
#include <lvgl.h>
#include "Arduino_GFX_Library.h"
#include "pin_config.h"
#include "TouchDrvCSTXXX.hpp"
#include "lv_conf.h"
#include "HWCDC.h"
#include <Wire.h>
HWCDC USBSerial;
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
static lv_disp_draw_buf_t draw_buf;
static lv_color_t *buf1 = nullptr;
static lv_color_t *buf2 = nullptr;
static lv_obj_t *status_label = nullptr;
static uint32_t click_count = 0;
Arduino_DataBus *bus = new Arduino_ESP32QSPI(
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
bus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
TouchDrvCST92xx touch;
int16_t touch_x[5], touch_y[5];
bool isPressed = false;
uint32_t screenWidth = 0;
uint32_t screenHeight = 0;
#if LV_USE_LOG != 0
void my_print(const char *buf) {
USBSerial.printf(buf);
USBSerial.flush();
}
#endif
static void example_lvgl_rounder_cb(struct _lv_disp_drv_t *disp_drv, lv_area_t *area) {
LV_UNUSED(disp_drv);
if (area->x1 % 2 != 0) area->x1--;
if (area->y1 % 2 != 0) area->y1--;
if (area->x2 % 2 == 0) area->x2++;
if (area->y2 % 2 == 0) area->y2++;
}
static void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
#if (LV_COLOR_16_SWAP != 0)
gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#else
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#endif
lv_disp_flush_ready(disp);
}
static void example_increase_lvgl_tick(void *arg) {
LV_UNUSED(arg);
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
static void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
LV_UNUSED(indev_driver);
if (isPressed) {
uint8_t touched = touch.getPoint(touch_x, touch_y, touch.getSupportTouchPoint());
if (touched) {
isPressed = false;
data->state = LV_INDEV_STATE_PR;
data->point.x = touch_x[0];
data->point.y = touch_y[0];
USBSerial.print("Touch x=");
USBSerial.print(touch_x[0]);
USBSerial.print(" y=");
USBSerial.println(touch_y[0]);
} else {
data->state = LV_INDEV_STATE_REL;
}
} else {
data->state = LV_INDEV_STATE_REL;
}
}
static void button_event_cb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
return;
}
const char *name = (const char *)lv_event_get_user_data(event);
click_count++;
String text = "Pressed: ";
text += name;
text += " (#";
text += String(click_count);
text += ")";
lv_label_set_text(status_label, text.c_str());
USBSerial.println(text);
}
static lv_obj_t *make_button(lv_obj_t *parent, const char *label_text, lv_coord_t x, lv_coord_t y, lv_coord_t w, lv_coord_t h, lv_color_t bg) {
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_set_size(btn, w, h);
lv_obj_set_pos(btn, x, y);
lv_obj_set_style_radius(btn, 16, 0);
lv_obj_set_style_bg_color(btn, bg, 0);
lv_obj_set_style_border_width(btn, 2, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0x8AA4BF), 0);
lv_obj_set_style_shadow_width(btn, 0, 0);
lv_obj_add_event_cb(btn, button_event_cb, LV_EVENT_CLICKED, (void *)label_text);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, label_text);
lv_obj_set_style_text_font(label, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(label);
return btn;
}
static void create_ui() {
lv_obj_t *screen = lv_scr_act();
lv_obj_set_style_bg_color(screen, lv_color_hex(0x0B1320), 0);
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
lv_obj_t *title = lv_label_create(screen);
lv_label_set_text(title, "LVGL OFFICIAL BASED TEST");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 14);
lv_obj_t *hint = lv_label_create(screen);
lv_label_set_text(hint, "Based on official LVGL_Widgets init. Tap buttons and watch the status.");
lv_obj_set_width(hint, 430);
lv_label_set_long_mode(hint, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(0xD4DDE7), 0);
lv_obj_align(hint, LV_ALIGN_TOP_MID, 0, 44);
make_button(screen, "Status", 22, 120, 136, 60, lv_color_hex(0x355C7D));
make_button(screen, "Connect", 174, 120, 136, 60, lv_color_hex(0x2A9D8F));
make_button(screen, "Wallet", 326, 120, 132, 60, lv_color_hex(0x457B9D));
make_button(screen, "Requests", 22, 196, 136, 60, lv_color_hex(0xE76F51));
make_button(screen, "Settings",174, 196, 136, 60, lv_color_hex(0x8D5A97));
make_button(screen, "Register",326, 196, 132, 60, lv_color_hex(0x6A994E));
make_button(screen, "Approve", 22, 272, 136, 60, lv_color_hex(0xBC6C25));
make_button(screen, "Reject", 174, 272, 136, 60, lv_color_hex(0xB56576));
make_button(screen, "Back", 326, 272, 132, 60, lv_color_hex(0x6C757D));
lv_obj_t *bottom_btn = make_button(screen, "Bottom Action", 22, 350, 436, 64, lv_color_hex(0x1D6F42));
lv_obj_set_style_text_font(lv_obj_get_child(bottom_btn, 0), &lv_font_montserrat_20, 0);
lv_obj_t *status_panel = lv_obj_create(screen);
lv_obj_set_size(status_panel, 436, 42);
lv_obj_set_pos(status_panel, 22, 426);
lv_obj_set_style_radius(status_panel, 12, 0);
lv_obj_set_style_bg_color(status_panel, lv_color_hex(0x17263A), 0);
lv_obj_set_style_border_width(status_panel, 2, 0);
lv_obj_set_style_border_color(status_panel, lv_color_hex(0x496582), 0);
lv_obj_set_style_pad_all(status_panel, 6, 0);
status_label = lv_label_create(status_panel);
lv_label_set_text(status_label, "Pressed: none");
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(status_label, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(status_label);
}
void setup() {
USBSerial.begin(115200);
Wire.begin(IIC_SDA, IIC_SCL);
digitalWrite(TP_RST, LOW);
delay(30);
digitalWrite(TP_RST, HIGH);
delay(50);
delay(1000);
Wire.begin(IIC_SDA, IIC_SCL);
touch.setPins(TP_RST, TP_INT);
bool result = touch.begin(Wire, 0x5A, IIC_SDA, IIC_SCL);
if (result == false) {
USBSerial.println("touch is not online...");
while (1) delay(1000);
}
USBSerial.print("Touch model: ");
USBSerial.println(touch.getModelName());
touch.sleep();
touch.reset();
touch.setMaxCoordinates(480, 480);
touch.setSwapXY(true);
touch.setMirrorXY(true, false);
attachInterrupt(
TP_INT, []() {
isPressed = true;
},
FALLING);
gfx->begin();
gfx->setBrightness(200);
bus->writeC8D8(0x36, 0xA0);
screenWidth = gfx->width();
screenHeight = gfx->height();
lv_init();
buf1 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
buf2 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
#if LV_USE_LOG != 0
lv_log_register_print_cb(my_print);
#endif
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, screenWidth * screenHeight / 4);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.rounder_cb = example_lvgl_rounder_cb;
disp_drv.draw_buf = &draw_buf;
disp_drv.sw_rotate = 1;
lv_disp_drv_register(&disp_drv);
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
create_ui();
USBSerial.println("LVGL official based test ready");
}
void loop() {
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,4 @@
#pragma once
#include <lvgl.h>
LV_FONT_DECLARE(lv_font_ru_18);
LV_FONT_DECLARE(lv_font_ru_24);
@@ -0,0 +1,264 @@
#include <Arduino.h>
#include <Wire.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <TouchDrvCSTXXX.hpp>
#include "lv_font_ru.h"
#define PIN_LCD_CS 12
#define PIN_LCD_SCLK 38
#define PIN_LCD_D0 4
#define PIN_LCD_D1 5
#define PIN_LCD_D2 6
#define PIN_LCD_D3 7
#define PIN_LCD_RST 2
#define PIN_I2C_SDA 15
#define PIN_I2C_SCL 14
#define PIN_TP_INT 11
#define DISP_W 480
#define DISP_H 480
#define LVGL_TICK_MS 2
#define TEST_VERSION "v1-ru-font"
static lv_disp_draw_buf_t gDrawBuf;
static lv_color_t *gBuf1 = nullptr;
static lv_color_t *gBuf2 = nullptr;
static lv_obj_t *gStatusLabel = nullptr;
static lv_obj_t *gTouchLabel = nullptr;
static uint32_t gClickCount = 0;
static bool gTouchActive = false;
static int16_t gLastX = -1;
static int16_t gLastY = -1;
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
PIN_LCD_CS, PIN_LCD_SCLK, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
TouchDrvCST92xx gTouch;
static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
#if (LV_COLOR_16_SWAP != 0)
gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#else
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#endif
lv_disp_flush_ready(disp);
}
static void lvglTick(void *arg) {
LV_UNUSED(arg);
lv_tick_inc(LVGL_TICK_MS);
}
static void updateTouchLabel() {
if (!gTouchLabel) {
return;
}
char text[96];
if (gTouchActive) {
snprintf(text, sizeof(text), "Касание: x=%d y=%d", gLastX, gLastY);
} else {
snprintf(text, sizeof(text), "Касание: нет x=%d y=%d", gLastX, gLastY);
}
lv_label_set_text(gTouchLabel, text);
}
static void lvglTouchRead(lv_indev_drv_t *indevDrv, lv_indev_data_t *data) {
LV_UNUSED(indevDrv);
int16_t x = 0;
int16_t y = 0;
bool touching = gTouch.getPoint(&x, &y, 1) > 0;
if (touching) {
gTouchActive = true;
gLastX = x;
gLastY = y;
data->state = LV_INDEV_STATE_PR;
data->point.x = x;
data->point.y = y;
} else {
gTouchActive = false;
data->state = LV_INDEV_STATE_REL;
data->point.x = gLastX >= 0 ? gLastX : 0;
data->point.y = gLastY >= 0 ? gLastY : 0;
}
updateTouchLabel();
}
static void buttonEventCb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
return;
}
const char *name = (const char *)lv_event_get_user_data(event);
gClickCount++;
char text[128];
snprintf(text, sizeof(text), "Нажато: %s (%lu)", name, (unsigned long)gClickCount);
lv_label_set_text(gStatusLabel, text);
Serial.println(text);
}
static lv_obj_t *makeButton(lv_obj_t *parent,
const char *labelText,
lv_coord_t x,
lv_coord_t y,
lv_coord_t w,
lv_coord_t h,
uint32_t bgColor) {
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_set_size(btn, w, h);
lv_obj_set_pos(btn, x, y);
lv_obj_set_style_radius(btn, 16, 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(bgColor), 0);
lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(btn, 2, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0x6F8BA4), 0);
lv_obj_set_style_shadow_width(btn, 0, 0);
lv_obj_set_style_pad_all(btn, 4, 0);
lv_obj_add_event_cb(btn, buttonEventCb, LV_EVENT_CLICKED, (void *)labelText);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, labelText);
lv_obj_set_width(label, w - 12);
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(label, &lv_font_ru_18, 0);
lv_obj_center(label);
return btn;
}
static void createUi() {
lv_obj_t *screen = lv_scr_act();
lv_obj_set_style_bg_color(screen, lv_color_hex(0x08131E), 0);
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
lv_obj_set_style_pad_all(screen, 0, 0);
lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
lv_obj_t *title = lv_label_create(screen);
lv_label_set_text(title, "Русский тест LVGL");
lv_obj_set_style_text_font(title, &lv_font_ru_24, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
lv_obj_t *version = lv_label_create(screen);
lv_label_set_text(version, "Версия: " TEST_VERSION);
lv_obj_set_style_text_font(version, &lv_font_ru_18, 0);
lv_obj_set_style_text_color(version, lv_color_hex(0xD8E0EA), 0);
lv_obj_align(version, LV_ALIGN_TOP_MID, 0, 42);
lv_obj_t *hint = lv_label_create(screen);
lv_label_set_text(hint, "Проверка кириллицы, длинных слов, кнопок и нажатий.");
lv_obj_set_width(hint, 436);
lv_label_set_long_mode(hint, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_font(hint, &lv_font_ru_18, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(0xD8E0EA), 0);
lv_obj_align(hint, LV_ALIGN_TOP_MID, 0, 72);
makeButton(screen, "Статус", 22, 126, 136, 62, 0x355C7D);
makeButton(screen, "Подключение", 172, 126, 136, 62, 0x2A9D8F);
makeButton(screen, "Кошелёк", 322, 126, 136, 62, 0x457B9D);
makeButton(screen, "Запросы", 22, 204, 136, 62, 0xE76F51);
makeButton(screen, "Настройки", 172, 204, 136, 62, 0x8D5A97);
makeButton(screen, "Регистрация", 322, 204, 136, 62, 0x6A994E);
makeButton(screen, "Разрешить", 22, 282, 136, 62, 0xBC6C25);
makeButton(screen, "Отклонить", 172, 282, 136, 62, 0xB56576);
makeButton(screen, "Назад", 322, 282, 136, 62, 0x6C757D);
makeButton(screen, "Проверка переноса русского текста", 22, 360, 436, 64, 0x1D6F42);
lv_obj_t *statusPanel = lv_obj_create(screen);
lv_obj_set_size(statusPanel, 436, 24);
lv_obj_set_pos(statusPanel, 22, 428);
lv_obj_set_style_radius(statusPanel, 10, 0);
lv_obj_set_style_bg_color(statusPanel, lv_color_hex(0x162435), 0);
lv_obj_set_style_bg_opa(statusPanel, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(statusPanel, 1, 0);
lv_obj_set_style_border_color(statusPanel, lv_color_hex(0x4D6986), 0);
lv_obj_set_style_pad_all(statusPanel, 2, 0);
gStatusLabel = lv_label_create(statusPanel);
lv_label_set_text(gStatusLabel, "Нажато: ничего");
lv_obj_set_style_text_font(gStatusLabel, &lv_font_ru_18, 0);
lv_obj_set_style_text_color(gStatusLabel, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(gStatusLabel);
lv_obj_t *touchPanel = lv_obj_create(screen);
lv_obj_set_size(touchPanel, 436, 24);
lv_obj_set_pos(touchPanel, 22, 454);
lv_obj_set_style_radius(touchPanel, 10, 0);
lv_obj_set_style_bg_color(touchPanel, lv_color_hex(0x162435), 0);
lv_obj_set_style_bg_opa(touchPanel, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(touchPanel, 1, 0);
lv_obj_set_style_border_color(touchPanel, lv_color_hex(0x4D6986), 0);
lv_obj_set_style_pad_all(touchPanel, 2, 0);
gTouchLabel = lv_label_create(touchPanel);
lv_label_set_text(gTouchLabel, "Касание: нет x=-1 y=-1");
lv_obj_set_style_text_font(gTouchLabel, &lv_font_ru_18, 0);
lv_obj_set_style_text_color(gTouchLabel, lv_color_hex(0xD8E0EA), 0);
lv_obj_center(gTouchLabel);
}
void setup() {
Serial.begin(115200);
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
gfx->fillScreen(0x0000);
gTouch.setPins(PIN_TP_INT, -1);
gTouch.begin(Wire, CST92XX_SLAVE_ADDRESS, PIN_I2C_SDA, PIN_I2C_SCL);
gTouch.setMaxCoordinates(DISP_W, DISP_H);
gTouch.setSwapXY(true);
gTouch.setMirrorXY(true, false);
lv_init();
uint32_t screenWidth = gfx->width();
uint32_t screenHeight = gfx->height();
uint32_t bufPixels = screenWidth * 40;
gBuf1 = (lv_color_t *)heap_caps_malloc(bufPixels * sizeof(lv_color_t), MALLOC_CAP_DMA);
gBuf2 = (lv_color_t *)heap_caps_malloc(bufPixels * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_disp_draw_buf_init(&gDrawBuf, gBuf1, gBuf2, bufPixels);
static lv_disp_drv_t dispDrv;
lv_disp_drv_init(&dispDrv);
dispDrv.hor_res = screenWidth;
dispDrv.ver_res = screenHeight;
dispDrv.flush_cb = lvglFlushCb;
dispDrv.draw_buf = &gDrawBuf;
lv_disp_drv_register(&dispDrv);
static lv_indev_drv_t indevDrv;
lv_indev_drv_init(&indevDrv);
indevDrv.type = LV_INDEV_TYPE_POINTER;
indevDrv.read_cb = lvglTouchRead;
lv_indev_drv_register(&indevDrv);
const esp_timer_create_args_t tickArgs = {
.callback = &lvglTick,
.name = "lvgl_tick"};
esp_timer_handle_t tickTimer = nullptr;
esp_timer_create(&tickArgs, &tickTimer);
esp_timer_start_periodic(tickTimer, LVGL_TICK_MS * 1000);
createUi();
Serial.println("Русский LVGL font test ready: " TEST_VERSION);
}
void loop() {
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,256 @@
#include <Arduino.h>
#include <Wire.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <TouchDrvCSTXXX.hpp>
// Подтверждено на устройстве: LVGL-рендер работает вместе с touch-путём из shine_homeserver_ui.
#define PIN_LCD_CS 12
#define PIN_LCD_SCLK 38
#define PIN_LCD_D0 4
#define PIN_LCD_D1 5
#define PIN_LCD_D2 6
#define PIN_LCD_D3 7
#define PIN_LCD_RST 2
#define PIN_I2C_SDA 15
#define PIN_I2C_SCL 14
#define PIN_TP_INT 11
#define DISP_W 480
#define DISP_H 480
#define LVGL_TICK_MS 2
#define TEST_VERSION "v1-subtouch"
static lv_disp_draw_buf_t gDrawBuf;
static lv_color_t *gBuf1 = nullptr;
static lv_color_t *gBuf2 = nullptr;
static lv_obj_t *gStatusLabel = nullptr;
static lv_obj_t *gTouchLabel = nullptr;
static lv_obj_t *gVersionLabel = nullptr;
static uint32_t gClickCount = 0;
static bool gTouchActive = false;
static int16_t gLastX = -1;
static int16_t gLastY = -1;
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
PIN_LCD_CS, PIN_LCD_SCLK, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
TouchDrvCST92xx gTouch;
static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
#if (LV_COLOR_16_SWAP != 0)
gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#else
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#endif
lv_disp_flush_ready(disp);
}
static void lvglTick(void *arg) {
LV_UNUSED(arg);
lv_tick_inc(LVGL_TICK_MS);
}
static void updateTouchLabel() {
if (!gTouchLabel) {
return;
}
char text[96];
if (gTouchActive) {
snprintf(text, sizeof(text), "Touch: pressed x=%d y=%d", gLastX, gLastY);
} else {
snprintf(text, sizeof(text), "Touch: released x=%d y=%d", gLastX, gLastY);
}
lv_label_set_text(gTouchLabel, text);
}
static void lvglTouchRead(lv_indev_drv_t *indevDrv, lv_indev_data_t *data) {
LV_UNUSED(indevDrv);
int16_t x = 0;
int16_t y = 0;
bool touching = gTouch.getPoint(&x, &y, 1) > 0;
if (touching) {
gTouchActive = true;
gLastX = x;
gLastY = y;
data->state = LV_INDEV_STATE_PR;
data->point.x = x;
data->point.y = y;
} else {
gTouchActive = false;
data->state = LV_INDEV_STATE_REL;
data->point.x = gLastX >= 0 ? gLastX : 0;
data->point.y = gLastY >= 0 ? gLastY : 0;
}
updateTouchLabel();
}
static void buttonEventCb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
return;
}
const char *name = (const char *)lv_event_get_user_data(event);
gClickCount++;
char text[96];
snprintf(text, sizeof(text), "Pressed: %s (#%lu)", name, (unsigned long)gClickCount);
lv_label_set_text(gStatusLabel, text);
Serial.println(text);
}
static lv_obj_t *makeButton(lv_obj_t *parent,
const char *labelText,
lv_coord_t x,
lv_coord_t y,
lv_coord_t w,
lv_coord_t h,
uint32_t bgColor) {
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_set_size(btn, w, h);
lv_obj_set_pos(btn, x, y);
lv_obj_set_style_radius(btn, 16, 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(bgColor), 0);
lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(btn, 2, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0x6F8BA4), 0);
lv_obj_set_style_shadow_width(btn, 0, 0);
lv_obj_add_event_cb(btn, buttonEventCb, LV_EVENT_CLICKED, (void *)labelText);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, labelText);
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(label, &lv_font_montserrat_18, 0);
lv_obj_center(label);
return btn;
}
static void createUi() {
lv_obj_t *screen = lv_scr_act();
lv_obj_set_style_bg_color(screen, lv_color_hex(0x07111C), 0);
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
lv_obj_set_style_pad_all(screen, 0, 0);
lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
gVersionLabel = lv_label_create(screen);
lv_label_set_text(gVersionLabel, "LVGL + subserver touch " TEST_VERSION);
lv_obj_set_style_text_font(gVersionLabel, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(gVersionLabel, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(gVersionLabel, LV_ALIGN_TOP_MID, 0, 12);
lv_obj_t *subtitle = lv_label_create(screen);
lv_label_set_text(subtitle, "Touch path comes from shine_homeserver_ui. Tap buttons and watch status.");
lv_obj_set_width(subtitle, 436);
lv_label_set_long_mode(subtitle, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(subtitle, lv_color_hex(0xD6DEE7), 0);
lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 42);
makeButton(screen, "Status", 22, 110, 136, 62, 0x355C7D);
makeButton(screen, "Connect", 172, 110, 136, 62, 0x2A9D8F);
makeButton(screen, "Wallet", 322, 110, 136, 62, 0x457B9D);
makeButton(screen, "Requests", 22, 188, 136, 62, 0xE76F51);
makeButton(screen, "Settings", 172, 188, 136, 62, 0x8D5A97);
makeButton(screen, "Register", 322, 188, 136, 62, 0x6A994E);
makeButton(screen, "Approve", 22, 266, 136, 62, 0xBC6C25);
makeButton(screen, "Reject", 172, 266, 136, 62, 0xB56576);
makeButton(screen, "Back", 322, 266, 136, 62, 0x6C757D);
makeButton(screen, "Bottom Action", 22, 346, 436, 68, 0x1D6F42);
lv_obj_t *statusPanel = lv_obj_create(screen);
lv_obj_set_size(statusPanel, 436, 26);
lv_obj_set_pos(statusPanel, 22, 420);
lv_obj_set_style_radius(statusPanel, 10, 0);
lv_obj_set_style_bg_color(statusPanel, lv_color_hex(0x162435), 0);
lv_obj_set_style_bg_opa(statusPanel, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(statusPanel, 1, 0);
lv_obj_set_style_border_color(statusPanel, lv_color_hex(0x4D6986), 0);
lv_obj_set_style_pad_all(statusPanel, 2, 0);
gStatusLabel = lv_label_create(statusPanel);
lv_label_set_text(gStatusLabel, "Pressed: none");
lv_obj_set_style_text_font(gStatusLabel, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(gStatusLabel, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(gStatusLabel);
lv_obj_t *touchPanel = lv_obj_create(screen);
lv_obj_set_size(touchPanel, 436, 26);
lv_obj_set_pos(touchPanel, 22, 450);
lv_obj_set_style_radius(touchPanel, 10, 0);
lv_obj_set_style_bg_color(touchPanel, lv_color_hex(0x162435), 0);
lv_obj_set_style_bg_opa(touchPanel, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(touchPanel, 1, 0);
lv_obj_set_style_border_color(touchPanel, lv_color_hex(0x4D6986), 0);
lv_obj_set_style_pad_all(touchPanel, 2, 0);
gTouchLabel = lv_label_create(touchPanel);
lv_label_set_text(gTouchLabel, "Touch: released x=-1 y=-1");
lv_obj_set_style_text_font(gTouchLabel, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(gTouchLabel, lv_color_hex(0xD6DEE7), 0);
lv_obj_center(gTouchLabel);
}
void setup() {
Serial.begin(115200);
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
gfx->fillScreen(0x0000);
gTouch.setPins(PIN_TP_INT, -1);
gTouch.begin(Wire, CST92XX_SLAVE_ADDRESS, PIN_I2C_SDA, PIN_I2C_SCL);
gTouch.setMaxCoordinates(DISP_W, DISP_H);
gTouch.setSwapXY(true);
gTouch.setMirrorXY(true, false);
lv_init();
uint32_t screenWidth = gfx->width();
uint32_t screenHeight = gfx->height();
uint32_t bufPixels = screenWidth * 40;
gBuf1 = (lv_color_t *)heap_caps_malloc(bufPixels * sizeof(lv_color_t), MALLOC_CAP_DMA);
gBuf2 = (lv_color_t *)heap_caps_malloc(bufPixels * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_disp_draw_buf_init(&gDrawBuf, gBuf1, gBuf2, bufPixels);
static lv_disp_drv_t dispDrv;
lv_disp_drv_init(&dispDrv);
dispDrv.hor_res = screenWidth;
dispDrv.ver_res = screenHeight;
dispDrv.flush_cb = lvglFlushCb;
dispDrv.draw_buf = &gDrawBuf;
lv_disp_drv_register(&dispDrv);
static lv_indev_drv_t indevDrv;
lv_indev_drv_init(&indevDrv);
indevDrv.type = LV_INDEV_TYPE_POINTER;
indevDrv.read_cb = lvglTouchRead;
lv_indev_drv_register(&indevDrv);
const esp_timer_create_args_t tickArgs = {
.callback = &lvglTick,
.name = "lvgl_tick"};
esp_timer_handle_t tickTimer = nullptr;
esp_timer_create(&tickArgs, &tickTimer);
esp_timer_start_periodic(tickTimer, LVGL_TICK_MS * 1000);
createUi();
Serial.println("LVGL + subserver touch test ready: " TEST_VERSION);
}
void loop() {
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,218 @@
#include <Arduino.h>
#include <Wire.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include "pin_config.h"
#include "TouchDrvCSTXXX.hpp"
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
static lv_disp_draw_buf_t gDrawBuf;
static lv_color_t *gBuf1 = nullptr;
static lv_color_t *gBuf2 = nullptr;
static lv_obj_t *gRawLabel = nullptr;
static lv_obj_t *gEventLabel = nullptr;
static lv_obj_t *gMarker = nullptr;
static TouchDrvCST92xx gTouch;
static int16_t gTouchX[5];
static int16_t gTouchY[5];
static bool gTouchPressed = false;
static uint32_t gRawTouchCount = 0;
static uint32_t gLvglButtonCount = 0;
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
#if (LV_COLOR_16_SWAP != 0)
gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#else
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#endif
lv_disp_flush_ready(disp);
}
static void lvglTick(void *arg) {
LV_UNUSED(arg);
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
static void lvglTouchRead(lv_indev_drv_t *indevDrv, lv_indev_data_t *data) {
LV_UNUSED(indevDrv);
uint8_t touched = gTouch.getPoint(gTouchX, gTouchY, gTouch.getSupportTouchPoint());
if (touched > 0) {
data->state = LV_INDEV_STATE_PR;
data->point.x = gTouchX[0];
data->point.y = gTouchY[0];
return;
}
data->state = LV_INDEV_STATE_REL;
}
static void bigButtonEventCb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
return;
}
gLvglButtonCount++;
String status = "LVGL button clicked: ";
status += String(gLvglButtonCount);
lv_label_set_text(gEventLabel, status.c_str());
Serial.println(status);
}
static void updateRawTouchState() {
uint8_t touched = gTouch.getPoint(gTouchX, gTouchY, gTouch.getSupportTouchPoint());
if (touched > 0) {
gTouchPressed = true;
gRawTouchCount++;
String line1 = "RAW pressed";
String line2 = "x=" + String(gTouchX[0]) + " y=" + String(gTouchY[0]) + " n=" + String(gRawTouchCount);
String text = line1 + "\n" + line2;
lv_label_set_text(gRawLabel, text.c_str());
int markerX = gTouchX[0] - 8;
int markerY = gTouchY[0] - 8;
if (markerX < 0) markerX = 0;
if (markerY < 0) markerY = 0;
lv_obj_set_pos(gMarker, markerX, markerY);
lv_obj_clear_flag(gMarker, LV_OBJ_FLAG_HIDDEN);
return;
}
if (gTouchPressed) {
gTouchPressed = false;
lv_label_set_text(gRawLabel, "RAW released\nTouch ended");
lv_obj_add_flag(gMarker, LV_OBJ_FLAG_HIDDEN);
}
}
static void createUi() {
lv_obj_t *screen = lv_scr_act();
lv_obj_set_style_bg_color(screen, lv_color_hex(0x000000), 0);
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
lv_obj_t *title = lv_label_create(screen);
lv_label_set_text(title, "LVGL TOUCH DEBUG");
lv_obj_set_style_text_font(title, &lv_font_montserrat_22, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 12);
gRawLabel = lv_label_create(screen);
lv_label_set_text(gRawLabel, "RAW released\nTouch not seen yet");
lv_obj_set_style_text_font(gRawLabel, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(gRawLabel, lv_color_hex(0x9EE493), 0);
lv_obj_align(gRawLabel, LV_ALIGN_TOP_LEFT, 16, 56);
gEventLabel = lv_label_create(screen);
lv_label_set_text(gEventLabel, "LVGL button clicked: 0");
lv_obj_set_style_text_font(gEventLabel, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(gEventLabel, lv_color_hex(0xFFD166), 0);
lv_obj_align(gEventLabel, LV_ALIGN_TOP_LEFT, 16, 118);
lv_obj_t *btn = lv_btn_create(screen);
lv_obj_set_size(btn, 360, 110);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 36);
lv_obj_set_style_radius(btn, 18, 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x2A9D8F), 0);
lv_obj_set_style_border_width(btn, 3, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0xBDE0FE), 0);
lv_obj_add_event_cb(btn, bigButtonEventCb, LV_EVENT_CLICKED, nullptr);
lv_obj_t *btnLabel = lv_label_create(btn);
lv_label_set_text(btnLabel, "Tap Here");
lv_obj_set_style_text_font(btnLabel, &lv_font_montserrat_24, 0);
lv_obj_set_style_text_color(btnLabel, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(btnLabel);
lv_obj_t *hint = lv_label_create(screen);
lv_label_set_text(hint, "Need both: RAW coords change and LVGL button click.");
lv_obj_set_width(hint, 430);
lv_label_set_long_mode(hint, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_font(hint, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(0xD9D9D9), 0);
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -34);
gMarker = lv_obj_create(screen);
lv_obj_set_size(gMarker, 16, 16);
lv_obj_set_style_radius(gMarker, LV_RADIUS_CIRCLE, 0);
lv_obj_set_style_bg_color(gMarker, lv_color_hex(0xFF4D6D), 0);
lv_obj_set_style_border_width(gMarker, 2, 0);
lv_obj_set_style_border_color(gMarker, lv_color_hex(0xFFFFFF), 0);
lv_obj_add_flag(gMarker, LV_OBJ_FLAG_HIDDEN);
}
void setup() {
Serial.begin(115200);
Wire.begin(IIC_SDA, IIC_SCL);
pinMode(TP_RST, OUTPUT);
pinMode(TP_INT, INPUT);
digitalWrite(TP_RST, LOW);
delay(30);
digitalWrite(TP_RST, HIGH);
delay(50);
delay(1000);
gTouch.setPins(TP_RST, TP_INT);
bool touchOk = gTouch.begin(Wire, 0x5A, IIC_SDA, IIC_SCL);
if (!touchOk) {
Serial.println("Touch init failed");
} else {
Serial.print("Touch model: ");
Serial.println(gTouch.getModelName());
gTouch.sleep();
gTouch.reset();
gTouch.setMaxCoordinates(480, 480);
gTouch.setSwapXY(true);
gTouch.setMirrorXY(true, false);
}
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
gfx->fillScreen(0x0000);
lv_init();
uint32_t screenWidth = gfx->width();
uint32_t screenHeight = gfx->height();
gBuf1 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
gBuf2 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
lv_disp_draw_buf_init(&gDrawBuf, gBuf1, gBuf2, screenWidth * screenHeight / 4);
static lv_disp_drv_t dispDrv;
lv_disp_drv_init(&dispDrv);
dispDrv.hor_res = screenWidth;
dispDrv.ver_res = screenHeight;
dispDrv.flush_cb = lvglFlushCb;
dispDrv.draw_buf = &gDrawBuf;
lv_disp_drv_register(&dispDrv);
static lv_indev_drv_t indevDrv;
lv_indev_drv_init(&indevDrv);
indevDrv.type = LV_INDEV_TYPE_POINTER;
indevDrv.read_cb = lvglTouchRead;
lv_indev_drv_register(&indevDrv);
const esp_timer_create_args_t lvglTickTimerArgs = {
.callback = &lvglTick,
.name = "lvgl_tick"};
esp_timer_handle_t lvglTickTimer = nullptr;
esp_timer_create(&lvglTickTimerArgs, &lvglTickTimer);
esp_timer_start_periodic(lvglTickTimer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
createUi();
Serial.println("LVGL touch debug test ready");
}
void loop() {
updateRawTouchState();
lv_timer_handler();
delay(5);
}
@@ -0,0 +1,115 @@
#include <Arduino.h>
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#include <U8g2lib.h>
#define PIN_LCD_CS 12
#define PIN_LCD_SCLK 38
#define PIN_LCD_D0 4
#define PIN_LCD_D1 5
#define PIN_LCD_D2 6
#define PIN_LCD_D3 7
#define PIN_LCD_RST 2
#define PIN_I2C_SDA 15
#define PIN_I2C_SCL 14
#define DISP_W 480
#define DISP_H 480
#define C_BG 0x0841u
#define C_PANEL 0x1082u
#define C_CARD 0x18C3u
#define C_BORDER 0x39C7u
#define C_TEXT 0xFFFFu
#define C_MUTE 0xBDF7u
#define C_OK 0x3666u
#define C_WARN 0xECA0u
#define C_BAD 0xD145u
#define C_BUTTON 0x2145u
#define C_BUTTON2 0x3186u
#define FONT_HEAD u8g2_font_10x20_t_cyrillic
#define FONT_BODY u8g2_font_9x15_t_cyrillic
#define FONT_SMALL u8g2_font_6x13_t_cyrillic
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
PIN_LCD_CS, PIN_LCD_SCLK, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
static void drawPanel(int x, int y, int w, int h, uint16_t fill, uint16_t border, int radius = 10) {
gfx->fillRoundRect(x, y, w, h, radius, fill);
gfx->drawRoundRect(x, y, w, h, radius, border);
}
static void drawDefaultText(int x, int y, const char *text, uint16_t color, uint8_t size = 1) {
gfx->setFont();
gfx->setTextSize(size);
gfx->setTextColor(color);
gfx->setCursor(x, y);
gfx->print(text);
}
static void drawU8Text(int x, int y, const String &text, uint16_t color, const uint8_t *font) {
gfx->setTextSize(1);
gfx->setFont(font);
gfx->setTextColor(color);
gfx->setCursor(x, y);
gfx->print(text);
}
static void drawButton(int x, int y, int w, int h, uint16_t fill, const String &label, const uint8_t *font, bool useDefaultFont = false) {
drawPanel(x, y, w, h, fill, C_BORDER, 12);
if (useDefaultFont) {
drawDefaultText(x + 14, y + 30, label.c_str(), C_TEXT, 2);
return;
}
drawU8Text(x + 14, y + 32, label, C_TEXT, font);
}
static void drawScreen() {
gfx->fillScreen(C_BG);
drawPanel(12, 12, 456, 456, C_PANEL, C_BORDER, 16);
drawDefaultText(28, 42, "TEXT TEST 123", C_TEXT, 2);
drawU8Text(28, 72, "Default ASCII above", C_MUTE, (const uint8_t *)FONT_SMALL);
drawPanel(24, 90, 432, 72, C_CARD, C_BORDER, 12);
drawDefaultText(38, 118, "A: Default font ABC123", C_TEXT, 2);
drawDefaultText(38, 146, "Visible? then base path OK", C_WARN, 1);
drawPanel(24, 174, 432, 84, C_CARD, C_BORDER, 12);
drawU8Text(38, 204, "B: U8g2 ASCII abc123", C_TEXT, (const uint8_t *)FONT_BODY);
drawU8Text(38, 230, "C: Русский текст 123", C_OK, (const uint8_t *)FONT_BODY);
drawPanel(24, 270, 432, 84, C_CARD, C_BORDER, 12);
drawU8Text(38, 298, "D: Мелкий шрифт кнопок", C_TEXT, (const uint8_t *)FONT_SMALL);
drawU8Text(38, 320, "Если это видно, FONT_SMALL жив", C_MUTE, (const uint8_t *)FONT_SMALL);
drawButton(24, 368, 128, 72, C_BUTTON, "BTN 1", nullptr, true);
drawButton(176, 368, 128, 72, C_OK, "abc123", (const uint8_t *)FONT_BODY);
drawButton(328, 368, 128, 72, C_BUTTON2, "Русский", (const uint8_t *)FONT_BODY);
}
void setup() {
Serial.begin(115200);
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
gfx->begin();
gBus->writeC8D8(0x36, 0xA0);
gfx->setBrightness(220);
gfx->setUTF8Print(true);
drawScreen();
Serial.println();
Serial.println("=== text_render_test ===");
Serial.println("A: default Arduino_GFX font");
Serial.println("B: U8g2 ASCII");
Serial.println("C: U8g2 Cyrillic");
Serial.println("Buttons: default / ASCII / Cyrillic");
}
void loop() {
delay(1000);
}
@@ -0,0 +1,784 @@
/**
* @file lv_conf.h
* Configuration file for v8.4.0
*/
/*
* Copy this file as `lv_conf.h`
* 1. simply next to the `lvgl` folder
* 2. or any other places and
* - define `LV_CONF_INCLUDE_SIMPLE`
* - add the path as include path
*/
/* clang-format off */
#if 1 /*Set it to "1" to enable content*/
#ifndef LV_CONF_H
#define LV_CONF_H
#include <stdint.h>
/*====================
COLOR SETTINGS
*====================*/
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0
/*Enable features to draw on transparent background.
*It's required if opa, and transform_* style properties are used.
*Can be also used if the UI is above another layer, e.g. an OSD menu or video player.*/
#define LV_COLOR_SCREEN_TRANSP 0
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
#define LV_COLOR_MIX_ROUND_OFS 0
/*Images pixels with this color will not be drawn if they are chroma keyed)*/
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/
/*=========================
MEMORY SETTINGS
*=========================*/
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 0
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
#define LV_MEM_SIZE (48U * 1024U) /*[bytes]*/
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
#define LV_MEM_ADR 0 /*0: unused*/
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
#if LV_MEM_ADR == 0
#undef LV_MEM_POOL_INCLUDE
#undef LV_MEM_POOL_ALLOC
#endif
#else /*LV_MEM_CUSTOM*/
#define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/
#define LV_MEM_CUSTOM_ALLOC malloc
#define LV_MEM_CUSTOM_FREE free
#define LV_MEM_CUSTOM_REALLOC realloc
#endif /*LV_MEM_CUSTOM*/
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
*You will see an error log message if there wasn't enough buffers. */
#define LV_MEM_BUF_MAX_NUM 16
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
#define LV_MEMCPY_MEMSET_STD 0
/*====================
HAL SETTINGS
*====================*/
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 10 /*[ms]*/
/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 10 /*[ms]*/
/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 0
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
/*If using lvgl as ESP32 component*/
// #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
// #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
#endif /*LV_TICK_CUSTOM*/
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
*(Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI_DEF 130 /*[px/inch]*/
/*=======================
* FEATURE CONFIGURATION
*=======================*/
/*-------------
* Drawing
*-----------*/
/*Enable complex draw engine.
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
#define LV_DRAW_COMPLEX 1
#if LV_DRAW_COMPLEX != 0
/*Allow buffering some shadow calculation.
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
#define LV_SHADOW_CACHE_SIZE 0
/* Set number of maximally cached circle data.
* The circumference of 1/4 circle are saved for anti-aliasing
* radius * 4 bytes are used per circle (the most often used radiuses are saved)
* 0: to disable caching */
#define LV_CIRCLE_CACHE_SIZE 4
#endif /*LV_DRAW_COMPLEX*/
/**
* "Simple layers" are used when a widget has `style_opa < 255` to buffer the widget into a layer
* and blend it as an image with the given opacity.
* Note that `bg_opa`, `text_opa` etc don't require buffering into layer)
* The widget can be buffered in smaller chunks to avoid using large buffers.
*
* - LV_LAYER_SIMPLE_BUF_SIZE: [bytes] the optimal target buffer size. LVGL will try to allocate it
* - LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE: [bytes] used if `LV_LAYER_SIMPLE_BUF_SIZE` couldn't be allocated.
*
* Both buffer sizes are in bytes.
* "Transformed layers" (where transform_angle/zoom properties are used) use larger buffers
* and can't be drawn in chunks. So these settings affects only widgets with opacity.
*/
#define LV_LAYER_SIMPLE_BUF_SIZE (24 * 1024)
#define LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE (3 * 1024)
/*Default image cache size. Image caching keeps the images opened.
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
*However the opened images might consume additional RAM.
*0: to disable caching*/
#define LV_IMG_CACHE_DEF_SIZE 0
/*Number of stops allowed per gradient. Increase this to allow more stops.
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#define LV_GRADIENT_MAX_STOPS 2
/*Default gradient buffer size.
*When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again.
*LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes.
*If the cache is too small the map will be allocated only while it's required for the drawing.
*0 mean no caching.*/
#define LV_GRAD_CACHE_DEF_SIZE 0
/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#define LV_DITHER_GRADIENT 0
#if LV_DITHER_GRADIENT
/*Add support for error diffusion dithering.
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
*The increase in memory consumption is (24 bits * object's width)*/
#define LV_DITHER_ERROR_DIFFUSION 0
#endif
/*Maximum buffer size to allocate for rotation.
*Only used if software rotation is enabled in the display driver.*/
#define LV_DISP_ROT_MAX_BUF (10*1024)
/*-------------
* GPU
*-----------*/
/*Use Arm's 2D acceleration library Arm-2D */
#define LV_USE_GPU_ARM2D 0
/*Use STM32's DMA2D (aka Chrom Art) GPU*/
#define LV_USE_GPU_STM32_DMA2D 0
#if LV_USE_GPU_STM32_DMA2D
/*Must be defined to include path of CMSIS header of target processor
e.g. "stm32f7xx.h" or "stm32f4xx.h"*/
#define LV_GPU_DMA2D_CMSIS_INCLUDE
#endif
/*Enable RA6M3 G2D GPU*/
#define LV_USE_GPU_RA6M3_G2D 0
#if LV_USE_GPU_RA6M3_G2D
/*include path of target processor
e.g. "hal_data.h"*/
#define LV_GPU_RA6M3_G2D_INCLUDE "hal_data.h"
#endif
/*Use SWM341's DMA2D GPU*/
#define LV_USE_GPU_SWM341_DMA2D 0
#if LV_USE_GPU_SWM341_DMA2D
#define LV_GPU_SWM341_DMA2D_INCLUDE "SWM341.h"
#endif
/*Use NXP's PXP GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_PXP 0
#if LV_USE_GPU_NXP_PXP
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
*/
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
#endif
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_VG_LITE 0
/*Use SDL renderer API*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
/*Texture cache size, 8MB by default*/
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
#endif
/*-------------
* Logging
*-----------*/
/*Enable the log module*/
#define LV_USE_LOG 0
#if LV_USE_LOG
/*How important log should be added:
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
*LV_LOG_LEVEL_INFO Log important events
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
*LV_LOG_LEVEL_USER Only logs added by the user
*LV_LOG_LEVEL_NONE Do not log anything*/
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
/*1: Print the log with 'printf';
*0: User need to register a callback with `lv_log_register_print_cb()`*/
#define LV_LOG_PRINTF 0
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
#define LV_LOG_TRACE_MEM 1
#define LV_LOG_TRACE_TIMER 1
#define LV_LOG_TRACE_INDEV 1
#define LV_LOG_TRACE_DISP_REFR 1
#define LV_LOG_TRACE_EVENT 1
#define LV_LOG_TRACE_OBJ_CREATE 1
#define LV_LOG_TRACE_LAYOUT 1
#define LV_LOG_TRACE_ANIM 1
#endif /*LV_USE_LOG*/
/*-------------
* Asserts
*-----------*/
/*Enable asserts if an operation is failed or an invalid data is found.
*If LV_USE_LOG is enabled an error message will be printed on failure*/
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/
/*Add a custom handler when assert happens e.g. to restart the MCU*/
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
/*-------------
* Others
*-----------*/
/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 0
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif
/*1: Show the used memory and the memory fragmentation
* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0
#if LV_USE_MEM_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif
/*1: Draw random colored rectangles over the redrawn areas*/
#define LV_USE_REFR_DEBUG 0
/*Change the built in (v)snprintf functions*/
#define LV_SPRINTF_CUSTOM 0
#if LV_SPRINTF_CUSTOM
#define LV_SPRINTF_INCLUDE <stdio.h>
#define lv_snprintf snprintf
#define lv_vsnprintf vsnprintf
#else /*LV_SPRINTF_CUSTOM*/
#define LV_SPRINTF_USE_FLOAT 0
#endif /*LV_SPRINTF_CUSTOM*/
#define LV_USE_USER_DATA 1
/*Garbage Collector settings
*Used if lvgl is bound to higher level language and the memory is managed by that language*/
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
#endif /*LV_ENABLE_GC*/
/*=====================
* COMPILER SETTINGS
*====================*/
/*For big endian systems set to 1*/
#define LV_BIG_ENDIAN_SYSTEM 0
/*Define a custom attribute to `lv_tick_inc` function*/
#define LV_ATTRIBUTE_TICK_INC
/*Define a custom attribute to `lv_timer_handler` function*/
#define LV_ATTRIBUTE_TIMER_HANDLER
/*Define a custom attribute to `lv_disp_flush_ready` function*/
#define LV_ATTRIBUTE_FLUSH_READY
/*Required alignment size for buffers*/
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
* E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN
/*Attribute to mark large constant arrays for example font's bitmaps*/
#define LV_ATTRIBUTE_LARGE_CONST
/*Compiler prefix for a big array declaration in RAM*/
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY
/*Place performance critical functions into a faster memory (e.g RAM)*/
#define LV_ATTRIBUTE_FAST_MEM
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
#define LV_ATTRIBUTE_DMA
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
*should also appear on LVGL binding API such as Micropython.*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
#define LV_USE_LARGE_COORD 0
/*==================
* FONT USAGE
*===================*/
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
*https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 1
#define LV_FONT_MONTSERRAT_10 1
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_18 1
#define LV_FONT_MONTSERRAT_20 1
#define LV_FONT_MONTSERRAT_22 1
#define LV_FONT_MONTSERRAT_24 1
#define LV_FONT_MONTSERRAT_26 1
#define LV_FONT_MONTSERRAT_28 1
#define LV_FONT_MONTSERRAT_30 1
#define LV_FONT_MONTSERRAT_32 1
#define LV_FONT_MONTSERRAT_34 1
#define LV_FONT_MONTSERRAT_36 1
#define LV_FONT_MONTSERRAT_38 1
#define LV_FONT_MONTSERRAT_40 1
#define LV_FONT_MONTSERRAT_42 1
#define LV_FONT_MONTSERRAT_44 1
#define LV_FONT_MONTSERRAT_46 1
#define LV_FONT_MONTSERRAT_48 1
/*Demonstrate special features*/
#define LV_FONT_MONTSERRAT_12_SUBPX 0
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
/*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0
/*Optionally declare custom fonts here.
*You can use these fonts as default font too and they will be available globally.
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
#define LV_FONT_CUSTOM_DECLARE
/*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_14
/*Enable handling large font and/or fonts with a lot of characters.
*The limit depends on the font size, font face and bpp.
*Compiler error will be triggered if a font needs it.*/
#define LV_FONT_FMT_TXT_LARGE 0
/*Enables/disables support for compressed fonts.*/
#define LV_USE_FONT_COMPRESSED 0
/*Enable subpixel rendering*/
#define LV_USE_FONT_SUBPX 0
#if LV_USE_FONT_SUBPX
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
#endif
/*Enable drawing placeholders when glyph dsc is not found*/
#define LV_USE_FONT_PLACEHOLDER 1
/*=================
* TEXT SETTINGS
*=================*/
/**
* Select a character encoding for strings.
* Your IDE or editor should have the same character encoding
* - LV_TXT_ENC_UTF8
* - LV_TXT_ENC_ASCII
*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8
/*Can break (wrap) texts on these chars*/
#define LV_TXT_BREAK_CHARS " ,.;:-_"
/*If a word is at least this long, will break wherever "prettiest"
*To disable, set to a value <= 0*/
#define LV_TXT_LINE_BREAK_LONG_LEN 0
/*Minimum number of characters in a long word to put on a line before a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
/*Minimum number of characters in a long word to put on a line after a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
/*The control character to use for signalling text recoloring.*/
#define LV_TXT_COLOR_CMD "#"
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
*The direction will be processed according to the Unicode Bidirectional Algorithm:
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
#define LV_USE_BIDI 0
#if LV_USE_BIDI
/*Set the default direction. Supported values:
*`LV_BASE_DIR_LTR` Left-to-Right
*`LV_BASE_DIR_RTL` Right-to-Left
*`LV_BASE_DIR_AUTO` detect texts base direction*/
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
#endif
/*Enable Arabic/Persian processing
*In these languages characters should be replaced with an other form based on their position in the text*/
#define LV_USE_ARABIC_PERSIAN_CHARS 0
/*==================
* WIDGET USAGE
*================*/
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/
#define LV_USE_ARC 1
#define LV_USE_BAR 1
#define LV_USE_BTN 1
#define LV_USE_BTNMATRIX 1
#define LV_USE_CANVAS 1
#define LV_USE_CHECKBOX 1
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/
#define LV_USE_IMG 1 /*Requires: lv_label*/
#define LV_USE_LABEL 1
#if LV_USE_LABEL
#define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
#define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/
#endif
#define LV_USE_LINE 1
#define LV_USE_ROLLER 1 /*Requires: lv_label*/
#if LV_USE_ROLLER
#define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
#endif
#define LV_USE_SLIDER 1 /*Requires: lv_bar*/
#define LV_USE_SWITCH 1
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/
#if LV_USE_TEXTAREA != 0
#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
#endif
#define LV_USE_TABLE 1
/*==================
* EXTRA COMPONENTS
*==================*/
/*-----------
* Widgets
*----------*/
#define LV_USE_ANIMIMG 1
#define LV_USE_CALENDAR 1
#if LV_USE_CALENDAR
#define LV_CALENDAR_WEEK_STARTS_MONDAY 0
#if LV_CALENDAR_WEEK_STARTS_MONDAY
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
#else
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
#endif
#define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
#define LV_USE_CALENDAR_HEADER_ARROW 1
#define LV_USE_CALENDAR_HEADER_DROPDOWN 1
#endif /*LV_USE_CALENDAR*/
#define LV_USE_CHART 1
#define LV_USE_COLORWHEEL 1
#define LV_USE_IMGBTN 1
#define LV_USE_KEYBOARD 1
#define LV_USE_LED 1
#define LV_USE_LIST 1
#define LV_USE_MENU 1
#define LV_USE_METER 1
#define LV_USE_MSGBOX 1
#define LV_USE_SPAN 1
#if LV_USE_SPAN
/*A line text can contain maximum num of span descriptor */
#define LV_SPAN_SNIPPET_STACK_SIZE 64
#endif
#define LV_USE_SPINBOX 1
#define LV_USE_SPINNER 1
#define LV_USE_TABVIEW 1
#define LV_USE_TILEVIEW 1
#define LV_USE_WIN 1
/*-----------
* Themes
*----------*/
/*A simple, impressive and very complete theme*/
#define LV_USE_THEME_DEFAULT 1
#if LV_USE_THEME_DEFAULT
/*0: Light mode; 1: Dark mode*/
#define LV_THEME_DEFAULT_DARK 0
/*1: Enable grow on press*/
#define LV_THEME_DEFAULT_GROW 1
/*Default transition time in [ms]*/
#define LV_THEME_DEFAULT_TRANSITION_TIME 80
#endif /*LV_USE_THEME_DEFAULT*/
/*A very simple theme that is a good starting point for a custom theme*/
#define LV_USE_THEME_BASIC 1
/*A theme designed for monochrome displays*/
#define LV_USE_THEME_MONO 1
/*-----------
* Layouts
*----------*/
/*A layout similar to Flexbox in CSS.*/
#define LV_USE_FLEX 1
/*A layout similar to Grid in CSS.*/
#define LV_USE_GRID 1
/*---------------------
* 3rd party libraries
*--------------------*/
/*File system interfaces for common APIs */
/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 0
#if LV_USE_FS_STDIO
#define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for open, read, etc*/
#define LV_USE_FS_POSIX 0
#if LV_USE_FS_POSIX
#define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for CreateFile, ReadFile, etc*/
#define LV_USE_FS_WIN32 0
#if LV_USE_FS_WIN32
#define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/
#define LV_USE_FS_FATFS 0
#if LV_USE_FS_FATFS
#define LV_FS_FATFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/
#define LV_USE_FS_LITTLEFS 0
#if LV_USE_FS_LITTLEFS
#define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*PNG decoder library*/
#define LV_USE_PNG 0
/*BMP decoder library*/
#define LV_USE_BMP 0
/* JPG + split JPG decoder library.
* Split JPG is a custom format optimized for embedded systems. */
#define LV_USE_SJPG 0
/*GIF decoder library*/
#define LV_USE_GIF 0
/*QR code library*/
#define LV_USE_QRCODE 1
/*FreeType library*/
#define LV_USE_FREETYPE 0
#if LV_USE_FREETYPE
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
#define LV_FREETYPE_CACHE_SIZE (16 * 1024)
#if LV_FREETYPE_CACHE_SIZE >= 0
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
/* if font size >= 256, must be configured as image cache */
#define LV_FREETYPE_SBIT_CACHE 0
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
/* (0:use system defaults) */
#define LV_FREETYPE_CACHE_FT_FACES 0
#define LV_FREETYPE_CACHE_FT_SIZES 0
#endif
#endif
/*Tiny TTF library*/
#define LV_USE_TINY_TTF 0
#if LV_USE_TINY_TTF
/*Load TTF data from files*/
#define LV_TINY_TTF_FILE_SUPPORT 0
#endif
/*Rlottie library*/
#define LV_USE_RLOTTIE 0
/*FFmpeg library for image decoding and playing videos
*Supports all major image formats so do not enable other image decoder with it*/
#define LV_USE_FFMPEG 0
#if LV_USE_FFMPEG
/*Dump input information to stderr*/
#define LV_FFMPEG_DUMP_FORMAT 0
#endif
/*-----------
* Others
*----------*/
/*1: Enable API to take snapshot for object*/
#define LV_USE_SNAPSHOT 0
/*1: Enable Monkey test*/
#define LV_USE_MONKEY 0
/*1: Enable grid navigation*/
#define LV_USE_GRIDNAV 0
/*1: Enable lv_obj fragment*/
#define LV_USE_FRAGMENT 0
/*1: Support using images as font in label or span widgets */
#define LV_USE_IMGFONT 0
/*1: Enable a published subscriber based messaging system */
#define LV_USE_MSG 0
/*1: Enable Pinyin input method*/
/*Requires: lv_keyboard*/
#define LV_USE_IME_PINYIN 0
#if LV_USE_IME_PINYIN
/*1: Use default thesaurus*/
/*If you do not use the default thesaurus, be sure to use `lv_ime_pinyin` after setting the thesauruss*/
#define LV_IME_PINYIN_USE_DEFAULT_DICT 1
/*Set the maximum number of candidate panels that can be displayed*/
/*This needs to be adjusted according to the size of the screen*/
#define LV_IME_PINYIN_CAND_TEXT_NUM 6
/*Use 9 key input(k9)*/
#define LV_IME_PINYIN_USE_K9_MODE 1
#if LV_IME_PINYIN_USE_K9_MODE == 1
#define LV_IME_PINYIN_K9_CAND_TEXT_NUM 3
#endif // LV_IME_PINYIN_USE_K9_MODE
#endif
/*==================
* EXAMPLES
*==================*/
/*Enable the examples to be built with the library*/
#define LV_BUILD_EXAMPLES 1
/*===================
* DEMO USAGE
====================*/
/*Show some widget. It might be required to increase `LV_MEM_SIZE` */
#define LV_USE_DEMO_WIDGETS 1
#if LV_USE_DEMO_WIDGETS
#define LV_DEMO_WIDGETS_SLIDESHOW 1
#endif
/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK 1
#if LV_USE_DEMO_BENCHMARK
/*Use RGB565A8 images with 16 bit color depth instead of ARGB8565*/
#define LV_DEMO_BENCHMARK_RGB565A8 0
#endif
/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS 1
/*Music player demo*/
#define LV_USE_DEMO_MUSIC 1
#if LV_USE_DEMO_MUSIC
#define LV_DEMO_MUSIC_SQUARE 0
#define LV_DEMO_MUSIC_LANDSCAPE 0
#define LV_DEMO_MUSIC_ROUND 0
#define LV_DEMO_MUSIC_LARGE 0
#define LV_DEMO_MUSIC_AUTO_PLAY 0
#endif
/*--END OF LV_CONF_H--*/
#endif /*LV_CONF_H*/
#endif /*End of "Content enable"*/
@@ -0,0 +1,11 @@
# Factory Firmware Backup
Здесь хранится полный дамп флеша текущего состояния платы.
Файлы:
- `factory-full-16mb.bin` — полный дамп флеша `0x000000..0xFFFFFF`
- `factory-full-16mb.bin.sha256` — контрольная сумма
Скрипты:
- `backup_factory.sh` — снять резервную копию
- `restore_factory_backup.sh` — восстановить резервную копию на плату
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PORT="${PORT:-/dev/ttyACM0}"
ESPTOOL="${ESPTOOL:-$HOME/.arduino15/packages/esp32/tools/esptool_py/5.1.0/esptool}"
BAUD="${BAUD:-921600}"
OUT_BIN="${ROOT_DIR}/factory-full-16mb.bin"
echo "== Port: ${PORT}"
echo "== Output: ${OUT_BIN}"
echo "== Esptool: ${ESPTOOL}"
echo "== Baud: ${BAUD}"
"${ESPTOOL}" --port "${PORT}" --baud "${BAUD}" read-flash 0 0x1000000 "${OUT_BIN}"
sha256sum "${OUT_BIN}" | tee "${OUT_BIN}.sha256"
echo
echo "== Backup done."
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PORT="${PORT:-/dev/ttyACM0}"
ESPTOOL="${ESPTOOL:-$HOME/.arduino15/packages/esp32/tools/esptool_py/5.1.0/esptool}"
IN_BIN="${IN_BIN:-${ROOT_DIR}/factory-full-16mb.bin}"
if [[ ! -f "${IN_BIN}" ]]; then
echo "Backup file not found: ${IN_BIN}" >&2
exit 1
fi
echo "== Port: ${PORT}"
echo "== Input: ${IN_BIN}"
echo "== Esptool: ${ESPTOOL}"
"${ESPTOOL}" --port "${PORT}" --baud 921600 write-flash 0 "${IN_BIN}"
echo
echo "== Restore done."
@@ -0,0 +1,17 @@
# Reference Notes
Модель: Waveshare `ESP32-S3-Touch-AMOLED-2.16` (SKU 33969/33970)
Полезные страницы:
- Product: https://www.waveshare.com/product/arduino/boards-kits/esp32-s3/esp32-s3-touch-amoled-2.16.htm
- Docs home: https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16
- Arduino setup: https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16/Development-Environment-Setup-Arduino
- Official examples: https://github.com/waveshareteam/ESP32-S3-Touch-AMOLED-2.16
Ключевое по железу (по документации):
- ESP32-S3R8, 16MB flash, 8MB PSRAM
- AMOLED 2.16" 480x480
- Capacitive touch (CST9220)
- IMU QMI8658
- Audio codec ES8311 (playback) и ES7210 (microphones)
- TF slot (SD), сейчас без карты
@@ -0,0 +1,353 @@
# SHiNE ESP32 Homeserver UI Nav Minimal Spec
Легаси-спецификация старого навигационного теста для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Актуальный основной скетч теперь находится в `../main-device/shine_homeserver_main/`; этот документ оставлен только как историческая справка по старому тестовому UI.
## Цель
Этот прототип проверяет базовую механику экранов, крупных кнопок, свайпов, первичную настройку Wi-Fi и настройку серверных адресов через общий экран редактирования текста.
На этом этапе отсутствуют:
- логин/пароль учётной записи SHiNE;
- PIN;
- кошелёк;
- QR;
- регистрация;
- PDA update/create транзакции;
- входящие запросы.
## Экраны
Прототип содержит 10 экранов:
- `HOME`
- `SETTINGS_MENU`
- `WIFI_SCREEN`
- `SERVER_SCREEN`
- `ACCOUNT_SCREEN`
- `ACCOUNT_HOMESERVER_SCREEN`
- `ACCOUNT_SECRET_SCREEN`
- `SECRET_SHOW_SCREEN`
- `SECRET_GENERATE_*`
- `TEXT_EDIT_SCREEN`
- `REGISTER_ACCOUNT_PLACEHOLDER`
## HOME
Показывает:
- сверху слева значение homeserver или `homeserver not set`;
- ниже значение логина или `login not set`;
- справа от строки логина индикатор статуса Solana-аккаунта:
- зелёный — все ключи совпадают;
- красный — есть mismatch;
- белый контур — пользователь не найден в Solana PDA;
- рядом с индикатором краткий текст ошибки, если статус не зелёный;
- третьей строкой `secret not set`, если секрет ещё не помечен как установленный;
- сверху справа один ряд индикаторов:
- процент батареи;
- иконка батареи;
- индикатор Wi-Fi уровня сигнала;
- по центру крупный текст `STATUS`;
- одна строка Wi-Fi вида `Wi-Fi (<ssid>) connected/disconnected`;
- кнопка баланса вида `Balance: <value SOL>` или `Balance: failed to load`, по нажатию выполняет повторный запрос;
- строка `SHiNE: <server> connected/account not configured/unavailable`;
- при отсутствии пользователя в Solana PDA слева снизу появляется кнопка `REGISTER ACCOUNT`;
- снизу кнопку `SETTINGS`, уменьшенную примерно до половины ширины экрана и сдвинутую к правому краю.
- внизу на тёмной полосе подпись `SHiNE homeserver (v.0.18)`.
Строка Wi-Fi на `HOME`:
- `Wi-Fi (not configured) not configured`
- `Wi-Fi (<saved_ssid>) disconnected`
- `Wi-Fi (<current_ssid>) connected`
Переходы:
- кнопка `REGISTER ACCOUNT` -> `REGISTER_ACCOUNT_PLACEHOLDER`, только если пользователь не найден;
- кнопка `SETTINGS` -> `SETTINGS_MENU`;
- свайп влево -> `SETTINGS_MENU`.
Примечание:
- поведение `REGISTER ACCOUNT -> REGISTER_ACCOUNT_PLACEHOLDER` относится к старой тестовой версии и не является актуальным для основного скетча.
Фоновая логика:
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду;
- при наличии `login + secret + homeserver` и Wi-Fi устройство читает Solana `user_pda` напрямую через RPC;
- сравниваются `root key`, `blockchain key`, `client key` и `homeserver` session-запись типа `100`;
- для строки `SHiNE:` устройство держит отдельную WebSocket-сессию с сервером SHiNE:
- авторизация через `AuthChallenge/CreateAuthSession` или `SessionChallenge/SessionLogin`;
- session key = публичный `homeserver key`;
- подтверждение создания сессии подписывается `client key`;
- heartbeat выполняется `Ping` раз в минуту.
## SETTINGS_MENU
Показывает вертикальное меню из 3 пунктов:
- `Wi-Fi`
- `Server`
- `Account`
Одновременно видны только 2 пункта.
Начальное состояние:
- `Wi-Fi`
- `Server`
После свайпа вверх:
- `Server`
- `Account`
После свайпа вниз:
- `Wi-Fi`
- `Server`
Обе видимые карточки меню имеют одинаковый цвет.
Переходы:
- нажатие `Wi-Fi` -> `WIFI_SCREEN`;
- нажатие `Server` -> `SERVER_SCREEN`;
- нажатие `Account` -> `ACCOUNT_SCREEN`;
- свайп вправо -> `HOME`.
## WIFI_SCREEN
Экран содержит 2 внутренних режима.
### 1. Overview
Показывает:
- текущий Wi-Fi статус;
- сохранённый SSID и число известных сетей;
- статусное сообщение;
- кнопку `SELECT NETWORK`;
- кнопку `CLEAR SAVED WI-FI`;
- кнопку `BACK`.
### 2. Scan Results
После `SELECT NETWORK` выполняется скан доступных Wi-Fi сетей.
Показывает:
- заголовок `SELECT NETWORK`;
- количество или результат сканирования;
- список найденных SSID как крупные кнопки;
- кнопку `SCAN AGAIN`;
- кнопку `BACK`.
Нажатие на SSID открывает `TEXT_EDIT_SCREEN` для ввода пароля.
Поведение:
- если для выбранного SSID пароль уже был сохранён раньше, он сразу подставляется в поле ввода;
- если пароль меняют для другого SSID, старые сохранённые пароли других сетей не теряются;
- после успешного подключения выбранная сеть становится текущей `wifi_ssid/wifi_pass`;
- одновременно хранится до `8` известных сетей `SSID -> password`;
- `CLEAR SAVED WI-FI` очищает все сохранённые сети и текущую сеть.
Переходы:
- свайп вправо из любого режима `WIFI_SCREEN` -> `SETTINGS_MENU`
- кнопка `BACK` -> `SETTINGS_MENU`
## SERVER_SCREEN
Показывает:
- статусное сообщение;
- текущий `Solana RPC` адрес;
- кнопку `SOLANA RPC`;
- текущий `SHiNE server login` или уже резолвленный адрес;
- кнопку `SHiNE SERVER LOGIN`, если обычный `user PDA` ещё не зарегистрирован.
Значения по умолчанию:
- Solana RPC: `https://api.mainnet-beta.solana.com`
- SHiNE server login: `shineupme`
Нажатие на любую из двух кнопок открывает `TEXT_EDIT_SCREEN`.
Переходы:
- свайп вправо -> `SETTINGS_MENU`
## ACCOUNT_SCREEN
Показывает:
- заголовок `ACCOUNT`;
- статусное сообщение;
- кнопку `Login (<value|not set>)`;
- кнопку `Homeserver (<value|not set>)`;
- кнопку `Secret (<*****|not set>)`.
Переходы:
- свайп вправо -> `SETTINGS_MENU`
- `Login` -> `TEXT_EDIT_SCREEN`
- `Homeserver` -> `ACCOUNT_HOMESERVER_SCREEN`
- `Secret` -> `ACCOUNT_SECRET_SCREEN`
## ACCOUNT_HOMESERVER_SCREEN
Показывает:
- текущий `homeserver`;
- рекомендацию оставить `homeserver1`, если устройство одно;
- кнопку `USE HOMESERVER1`;
- кнопку `EDIT MANUALLY`;
- кнопку `BACK`.
Переходы:
- `USE HOMESERVER1` -> сохраняет `homeserver1` и возвращает в `ACCOUNT_SCREEN`
- `EDIT MANUALLY` -> `TEXT_EDIT_SCREEN`
- свайп вправо -> `ACCOUNT_SCREEN`
## ACCOUNT_SECRET_SCREEN
Показывает:
- кнопку `SHOW SECRET` или серую `SECRET NOT SET`, если секрета ещё нет;
- кнопку `ENTER SECRET MANUALLY (NOT RECOMMENDED)`;
- кнопку `GENERATE SECRET`.
Переходы:
- свайп вправо -> `ACCOUNT_SCREEN`
- `SHOW SECRET` -> экран просмотра секрета
- `ENTER SECRET MANUALLY (NOT RECOMMENDED)` -> `TEXT_EDIT_SCREEN`
- `GENERATE SECRET` -> экран подтверждения генерации
## SECRET_SHOW_SCREEN
Показывает:
- заголовок `SECRET`;
- вертикально прокручиваемый список ключей;
- `Secret (base58)`;
- `Root key (base58)`;
- `Root key priv (base58)`;
- `Blockchain key (base58)`;
- `Blockchain key priv (base58)`;
- `Client key (base58)`;
- `Client key priv (base58)`;
- `Homeserver key (base58)`;
- `Homeserver key priv (base58)`;
- для каждого поля показывается формула derivation;
- значения ключей показываются полными строками увеличенным шрифтом;
- кнопку `BACK`.
## TEXT_EDIT_SCREEN
Общий экран редактирования строковых значений.
Используется для:
- пароля Wi-Fi;
- Solana RPC;
- SHiNE server login.
Показывает:
- заголовок;
- поле ввода, уже заполненное старым значением;
- новые символы всегда добавляются только в конец текста;
- визуальный курсор не показывается;
- пароль Wi-Fi показывается открыто, без маскировки точками;
- кнопки `SAVE`, `CANCEL`, `DEL`, `CLR`;
- большую экранную клавиатуру.
## REGISTER_ACCOUNT_PLACEHOLDER
Временный экран-заглушка регистрации.
Показывает:
- заголовок `REGISTER ACCOUNT`;
- сообщение, что регистрационный flow пока не реализован;
- кнопку `BACK`.
## Клавиатура
Клавиатура единая для всех текстовых вводов.
Особенности:
- занимает нижнюю часть экрана;
- разбита на 2 страницы;
- переключение страниц выполняется свайпом влево/вправо внутри `TEXT_EDIT_SCREEN`;
- страница 1 содержит в основном буквы и базовые URL-символы;
- страница 2 содержит цифры и дополнительные URL/символьные кнопки;
- на символьных страницах должны быть доступны как минимум `!` и отдельная кнопка `SPACE`;
- переключение `ABC/123` и `SHIFT` не должно очищать уже введённый текст;
- переключение страниц клавиатуры свайпом тоже не должно очищать уже введённый текст, включая цифры, символы и уже набранные заглавные буквы;
- есть специальные действия `DEL` и `CLR`.
## Хранение Wi-Fi
Используется `Preferences` (NVS памяти ESP32):
- `wifi_ssid`
- `wifi_pass`
- `wifi_known_good`
- до `8` сохранённых пар `SSID -> password`
При старте устройства, если сохранён SSID, выполняется попытка подключения к текущей сохранённой сети.
Если пользователь уже вводил пароль для сети раньше:
- при повторном выборе этого SSID старый пароль сразу подставляется в editor screen;
- сохранение пароля для другой сети не удаляет уже сохранённые пароли остальных сетей.
Если сеть раньше уже была успешно подключена и помечена как валидная:
- после потери связи устройство автоматически пытается переподключиться;
- первые повторные попытки идут раз в `10` секунд;
- если связи нет долго, интервал увеличивается до `30` секунд.
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду и подтягивает актуальные:
- статус Wi-Fi;
- SSID;
- уровень сигнала;
- процент батареи.
## Хранение серверов
Используется `Preferences` (NVS памяти ESP32):
- `solana_rpc`
- `shine_server_login`
## Хранение аккаунта
Используется `Preferences` (NVS памяти ESP32):
- `login`
- `homeserver`
- `secret_set`
## Детали клавиатуры
- клавиатура занимает примерно `2/3`-`3/4` высоты экрана;
- сверху остаются только заголовок и поле ввода;
- буквы занимают 3 ряда;
- половина букв находится на левой странице, половина на правой;
- на правой странице кнопки тоже стоят в ровных колонках, без сдвига рядов вправо;
- отдельный режим `symbols` тоже разделён на 2 страницы;
- в режиме `symbols` на правой странице есть отдельная кнопка `SPACE` и отдельная кнопка `!`;
- 3 основных ряда клавиш и нижний служебный ряд увеличены по высоте;
- клавиши дополнительно увеличены по высоте по сравнению с предыдущим промежуточным вариантом;
- четвёртый ряд содержит:
- переключение `ABC/123`
- `SHIFT`
- `DEL`
- `SAVE`
- `CANCEL`
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE homeserver (v.0.18)`.
## Жесты
Поддерживаются направления:
- `SWIPE_LEFT`
- `SWIPE_RIGHT`
- `SWIPE_UP`
- `SWIPE_DOWN`
Маршруты:
- `HOME`: свайп влево -> `SETTINGS_MENU`
- `SETTINGS_MENU`: свайп вправо -> `HOME`
- `SETTINGS_MENU`: свайп вверх/вниз -> прокрутка списка
- `WIFI_SCREEN`: свайп вправо -> `SETTINGS_MENU`
- `SERVER_SCREEN`: свайп вправо -> `SETTINGS_MENU`
- `ACCOUNT_SCREEN`: свайп вправо -> `SETTINGS_MENU`
- `ACCOUNT_HOMESERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
- `ACCOUNT_SECRET_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
- `TEXT_EDIT_SCREEN`: свайп влево/вправо -> переключение страниц клавиатуры
- переключение страниц клавиатуры срабатывает только если свайп начался в зоне самой клавиатуры, а не по всему экрану редактора
## Особенность обработки жестов
- если палец ушёл из зоны обычного тапа и жест определяется как свайп, случайное попадание на кнопку по пути не должно превращать свайп в нажатие;
- приоритет у свайпа, а не у `click`.
- одно непрерывное касание может вызвать не более одного действия кнопки;
- скольжение пальцем по экранной клавиатуре не должно вызывать цепочку из нескольких нажатий;
- периодический refresh на `HOME` не должен срабатывать во время активного касания.
## Язык
Пока интерфейс отображается в ASCII/English, чтобы использовать подтверждённый рабочий шрифтовой путь на устройстве.
@@ -0,0 +1,681 @@
# SHiNE ESP32 Homeserver UI Spec
## Назначение
Этот документ описывает актуальный UI-прототип homeserver `SHiNE` для платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
Актуальный основной Arduino-скетч лежит в `../main-device/shine_homeserver_main/`.
Документ является источником истины для Arduino-скетча:
- если меняется этот документ, должен меняться и скетч;
- если меняется скетч, должен обновляться и этот документ;
- экраны, кнопки, поля, статусы, переходы и тексты не должны расходиться.
## Текущий объём реализации
Текущая реализация является интерактивным прототипом экрана устройства, пригодным для ручной проверки на железе.
Что уже входит в прототип:
- локальный UI на тач-экране;
- хранение настроек и секретов во внутренней памяти `ESP32` через `NVS`;
- экранный UI устройства работает на английском языке, потому что кириллица на этом шрифтовом пути пока не поддерживается стабильно;
- экран пополнения с реальным `solana:` URI и рисованием QR-кода через `LVGL`;
- реальное подключение к `Wi-Fi` по сохранённым `SSID/паролю`;
- реальная проверка доступности `API`, `RPC` и `WS`-адресов;
- реальное чтение баланса кошелька из `Solana RPC`;
- проверка обязательных условий перед регистрацией;
- живая on-chain регистрация серверного `user_pda` в `shine_users` по нажатию кнопки на главном экране;
- живой экран заявок на подключение новых устройств через доверенную homeserver-сессию;
- PIN-блокировка (в текущей временной сборке вход по PIN отключён, устройство открывает `HOME` сразу после старта);
- базовые настройки, статус и главный экран;
- сохранение `PDA` и `tx signature` после успешной регистрации.
- создание и возобновление серверной сессии `SHiNE` через WebSocket с `sessionType = 100` и `clientPlatform = "ESP32"`.
Что пока считается именно прототипом, а не финальной интеграцией:
- пока реализован только сценарий заявок на подключение устройств через доверенную сессию;
- другие типы входящих запросов на подпись и произвольные approval-flow на этом устройстве ещё не подключены.
## Основная идея устройства
Устройство работает как отдельный homeserver:
- хранит секрет на самом устройстве;
- позволяет ввести логин, секрет и имя homeserver;
- показывает адрес кошелька устройства;
- позволяет пополнить баланс перед регистрацией;
- после выполнения условий даёт зарегистрировать устройство как homeserver;
- после авторизации в SHiNE может подтверждать заявки на подключение новых устройств пользователя.
`SD`-карта не нужна для постоянного хранения секрета в этом прототипе.
Основное сохранение идёт во внутреннюю flash-память через `NVS`.
## Данные, которые хранятся на устройстве
Прототип хранит:
- `PIN`;
- `Wi-Fi SSID`;
- `Wi-Fi password`;
- `login`;
- `session/homeserver name`;
- `master secret`;
- `wallet address`;
- `user pda address`;
- `registration signature`;
- `balance`;
- `server login` для первичной привязки;
- `resolved server api url` / `rpc url` / `ws url` после чтения PDA сервера;
- флаги:
`wifiReady`, `serversReady`, `secretReady`, `registered`, `online`.
Для первой регистрации обычного `user PDA` устройство берёт `createdAtMs` из NTP прямо перед отправкой транзакции в Solana. При последующих обновлениях `user PDA` устройство так же берёт актуальный `updatedAtMs` из NTP перед отправкой update-транзакции. Дальше в `user PDA` сохраняется `accessServers`, где по умолчанию лежит `shineupme`.
## Правило серверной сессии SHiNE
При подключении к серверу `SHiNE` устройство должно авторизовываться как homeserver-сеанс:
- `sessionType = 100`
- `clientPlatform = "ESP32"`
- `clientInfo = "ESP32 homeserver:<homeserverName>"`
Это относится и к `CreateAuthSession`, и к `SessionLogin`.
## Правила готовности к регистрации
Кнопка регистрации доступна только если одновременно выполнены условия:
1. настроен и подтверждён `Wi-Fi`;
2. задан и подтверждён `SHiNE server login`;
3. задан логин;
4. сгенерирован или введён секрет;
5. баланс кошелька не меньше `0.20 SOL`;
6. устройство ещё не зарегистрировано.
Если хотя бы одно условие не выполнено, главный экран показывает, чего именно не хватает.
## Экранная модель
В прототипе используются следующие экраны:
1. `LOCK`
2. `HOME`
3. `STATUS`
4. `CONNECTION`
5. `WIFI_EDIT`
6. `SERVERS`
7. `ACCOUNT`
8. `WALLET`
9. `WALLET_QR`
10. `WALLET_SIGN_REQUEST`
11. `REQUESTS`
12. `REQUEST_DETAIL`
13. `SETTINGS`
14. `PIN_EDIT`
15. `TEXT_INPUT`
16. `CONFIRM`
17. `REGISTER_ACCOUNT_CONFIRM`
18. `REGISTER_ACCOUNT_RESULT`
## Общие правила интерфейса
- Верхняя строка всегда показывает краткий статус устройства:
`PIN`, `Wi-Fi`, `сервер`, `регистрация`.
- В правом верхнем углу рядом с батареей:
- сначала процент заряда;
- затем, если устройство реально заряжается, маленькая иконка молнии;
- затем контур батареи;
- затем индикатор `Wi-Fi`.
- Основной язык прототипа: английский.
- Для вывода текста в текущей временной сборке используется стандартный шрифт `Arduino_GFX`.
- Русские строки в UI устройства пока не использовать.
- Кнопки крупные, с тач-ориентированным размером.
- Опасные действия подтверждаются отдельным диалогом.
- После изменения данных конфигурация сразу сохраняется в `NVS`.
## Экран LOCK
Назначение:
- экран блокировки предусмотрен в UI, но в текущей временной сборке после запуска пропускается;
- вход по PIN временно отключён для отладки остальных экранов.
Отображается:
- заголовок `SHiNE Device`;
- статус `Устройство заблокировано`;
- поле ввода PIN в виде маски;
- кнопки цифровой клавиатуры;
- кнопки `Стереть` и `Открыть`.
Поведение:
- если PIN введён верно, открывается `HOME`;
- если PIN неверный, показывается ошибка `Неверный PIN`;
- в текущей временной сборке этот экран не показывается автоматически при старте.
## Экран HOME
Это основной экран устройства.
Показывает:
- крупный статус регистрации;
- имя логина;
- имя homeserver;
- строку `Wi-Fi: <SSID> connected|disconnected`;
- строку `SHiNE: <server address> connected|unavailable`;
- короткий статус баланса.
Особенности верхнего блока:
- зелёный/контурный статусный кружок аккаунта расположен слева от строки логина;
- блок `STATUS` поднят выше относительно предыдущей версии;
- если состояние хорошее, слово `connected` в строках `Wi-Fi` и `SHiNE` показывается зелёным.
В зоне кошелька:
- вместо старых двух кнопок `баланс` и `QR` показывается одна широкая кнопка;
- текст кнопки: `Wallet: <selected wallet name>`;
- доступные имена:
- `ClientKey`
- `RootKey`
- либо сохранённое имя `custom`-кошелька;
- после старта устройства баланс активного выбранного кошелька пытается загрузиться автоматически, если уже есть секрет и `Wi-Fi`;
- нажатие на эту кнопку открывает экран `WALLET`.
Нижние кнопки:
- `Статус`
- `Подключение`
- `Аккаунт`
- `Wallet`
- `Запросы`
- `Настройки`
Дополнительная большая кнопка:
- `REGISTER ACCOUNT`
- либо жёлтая `ADD HOMESERVER`
- либо жёлтая `FIX HOMESERVER PASSWORD`
Если регистрация уже сделана:
- если пользователь создан, но в `PDA` ещё нет сессии текущего homeserver, показывается жёлтая кнопка `ADD HOMESERVER`;
- если в `PDA` есть homeserver с тем же именем, но с другим ключом, показывается жёлтая кнопка `FIX HOMESERVER PASSWORD`;
- если и пользователь, и homeserver-сессия уже корректны, вместо призыва к регистрации показывается статус `Homeserver активен`.
- две нижние кнопки внизу экрана не прилегают вплотную друг к другу, между ними есть небольшой зазор.
## Экран REGISTER_ACCOUNT_CONFIRM
Показывает предварительную проверку перед отправкой транзакции регистрации.
Отображается:
- повторный `login`;
- сообщение о том, что логин свободен или уже занят;
- баланс кошелька с проверкой порога `0.020 SOL`;
- имя homeserver;
- пометка, если используется стандартное значение `homeserver1`;
- отдельное предупреждение, если `Wi-Fi` не подключён.
Кнопки:
- `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`
- `BACK`
Поведение:
- если `Wi-Fi` не подключён, на экране прямо показывается уведомление об этом и кнопка регистрации недоступна;
- если `login` уже занят в `shine_users`, регистрация недоступна;
- если баланс меньше `0.020 SOL`, на экране показывается соответствующая ошибка;
- если все проверки пройдены, кнопка регистрации активна и запускает on-chain регистрацию.
## Экран REGISTER_ACCOUNT_RESULT
Показывает результат отправки регистрации.
Отображается:
- текст успеха или ошибки;
- подробное сообщение;
- при успехе краткий `user_pda`;
- при успехе краткий `tx signature`.
Кнопки:
- `BACK HOME`
- `ACCOUNT`
Поведение:
- после успешной регистрации данные `user_pda` и `tx signature` сохраняются в `NVS`;
- при ошибке на экране показывается причина отказа;
- если ошибку вернул `sendTransaction`, экран старается показать не только общий текст, но и детали `RPC`/preflight/simulate-логов.
## Экран HOMESERVER_PDA_CONFIRM
Показывает, что именно не так с homeserver-секцией уже существующей пользовательской `PDA`.
Отображается:
- причина (`homeserver` отсутствует в `PDA` или ключ не совпадает с локальным секретом);
- `login`;
- имя `homeserver`;
- короткое пояснение, что именно будет сделано.
Кнопки:
- `ADD HOMESERVER` или `FIX HOMESERVER PASSWORD`
- `BACK`
Поведение:
- если `Wi-Fi` не подключён, действие недоступно;
- экран не создаёт нового пользователя, а запускает `update_user_pda`;
- при `ADD HOMESERVER` в блок `sessions` добавляется запись `session_type=100`;
- при `FIX HOMESERVER PASSWORD` обновляется публичный ключ уже существующей записи `homeserver`.
## Экран HOMESERVER_PDA_RESULT
Показывает результат обновления `sessions` в пользовательской `PDA`.
Отображается:
- успех или ошибка;
- короткое сообщение;
- при успехе краткий `tx signature`.
Кнопки:
- `BACK HOME`
- `ACCOUNT`
Поведение:
- при ошибке текст ошибки сохраняется в ту же USB/NVS-диагностику, что и регистрация;
- после успешного обновления выполняется повторная проверка `PDA`, и основной экран должен перейти в состояние `ok`.
## Экран STATUS
Показывает сводку:
- логин;
- homeserver;
- есть ли секрет;
- зарегистрировано ли устройство;
- подключён ли Wi-Fi;
- доступны ли серверы;
- хватает ли баланса;
- находится ли устройство онлайн;
- краткий отпечаток `PDA` или `tx`.
Кнопки:
- `Назад`
- `Обновить статус`
`Обновить статус` в прототипе не делает сеть, а просто перерисовывает текущие вычисленные состояния.
## Экран CONNECTION
Показывает:
- `Wi-Fi`: готов / не готов;
- `Серверы`: готовы / не готовы;
- `Онлайн`: да / нет.
Кнопки:
- `Wi-Fi`
- `Серверы`
- `Подключить`
- `Отключить`
- `Назад`
Поведение:
- `Подключить` переводит устройство в `online=true`, если `Wi-Fi` реально подключён и серверы реально проверены;
- `Отключить` переводит устройство в `online=false`.
## Экран WIFI_EDIT
Поля:
- `SSID`
- `Пароль`
Кнопки:
- `Изменить SSID`
- `Изменить пароль`
- `Проверить`
- `Сбросить`
- `Назад`
Поведение:
- `Проверить` делает реальную попытку подключения к `Wi-Fi`;
- `Сбросить` очищает обе строки и ставит `wifiReady=false`.
## Экран SERVERS
Поля:
- `API URL`
- `RPC URL`
- `WS URL`
Кнопки:
- `Изменить API`
- `Изменить RPC`
- `Изменить WS`
- `Проверить`
- `Тестовые`
- `Назад`
Поведение:
- `Тестовые` подставляет дефолтные тестовые значения;
- `Проверить` делает реальные сетевые проверки:
- `API URL` должен отвечать по `HTTP/HTTPS`;
- `RPC URL` должен отвечать на `Solana JSON-RPC`;
- `WS URL` должен принимать `TCP/TLS`-соединение.
## Экран ACCOUNT
Показывает:
- логин;
- имя homeserver;
- статус секрета;
- короткий отпечаток секрета;
- статус регистрации;
- короткий отпечаток `PDA` или `tx`.
Кнопки:
- `Изменить логин`
- `Секрет`
- `Имя homeserver`
- `Сгенерировать`
- `Очистить`
- `Назад`
Поведение:
- `Сгенерировать` создаёт новый `master secret` и пересчитывает из него `device`-кошелёк;
- `Очистить` удаляет секрет, адрес кошелька, `PDA`, `tx`, регистрацию и онлайн-статус;
- логин приводится к нижнему регистру и trim.
- после успешной регистрации на экране сохраняются и отображаются краткие отпечатки `PDA` и `TX`.
## Экран WALLET
Показывает:
- кнопку `Wallet: <selected wallet name>`;
- строку текущего статуса/баланса активного кошелька;
- сокращённый публичный ключ активного кошелька.
Кнопки:
- `Wallet: <selected wallet name>`
- `SHOW BALANCE`
- `SHOW WALLET QR`
Поведение:
- верхняя кнопка открывает экран выбора кошелька `WALLET_SELECT`;
- `Показать баланс кошелька` читает реальный баланс именно активного выбранного кошелька из `Solana RPC`;
- `Показать QR-код кошелька` открывает экран `WALLET_QR`;
- этот экран не меняет логику Solana-регистрации пользователя: on-chain регистрация и проверки `PDA` по-прежнему завязаны на `client key`.
## Экран WALLET_SELECT
Показывает:
- строку `Current: <selected wallet name>`;
- три кнопки выбора:
- `ClientKey`
- `RootKey`
- `Custom` или `Custom: <имя>`;
- у текущего выбора видна галочка.
Поведение:
- `ClientKey` активирует кошелёк, выведенный из suffix `client.key`;
- `RootKey` активирует кошелёк, выведенный из suffix `root.key`;
- `Custom` использует derivation:
`sha256(base64(secret32) + "|wallet." + customName)`;
- если имя `custom`-кошелька ещё не задано, нажатие `Custom` открывает экран текстового ввода;
- если `custom` уже выбран, повторное нажатие на него открывает экран редактирования имени;
- после выбора кошелька устройство возвращается на экран `WALLET`.
## Экран WALLET_QR
Экран показывает:
- крупный реальный `QR` для строки `solana:<wallet_address>`;
- снизу крупный текст самого адреса кошелька.
Поведение:
- отдельная текстовая подсказка возврата не показывается;
- возврат на главный экран выполняется обычным тапом по экрану.
Показывает:
- QR-код для строки вида:
`solana:<wallet>`;
- мелкую подпись с полным адресом кошелька под QR.
Поведение:
- QR должен быть сканируемым, а не декоративным;
- адрес кошелька берётся из текущего активного выбранного кошелька;
- нажатие в любую точку экрана возвращает пользователя на `WALLET`.
## Экран WALLET_SIGN_REQUEST
Экран показывается, когда браузерное wallet-расширение присылает на ESP32 запрос `sign_transaction`.
Показывает:
- заголовок `SIGN REQUEST`;
- вопрос о подписи транзакции текущим активным кошельком;
- сокращённый публичный ключ кошелька;
- комментарий, присланный полем `comment`;
- две кнопки:
- `REJECT`
- `APPROVE`
Поведение:
- если пользователь нажимает `APPROVE`, ESP32 подписывает транзакцию текущим активным кошельком и отправляет ответ в расширение;
- если пользователь нажимает `REJECT`, ESP32 отправляет отказ `rejected_by_user`;
- пока запрос активен, свайпом этот экран не закрывается;
- после ответа расширению устройство возвращается на предыдущий экран.
## Экран REQUESTS
Экран доступен только если homeserver уже авторизован в SHiNE.
Показывает:
- слева сверху кнопку `REFRESH`;
- заголовок `REQUESTS:` немного правее стандартного левого положения;
- справа сверху только большую цифру числа активных заявок;
- ниже прокручиваемый список активных pairing-заявок;
- на экране одновременно видны примерно две плитки.
Каждая плитка показывает:
- код подключения из `10` цифр в виде `5` пар: `XX XX XX XX XX`;
- строку `Session: <platform/name>`;
- строку `Kind: Client session` или `Kind: Wallet session`.
Поведение:
- список берётся из живой операции `ListTrustedDeviceLoginRequests`;
- если заявок нет, экран показывает `No active requests`;
- отдельная строка вида `Active requests: N` на этом экране не показывается;
- вертикальный скролл позволяет просматривать все активные заявки;
- нажатие по плитке открывает `REQUEST_DETAIL`;
- свайп вправо возвращает в `SETTINGS`.
## Экран REQUEST_DETAIL
Показывает детали выбранной pairing-заявки:
- вопрос `Connect session ...?`;
- код подключения `XX XX XX XX XX`;
- строку `Session: <platform/name>`;
- строку `Kind: Client session` или `Kind: Wallet session`;
- пояснение:
- для client session: `Only client key will be transferred. No additional keys will be sent.`
- для wallet session: `No keys will be transferred.`
Кнопки:
- `YES`
- `NO`
Поведение:
- `YES` подтверждает заявку:
- для client session устройство передаёт только `client key`;
- для wallet session устройство выпускает отдельную `wallet-session` без передачи ключей;
- `NO` отклоняет заявку;
- после любого решения устройство возвращается в список `REQUESTS` и обновляет его;
- свайп вправо возвращает в `REQUESTS`.
## Экран SETTINGS
Показывает вертикальное меню крупных пунктов.
Если homeserver ещё не авторизован в SHiNE:
- `1. Wi-Fi`
- `2. Server`
- `3. Account`
Если homeserver уже авторизован:
- `1. Device requests`
- `2. Wi-Fi`
- `3. Server`
- `4. Account`
Поведение:
- одновременно видны только две карточки;
- список листается свайпом вверх/вниз;
- свайп вправо возвращает на `HOME`;
- пункт `Device requests` должен быть первым и появляется только для авторизованного homeserver.
## Экран PIN_EDIT
Используется для ввода нового PIN.
Правила:
- допустимы только цифры;
- длина PIN: 4..8 символов;
- после сохранения новый PIN немедленно пишется в `NVS`.
## Экран TEXT_INPUT
Это общий экран редактирования текстовых полей.
Используется для:
- `SSID`
- `Пароль Wi-Fi`
- `Логин`
- `Имя homeserver`
- `API URL`
- `RPC URL`
- `WS URL`
Состав экрана:
- заголовок поля;
- текущее значение;
- программная клавиатура;
- кнопки `Стереть`, `OK`, `Отмена`.
## Экран CONFIRM
Это модальный экран подтверждения.
Используется для:
- регистрации;
- очистки секрета;
- полного сброса.
Кнопки:
- `Подтвердить`
- `Отмена`
## Сценарий первой настройки
Ожидаемый путь пользователя:
1. открыть устройство сразу после старта; экран PIN временно не требуется;
2. открыть `Подключение -> Wi-Fi`;
3. ввести `SSID` и пароль, нажать `Проверить`;
4. открыть `Подключение -> Серверы`;
5. проверить или задать `SHiNE server login` (по умолчанию `shineupme`);
6. открыть `Аккаунт`;
7. ввести логин;
8. задать имя homeserver;
9. сгенерировать секрет;
10. открыть `Кошелёк`;
11. при необходимости пополнить баланс;
12. вернуться на `HOME`;
13. нажать `REGISTER ACCOUNT`;
14. на экране проверки ещё раз увидеть `login`, статус свободного `PDA`, баланс, `homeserver1`, серверный login и при необходимости сообщение о неподключённом `Wi-Fi`;
15. нажать `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`;
16. после завершения увидеть либо экран успеха с `user_pda` и `tx signature`, либо подробную ошибку;
17. после успешной регистрации увидеть статус `Homeserver активен`.
Примечание:
- устройство реально отправляет `create_user_pda` в `shine_users`, а после подтверждения сохраняет `PDA` и `tx signature`;
- при первой регистрации для обычного `user PDA` не заполняется `serverAddress`, а `accessServers` получает `shineupme` или другой выбранный `SHiNE server login`.
## Сценарий входящего запроса
1. открыть `Запросы`;
2. выбрать один из запросов;
3. прочитать детали;
4. нажать `Разрешить` или `Отклонить`;
5. убедиться, что статус запроса изменился.
## Временный режим текста
В текущей диагностической версии:
- строковые литералы экранного UI должны оставаться английскими ASCII-совместимыми;
- возвращение кириллицы допустимо только после отдельной доработки шрифтов и реальной проверки на устройстве;
- рендер выполняется стандартным шрифтом `Arduino_GFX`;
- это обходной режим, пока `U8g2`-шрифты на устройстве не начнут рисоваться стабильно.
## Критерии ручной проверки
Минимально нужно проверить:
1. устройство загружается и сразу открывает `HOME`; экран блокировки временно отключён;
2. весь экранный текст отображается читаемо на английском без замены символов;
3. ввод по экранной клавиатуре работает;
4. после перезагрузки сохранённые поля остаются в памяти;
5. секрет и адрес кошелька сохраняются на устройстве;
6. экран `QR и URI` рисует читаемый QR-код;
7. регистрация блокируется, пока условия не выполнены;
8. после выполнения условий регистрация становится доступной;
9. список запросов и экран подтверждения работают;
10. полный сброс действительно очищает сохранённое состояние.
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,71 @@
# Задание для Айдара: навести порядок в инструкциях агентов SHiNE
## Кратко
Нужно согласовать и оформить единый порядок инструкций для Codex/Telegram-агентов в проекте SHiNE, чтобы агенты стабильно понимали структуру проекта, границы ответственности и правила работы с сервером, UI, Solana-модулем, Telegram-ботом и игроками.
## Зачем это нужно
Сейчас проект состоит из нескольких связанных, но разных частей:
- основной сервер `SHiNE-server/`;
- UI `shine-UI/`;
- Solana/Anchor-модуль `shine-solana/shine/`;
- Telegram-агент-кодер `SHiNE-agent-bot-coder/`;
- TURN-сервер;
- документация `Dev_Docs/`;
- отдельные рабочие папки игроков `Players/`.
Без явных инструкций агент может путать эти зоны: например, смешать деплой Solana с деплоем сервера, изменить код от имени игрока, не обновить документацию API/DM/блокчейна или неправильно трактовать файл инструкций.
## Что предлагается сделать
1. Утвердить корневой `AGENTS.md` как главный набор правил проекта.
2. Проверить и при необходимости уточнить локальный `AGENTS.md` внутри `shine-solana/shine/`.
3. Оставить отдельные служебные инструкции Telegram-агента в `SHiNE-agent-bot-coder/AGENT.md`.
4. Оставить автоматически читаемые инструкции Telegram-агента в `SHiNE-agent-bot-coder/AGENTS.md`.
5. Явно закрепить режим игроков:
- игроки могут задавать вопросы, просить анализ, идеи и ТЗ;
- игроки не меняют код проекта напрямую;
- материалы игроков сохраняются только в `Players/<username>/`.
6. Зафиксировать правило: если пользователь говорит «агент MD» или похожую формулировку, считать, что речь про автоматически читаемый `AGENTS.md`.
7. Добавить простой процесс согласования изменений инструкций:
- Дима или другой участник готовит предложение;
- Айдар получает уведомление/заявку;
- Айдар отвечает: одобрить, отклонить или попросить доработать;
- только после одобрения агент вносит изменения в проектные инструкции.
## Предлагаемая логика уведомления Айдару
Минимальный вариант без сложной разработки:
1. Агент готовит текст заявки.
2. Текст отправляется Айдару в Telegram или в общий рабочий чат.
3. В заявке явно указаны варианты ответа:
- `одобрить`;
- `отклонить`;
- `доработать: ...`.
4. После ответа Айдара агент либо выполняет согласованные правки, либо фиксирует, что задача отклонена/нужна доработка.
Более удобный вариант на будущее:
- добавить в Telegram-бота команду или сценарий согласования задач, например:
- `/approve <id>`;
- `/reject <id> причина`;
- `/revise <id> комментарий`.
Но для начала достаточно простого текстового согласования через Telegram.
## Что нужно от Айдара
Подтвердить, что такой порядок подходит:
1. Корневой `AGENTS.md` остается главным правилом проекта.
2. Для Solana, Telegram-агента и игроков сохраняются отдельные локальные правила.
3. Игроки не меняют код напрямую, а готовят материалы и предложения.
4. Изменения инструкций выполняются только после явного одобрения Айдара.
5. Уведомления Айдару на первом этапе можно делать простым текстом в Telegram, без отдельной сложной системы заявок.
## Ожидаемый результат
После одобрения:
- агенты будут стабильнее понимать границы проекта;
- снизится риск случайных изменений не в той части системы;
- появится понятный порядок согласования задач от игроков;
- Айдар будет явно контролировать изменения в инструкциях и правилах работы агентов.
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+7
View File
@@ -0,0 +1,7 @@
# SHiNE
## План запуска DAO
План запуска DAO зафиксирован в [DAO_запуск/README.md](DAO_запуск/README.md).
Это рабочий список задач по `этап1` и `этап2`. Дальше ведём его как основной чек-лист запуска DAO и отмечаем в нём выполненные пункты по мере готовности.
+19
View File
@@ -0,0 +1,19 @@
TELEGRAM_BOT_TOKEN=replace_me
OPENAI_API_KEY=replace_me
ALLOWED_TELEGRAM_USERNAME=AidarKC
ALLOWED_TELEGRAM_PLAYERS=malvviiina:Милана,zodiaktechnika32:Сергей,oidasyda:Иван,blackbyrd1:Ворон,dimasol1:Дима
ALLOWED_TELEGRAM_CHANNEL_USERNAME=shine_writing
BOT_USERNAME=aidar_su_bot
OPENAI_TRANSCRIBE_MODEL=gpt-4o-mini-transcribe
TELEGRAM_FILE_DOWNLOAD_TIMEOUT_SECONDS=300
OPENAI_TRANSCRIBE_TIMEOUT_SECONDS=900
OPENAI_TTS_MODEL=gpt-4o-mini-tts
OPENAI_TTS_VOICE=alloy
OPENAI_TTS_RESPONSE_FORMAT=opus
OPENAI_TTS_TIMEOUT_SECONDS=180
OPENAI_TTS_CHUNK_CHARS=3500
CODEX_BIN=/home/ai/.cache/JetBrains/IntelliJIdea2026.1/aia/codex/bin/codex-x86_64-unknown-linux-musl
CODEX_WORKDIR=/home/ai/work/SHiNE/SHiNE-server-sha256
CODEX_TIMEOUT_SECONDS=900
MAX_RETRIES=3
DATA_DIR=./data
+5
View File
@@ -0,0 +1,5 @@
.env
data/
logs/
run/
__pycache__/
+81
View File
@@ -0,0 +1,81 @@
# AGENT.md для SHiNE-agent-bot-coder
Ты запущен как обработчик входящего Telegram-сообщения от пользователя.
## Контекст
- `SHiNE-agent-bot-coder` — локальный Telegram-бот-сервис агента-кодера для работы с этим проектом.
- Сервис принимает входящие сообщения от пользователя Telegram, сохраняет историю, ставит задачи в очередь и последовательно запускает Codex CLI в рабочем проекте.
- Текстовые сообщения обрабатываются напрямую, voice/audio сначала распознаются через OpenAI transcription, затем передаются как текстовая задача.
- История диалога хранится в JSONL-файле, путь передаётся в промпте.
- Сообщение может быть текстом или результатом распознавания голосового.
- Ответ пойдёт пользователю в Telegram как обычное текстовое сообщение.
- Единственная рабочая реализация сервиса — Python-скрипт `py_bot_service.py`; старая Java-реализация удалена как нерабочая и не должна восстанавливаться без отдельного решения Айдара.
- В репозитории также есть отдельный Solana/Anchor-модуль `shine-solana/shine/`; он логически связан с SHiNE, но не должен автоматически подключаться к основному серверному deploy без отдельной команды.
- Перед изменениями внутри `shine-solana/shine/` читать локальные инструкции `shine-solana/shine/AGENTS.md`; в git не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги.
## Авторитет команд и история
- Основной пользователь и источник команд — Айдар: `@AidarKC` / `@aidarkc`.
- Дополнительно разрешены игроки из whitelist (`ALLOWED_TELEGRAM_PLAYERS`), каждый со своей отдельной историей и рабочей папкой `Players/<username>/`.
- Игроки работают в режиме вопросов/анализа/подготовки материалов: в промпте явно задано правило не менять код проекта и писать материалы только в своей папке.
- Для неизвестных пользователей в личном чате сервис отвечает вежливым отказом.
- В Telegram-канале/группе `@shine_writing` сервис выполняет сообщения только от Айдара, а ответы отправляет в тот же чат.
- Если Telegram сообщает о миграции обычной группы в supergroup, сервис должен запомнить новый `chat_id` и отправлять ответы уже туда.
- На события подключения/отключения пользователей (join/leave) сервис не отвечает и ничего не отправляет.
## Очередь и состояние
- Входящие задачи записываются в файловую очередь и обрабатываются строго по одной, чтобы не смешивать изменения в проекте.
- Сервис ведёт состояние активной задачи и текущего файла истории, а после рестарта продолжает незавершённую обработку с учётом сохранённого состояния.
- Истории диалогов хранятся в JSONL по каждому разрешённому username отдельно: `data/history/<username>/`.
- Архив истории после `/new`: `data/history/<username>/archive/`.
- После `/new` для этого же пользователя должен сбрасываться и контекст продолжения Codex-сессии; следующий запрос запускается как новая сессия, не через resume.
- Для просмотра истории игрока открывать файлы в его папке истории по username.
- Дедупликация входящих Telegram update нужна, чтобы одно сообщение не попало в обработку повторно.
- Если Codex молчит во время активной задачи 2 минуты подряд, сервис отправляет аварийный статус с общим временем работы задачи; при дальнейшем молчании повторяет статус каждые 2 минуты.
- После успешной обработки задачи из личного чата Айдара сервис должен отправить публичный итоговый отчёт в группу `@shine_writing`: первым сообщением исходный запрос, вторым сообщением-ответом итоговый ответ Codex. Промежуточные статусы в группу не дублировать.
- Для приватных voice/audio-запросов в публичном отчёте первым сообщением отправлять исходный Telegram voice/audio-файл с подписью, где указан распознанный текст. В пользовательском тексте отчёта не показывать Telegram `file_id`.
- Озвучивание финальных ответов настраивается персонально для каждого Telegram-пользователя командами `/voice_on`, `/voice_off`; для новых пользователей оно включено по умолчанию.
- Адаптация текста перед озвучкой настраивается персонально командами `/voice_rewrite_on`, `/voice_rewrite_off`. Если она включена, сервис перед TTS вызывает дешёвую текстовую модель OpenAI и делает голосовую версию без длинных хэшей, путей, команд и технического шума, сохраняя смысл и порядок исходного ответа.
- Режим личных ответов настраивается персонально командами `/single_message_on`, `/single_message_off`: либо одно редактируемое сообщение по этапам, либо отдельные сообщения как раньше.
- Команда `/settings` должна сразу показывать текущее состояние всех персональных настроек пользователя и список команд для их изменения.
- Если озвучивание включено, после полного текстового финального ответа сервис дополнительно отправляет voice-файл с синтезированной речью через OpenAI TTS даже для текстовых запросов. Voice отправляется в исходный чат, а также в известный личный чат пользователя и в общий чат `@shine_writing`, если они отличаются и доступны. Промежуточные статусы не озвучивать.
- Команда `/status` должна показывать состояние очереди и персональные настройки: voice-ответы, адаптацию текста перед озвучкой и режим одного сообщения в личке.
## Правила голосовой версии ответа
- Текстовый финальный ответ должен оставаться полноценным: в нём можно указывать команды, пути, хэши коммитов, номера версий, результаты проверок и другие технические детали.
- Голосовую версию финального ответа нужно делать короче и проще для восприятия на слух. Основной механизм — персонально включаемая адаптация текста через дополнительный OpenAI-вызов перед TTS.
- В голосовой версии не зачитывать длинные хэши коммитов, токены, file_id, длинные команды, полные пути и другие строки, которые человек всё равно не сможет надёжно запомнить на слух.
- Для commit/push в голосовой версии достаточно сказать краткий итог: что коммит сделан, что именно изменено, проверки прошли без ошибок, push выполнен, рабочее дерево чистое.
- Если пользователю нужны точные команды, хэши или подробности, они должны оставаться в текстовом ответе.
## Планы и отложенные фичи
- Планы проекта по отложенным фичам хранятся в `TODO/`.
- Внутри есть три горизонта:
- `near/` - ближайшие планы, обычно сегодня/завтра;
- `medium/` - среднесрочные планы, обычно недели или 1-2 месяца;
- `far/` - дальнее будущее без понятного срока.
- Если пользователь спрашивает, какие есть планы или что можно продолжить, нужно смотреть эти три папки и отвечать кратким списком по горизонтам.
- Файлы из `TODO/` не начинать реализовывать без явной команды пользователя.
- После реализации фичи, требующей ручной проверки, нужно добавить отдельный файл в `Dev_Docs/Pending_Features/`.
## Центр задач и предложений
- Сервис хранит простые задачи и предложения в `data/task_center/items.json`.
- Айдар может смотреть список через `/tasks` или естественные фразы вроде «покажи мои задачи», «покажи задачи Миланы».
- Айдар может ставить задачи игрокам фразой вида «поставь задачу Милане: ...».
- Игроки могут отправлять предложения Айдару фразой вида `предложение: ...`, `идея: ...` или `заявка: ...`.
- Статусы меняются фразами с ID: `одобрить TC-0001`, `отклонить TC-0001`, `доработать TC-0001`, `закрыть TC-0001`.
- После финального ответа в личном чате сервис добавляет короткое напоминание, если у пользователя есть активные задачи или предложения.
## Локальный запуск и systemd
- Основной запуск сервиса выполняется Python-скриптом `py_bot_service.py` из папки `SHiNE-agent-bot-coder/`.
- Локальные секреты и параметры должны храниться в `.env`, этот файл не коммитится.
- Для проверки Codex без Telegram можно использовать self-test режим сервиса.
- Для постоянного локального запуска используется user-level systemd service `shine-agent-bot-coder`; скрипты установки лежат в `SHiNE-agent-bot-coder/scripts/systemd/`.
- Если меняется логика сервиса, после изменений нужно проверить запуск локально и при необходимости перезапустить user systemd service.
- Команда Telegram `/restart` (`/restart_service`) доступна только Айдару и выполняет отложенный рестарт после текущей задачи, до взятия следующей. Аварийный жёсткий рестарт доступен только Айдару командами `/restart_hard`, `/restart_now`, `/restart_force`.
## Правила ответа
- Пиши содержательно и коротко.
- Не упоминай внутренние служебные детали, файловую систему и технические логи.
- Если запрос требует действий с кодом/проектом, выполняй их в рабочей директории.
- Если для ответа данных недостаточно, задай ровно один уточняющий вопрос.
- Если была ошибка предыдущего запуска, в промпте будет пометка retry — учти это и продолжи с учётом текущего состояния проекта.
+25
View File
@@ -0,0 +1,25 @@
# AGENTS
## Назначение
- Это автоматически читаемые инструкции Codex для папки `SHiNE-agent-bot-coder/`.
- `SHiNE-agent-bot-coder` — локальный Telegram-бот-сервис агента-кодера для работы с проектом SHiNE.
- Если пользователь говорит «агент MD», «агент с MD» или похожим образом про файл инструкций Codex, считать, что имеется в виду `AGENTS.md`.
## Связанные инструкции
- Подробные служебные правила Telegram-обработчика лежат в `AGENT.md`.
- `AGENT.md` используется самим сервисом как файл инструкций, который передаётся в промпт обработчика входящих Telegram-сообщений.
- При изменении логики сервиса сначала читать `AGENT.md`, затем код `py_bot_service.py`.
## Планы и задачи
- Отложенные задачи проекта лежат в `../TODO/`.
- Точка входа по планам: `../TODO/README.md`.
- Горизонты планов:
- `near/` - ближайшие планы;
- `medium/` - среднесрочные планы;
- `far/` - дальнее будущее.
- Если пользователь спрашивает, какие есть планы или что можно продолжить, кратко перечислять задачи по этим горизонтам.
- Не начинать реализацию задач из `TODO` без явной команды пользователя.
## Проверка после изменений
- Если меняется логика Telegram-бота, проверить локальный запуск или self-test, когда это уместно.
- Если меняется только документация или инструкции, достаточно проверить, что ссылки на документы актуальны.
+2
View File
@@ -0,0 +1,2 @@
@AGENTS.md
@AGENT.md
@@ -0,0 +1,26 @@
# Промпты для режима игроков (на согласование)
## 1) Базовый служебный промпт (добавка к задаче игрока)
```text
Режим игрока (обязательно):
- Пользователь: <Имя> (@<username>).
- Рабочая папка игрока: <project>/Players/<username>
- Код проекта не изменять.
- Можно отвечать на вопросы по проекту, предлагать идеи и готовить ТЗ.
- Если нужны правки кода, описывать предложение текстом и сохранять материалы только в папке игрока.
```
## 2) Приветственное сообщение игроку (один раз)
```text
Привет, <Имя>.
Можно задавать вопросы по проекту, просить анализ, идеи и подготовку готового ТЗ.
Команда /new начинает новую сессию и архивирует текущую историю.
```
## 3) Отказ неизвестному пользователю
```text
Извините, доступ к этому агенту пока не выдан. Обратитесь к Айдару.
```
+100
View File
@@ -0,0 +1,100 @@
# SHiNE-agent-bot-coder
Локальный Telegram-бот-сервис для пользователя `ai`:
- принимает сообщения от `@AidarKC`;
- поддерживает whitelist игроков (`ALLOWED_TELEGRAM_PLAYERS`) с отдельными историями;
- ведёт историю диалога в `JSONL`;
- ставит задачи в файловую очередь;
- обрабатывает задачи строго последовательно;
- поддерживает текстовые и голосовые сообщения (voice/audio через OpenAI transcription);
- вызывает Codex CLI и отправляет ответ в Telegram;
- в личном чате умеет работать в двух персонально переключаемых режимах: через одно редактируемое статусное сообщение или через отдельные сообщения по этапам;
- умеет персонально для каждого пользователя озвучивать финальный ответ через OpenAI TTS;
- при рестарте восстанавливает незавершённые задачи;
- отправляет аварийный статус только если Codex молчит 2 минуты подряд во время активной задачи;
- принимает сообщения из канала/группы `@shine_writing`, выполняет команды только от `@AidarKC`;
- учитывает миграцию обычной Telegram-группы в supergroup и перенаправляет ответы на новый `chat_id`.
Рабочая реализация сервиса — только `py_bot_service.py`. Старая Java-реализация удалена, потому что не заработала и больше не используется.
## Структура
- `.env` — локальные секреты и параметры запуска (не коммитится);
- `data/py_queue.jsonl` — очередь Python-сервиса;
- `data/py_state.json` — текущее состояние Python-сервиса;
- `data/py_processed_updates.log` — дедуп входящих update;
- `data/history/<username>/*.jsonl` — активные истории по пользователям;
- `data/history/<username>/archive/*.jsonl` — архивы после `/new`.
## Локальный запуск
1. Скопировать пример:
- `cp .env.example .env`
2. Заполнить секреты в `.env`.
- `TELEGRAM_BOT_TOKEN` — токен рабочего Telegram-бота.
- `ALLOWED_TELEGRAM_USERNAME` — пользователь, чьи сообщения выполняются как команды.
- `ALLOWED_TELEGRAM_PLAYERS` — whitelist игроков в формате `username:Имя,username2:Имя2`.
- `ALLOWED_TELEGRAM_CHANNEL_USERNAME` — канал, из которого принимаются `channel_post`; обычные group/supergroup-сообщения обрабатываются как `message`.
- `TELEGRAM_API_BASE_URL` — базовый URL Bot API; по умолчанию `https://api.telegram.org`. Для очень больших voice/audio можно поднять локальный `telegram-bot-api` и направить бота туда.
- `TELEGRAM_FILE_DOWNLOAD_TIMEOUT_SECONDS` — тайм-аут скачивания voice/audio из Telegram, по умолчанию 300 секунд.
- `OPENAI_TRANSCRIBE_TIMEOUT_SECONDS` — тайм-аут распознавания voice/audio в OpenAI, по умолчанию 900 секунд.
- `OPENAI_TRANSCRIBE_MAX_UPLOAD_BYTES` — безопасный лимит размера одного куска для OpenAI transcription, по умолчанию `24 MiB`.
- `OPENAI_TRANSCRIBE_MAX_CHUNK_SECONDS` — максимальная длина одного куска при длинном аудио, по умолчанию `900` секунд.
- `OPENAI_TRANSCRIBE_OVERLAP_SECONDS` — перекрытие соседних кусков для более ровной склейки текста, по умолчанию `2` секунды.
- `OPENAI_TRANSCRIBE_REENCODE_BITRATE_KBPS` — битрейт локального пережатия длинного аудио через `ffmpeg`, по умолчанию `24`.
- `OPENAI_TRANSCRIBE_FFMPEG_TIMEOUT_SECONDS` — тайм-аут локальной обработки длинного аудио через `ffmpeg`/`ffprobe`, по умолчанию `1800`.
- `FFMPEG_BIN` и `FFPROBE_BIN` — пути к локальным бинарям `ffmpeg`/`ffprobe`, если они не лежат в `PATH`.
- `OPENAI_TTS_MODEL` — модель синтеза речи, по умолчанию `gpt-4o-mini-tts`.
- `OPENAI_TTS_VOICE` — голос синтеза речи, по умолчанию `alloy`.
- `OPENAI_TTS_RESPONSE_FORMAT` — аудиоформат для Telegram voice, по умолчанию `opus`.
- `OPENAI_TTS_TIMEOUT_SECONDS` — тайм-аут генерации одного фрагмента речи, по умолчанию 180 секунд.
- `OPENAI_TTS_CHUNK_CHARS` — максимальный размер одного фрагмента озвучки, по умолчанию 3500 символов.
3. Запуск:
- `python3 SHiNE-agent-bot-coder/py_bot_service.py`
## Быстрый self-test Codex (без Telegram)
```bash
python3 SHiNE-agent-bot-coder/py_bot_service.py --selftest-codex "Ответь одной строкой: Codex работает"
```
## Длинные voice/audio
- Если аудио короткое, бот отправляет его в OpenAI как раньше.
- Если аудио большое или длинное, бот локально пережимает его через `ffmpeg`, при необходимости режет на куски и распознаёт последовательно.
- Если Telegram заранее сообщает большой размер файла, бот больше не отказывается сразу: сначала явно пишет, что пробует скачать файл, затем отдельно сообщает, удалось ли скачивание, и только после успешной загрузки переходит к подготовке аудио и OpenAI.
- Для очень больших файлов упираемся не только в OpenAI, но и в лимит обычного облачного Telegram Bot API на скачивание файла ботом. Для таких случаев нужно использовать локальный `telegram-bot-api` сервер и указать его через `TELEGRAM_API_BASE_URL`.
## Статусы в личке
- Для `private`-чата бот поддерживает персональную настройку режима ответа.
- По умолчанию он старается не засорять переписку промежуточными сообщениями: создаёт одно статусное сообщение и редактирует его по этапам.
- Если включить `/single_message_off`, бот возвращается к старому режиму и отправляет отдельные сообщения по этапам и финальный ответ отдельно.
- Если финальный текст в режиме одного сообщения не помещается целиком, бот оставляет первую часть в отредактированном статусном сообщении и отправляет максимум ещё одно дополнительное текстовое сообщение с хвостом ответа.
- Голосовой ответ, если он включён, всегда приходит отдельным новым сообщением.
## Запуск как systemd-сервис
Файлы для установки:
- `scripts/systemd/shine-agent-bot-coder.service`
- `scripts/systemd/install-local-systemd.sh`
Установка:
- `bash SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh`
Проверка:
- `systemctl --user status shine-agent-bot-coder --no-pager`
- `journalctl --user -u shine-agent-bot-coder -f`
Перезапуск после изменений:
- `systemctl --user restart shine-agent-bot-coder`
## Telegram-команды
- `/status` — активная задача и размер очереди.
- `/settings` — текущие пользовательские настройки и команды для их изменения.
- `/queue` — список задач в очереди.
- `/stop` — остановить текущую задачу.
- `/cancel <id|all>` — удалить задачу по id/префиксу или очистить очередь.
- `/new` — архивировать текущую историю, сбросить продолжение Codex-сессии для этого пользователя и начать новый диалог.
- `/voice_on` — включить озвучивание финальных ответов для текущего пользователя.
- `/voice_off` — выключить озвучивание финальных ответов для текущего пользователя.
- `/voice_rewrite_on` — включить адаптацию текста перед озвучкой.
- `/voice_rewrite_off` — выключить адаптацию текста перед озвучкой.
- `/single_message_on` — вести ответ в личке через одно редактируемое сообщение.
- `/single_message_off` — слать отдельные сообщения по этапам и отдельный финальный ответ.
- `/restart` или `/restart_service` — отложенный рестарт после текущей задачи, до взятия следующей (только для Айдара).
- `/restart_hard` — жёсткий рестарт прямо сейчас (только для Айдара).
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="/home/ai/work/SHiNE/SHiNE-server-sha256"
SERVICE_DIR="${ROOT_DIR}/SHiNE-agent-bot-coder"
UNIT_SRC="${SERVICE_DIR}/scripts/systemd/shine-agent-bot-coder.service"
UNIT_DST="${HOME}/.config/systemd/user/shine-agent-bot-coder.service"
echo "[1/6] Проверка python3..."
command -v python3 >/dev/null 2>&1 || { echo "python3 не найден"; exit 1; }
echo "[2/6] Подготовка папки логов..."
mkdir -p "${SERVICE_DIR}/logs"
echo "[3/6] Копирование user systemd unit..."
mkdir -p "$(dirname "${UNIT_DST}")"
cp "${UNIT_SRC}" "${UNIT_DST}"
echo "[4/6] daemon-reload..."
systemctl --user daemon-reload
echo "[5/6] enable + start..."
systemctl --user enable --now shine-agent-bot-coder
echo "[6/6] Статус:"
systemctl --user status shine-agent-bot-coder --no-pager
echo "Готово. Логи: journalctl --user -u shine-agent-bot-coder -f"
@@ -0,0 +1,19 @@
[Unit]
Description=SHiNE Agent Bot Coder (Telegram + Codex queue worker)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/ai/work/SHiNE/SHiNE-server-sha256/SHiNE-agent-bot-coder
EnvironmentFile=/home/ai/work/SHiNE/SHiNE-server-sha256/SHiNE-agent-bot-coder/.env
ExecStart=/usr/bin/python3 /home/ai/work/SHiNE/SHiNE-server-sha256/SHiNE-agent-bot-coder/py_bot_service.py
Restart=always
RestartSec=5
TimeoutStopSec=20
SuccessExitStatus=143 0
StandardOutput=append:/home/ai/work/SHiNE/SHiNE-server-sha256/SHiNE-agent-bot-coder/logs/service.log
StandardError=append:/home/ai/work/SHiNE/SHiNE-server-sha256/SHiNE-agent-bot-coder/logs/service.log
[Install]
WantedBy=default.target
+44
View File
@@ -0,0 +1,44 @@
.gradle
build/
node_modules/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+1
View File
@@ -0,0 +1 @@
ESP-wallet
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
<option name="myGradleHome" value="" />
</GradleProjectSettings>
</option>
</component>
</project>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+45
View File
@@ -0,0 +1,45 @@
# SHiNE Browser Plugin Wallet
Chrome-compatible Manifest V3 plugin for SHiNE wallet-session login.
## Что уже умеет
- создать `wallet-session` через `StartTrustedDeviceLogin`;
- показать код подключения;
- дождаться подтверждения на доверенном устройстве;
- принять `session-only` payload без передачи `clientKey/rootKey/blockchainKey`;
- сохранить `sessionPriv/sessionKey/sessionId` в локальном хранилище plugin;
- восстанавливать session через `SessionChallenge -> SessionLogin`;
- держать wallet-state в `background service worker`, а side panel использовать как UI.
- принимать не адрес сервера, а логин серверного аккаунта SHiNE и находить точный `https://...` / `wss://...` адрес через его PDA.
- внедрять legacy `window.solana` / `window.phantom.solana` provider для сайтов.
- регистрировать кошелёк как `Wallet Standard` wallet для dapp, которые ищут стандартные кошельки.
## Как загрузить локально
1. Открой `chrome://extensions/`
2. Включи `Developer mode`
3. Нажми `Load unpacked`
4. Выбери папку `SHiNE-browser-plugin-wallet/`
5. Нажми на иконку расширения в toolbar: откроется side panel SHiNE Wallet
## Ограничения текущего этапа
- plugin пока не держит постоянный фоновый WS-канал без активного действия пользователя, но хранит wallet-state в `background`;
- на этом этапе реализован только `session-only login`;
- запросы на подпись будут следующим этапом.
- pairing-пароль, если он используется, должен генерироваться в формате `sha256$<hex>` от строки `shine-pairing|loginLower|password`.
- сторона side panel в Chromium выбирается самим браузером/пользователем; extension не закрепляет панель принудительно слева.
- для совместимости с некоторыми dapp расширение одновременно держит и legacy provider, и Wallet Standard регистрацию.
## Сборка crypto bundle
Для обычной загрузки plugin это не нужно: bundled crypto-файл уже лежит в репозитории.
Если понадобится пересобрать локальный crypto bundle:
```bash
npm install
npx esbuild js/lib/vendor/noble-ed25519-entry.js --bundle --format=esm --platform=browser --outfile=js/lib/vendor/noble-ed25519-bundle.js
npx esbuild js/lib/vendor/solana-publickey-entry.js --bundle --format=esm --platform=browser --outfile=js/lib/vendor/solana-publickey-bundle.js
```
+958
View File
@@ -0,0 +1,958 @@
import { createRequesterPairingMaterial, decryptPairingPayloadFromEnvelope, deriveEspPairingPasswordHash } from './js/lib/device-pairing.js';
import { loadPluginSettings, loadSessionMaterial, savePluginSettings, saveSessionMaterial, clearSessionMaterial } from './js/lib/session-store.js';
import { ShineApiClient } from './js/lib/shine-api.js';
import {
DEFAULT_SHINE_SERVER_LOGIN,
buildHttpBase,
readWalletProfileByLogin,
resolveShineServerByUserLogin,
} from './js/lib/shine-server-resolver.js';
const state = {
api: null,
settings: {
serverLogin: DEFAULT_SHINE_SERVER_LOGIN,
serverHttp: buildHttpBase('shineup.me'),
serverUrl: 'wss://shineup.me/ws',
login: '',
},
requesterMaterial: null,
pairingId: '',
expiresAtMs: 0,
shortCode: '',
trustedSessionOnline: false,
pollTimer: 0,
activeSession: null,
connectionOnline: false,
walletProfile: null,
signing: {
selectedDeviceName: '',
devicesResolvedAtMs: 0,
},
currentWallet: null,
pendingApprovals: [],
siteApprovalChain: Promise.resolve(),
sessionAttachInProgress: false,
statusText: '',
statusKind: 'info',
};
const WALLET_RPC_REQUEST_TYPE = 9100;
const WALLET_RPC_RESPONSE_TYPE = 9101;
async function configureSidePanelBehavior() {
if (!chrome.sidePanel?.setPanelBehavior) {
return;
}
try {
await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
} catch (error) {
console.warn('Failed to configure SHiNE side panel behavior:', error);
}
}
function normalizeOrigin(value = '') {
const raw = String(value || '').trim();
if (!raw) return '';
try {
return new URL(raw).origin;
} catch {
return raw;
}
}
function makeCodeError(message, code) {
const error = new Error(String(message || 'Wallet error'));
error.code = String(code || '').trim().toUpperCase();
return error;
}
function setStatus(message = '', kind = 'info') {
state.statusText = String(message || '');
state.statusKind = kind === 'error' ? 'error' : 'info';
}
function makePendingApprovalSnapshot(payload = {}) {
const pendingId = String(payload?.id || '').trim();
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals : [];
const queueIndex = pendingApprovals.findIndex((item) => String(item?.id || '').trim() === pendingId);
const queueLength = pendingApprovals.length;
return {
id: pendingId,
kind: String(payload?.kind || 'sign_transaction').trim() || 'sign_transaction',
origin: String(payload?.origin || '').trim(),
publicKeyBase58: String(payload?.publicKeyBase58 || '').trim(),
comment: String(payload?.comment || '').trim(),
createdAtMs: Number(payload?.createdAtMs || Date.now()),
status: String(payload?.status || 'queued').trim() || 'queued',
queuePosition: queueIndex >= 0 ? queueIndex + 1 : 1,
queueLength: queueLength || 1,
transactionSummary: payload?.transactionSummary && typeof payload.transactionSummary === 'object'
? { ...payload.transactionSummary }
: null,
};
}
function getCurrentPendingApproval() {
return Array.isArray(state.pendingApprovals) && state.pendingApprovals.length
? state.pendingApprovals[0]
: null;
}
function removePendingApproval(pendingId, { rejectError = null } = {}) {
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals : [];
const index = pendingApprovals.findIndex((item) => String(item?.id || '').trim() === String(pendingId || '').trim());
if (index < 0) return;
const [pending] = pendingApprovals.splice(index, 1);
if (pending.timeoutId) {
clearTimeout(pending.timeoutId);
}
if (rejectError && pending.abortController) {
try {
pending.abortController.abort(rejectError);
} catch {}
}
}
async function openSidePanelForSender(sender) {
if (!chrome.sidePanel?.open || !sender?.tab?.id) return;
try {
await chrome.sidePanel.open({ tabId: sender.tab.id });
} catch {}
}
function stopPoll() {
if (state.pollTimer) {
clearTimeout(state.pollTimer);
state.pollTimer = 0;
}
}
function clearPairingState() {
stopPoll();
state.requesterMaterial = null;
state.pairingId = '';
state.expiresAtMs = 0;
state.shortCode = '';
state.trustedSessionOnline = false;
}
function ensureApi(serverUrl = state.settings.serverUrl) {
const normalized = String(serverUrl || '').trim() || 'wss://shineup.me/ws';
if (!state.api || state.api.serverUrl !== normalized) {
state.api?.close();
state.api = new ShineApiClient(normalized);
}
return state.api;
}
async function loadStateFromStorage() {
const settings = await loadPluginSettings();
state.settings = {
serverLogin: String(settings?.serverLogin || state.settings.serverLogin || DEFAULT_SHINE_SERVER_LOGIN).trim(),
serverHttp: String(settings?.serverHttp || state.settings.serverHttp || buildHttpBase('shineup.me')).trim() || buildHttpBase('shineup.me'),
serverUrl: String(settings?.serverUrl || state.settings.serverUrl || 'wss://shineup.me/ws').trim() || 'wss://shineup.me/ws',
login: String(settings?.login || '').trim(),
connectedOrigins: Array.isArray(settings?.connectedOrigins) ? settings.connectedOrigins.map((item) => normalizeOrigin(item)).filter(Boolean) : [],
};
const storedSession = await loadSessionMaterial();
if (storedSession || !state.sessionAttachInProgress) {
state.activeSession = storedSession;
}
state.walletProfile = state.activeSession?.walletProfile || null;
state.signing = {
selectedDeviceName: String(state.activeSession?.selectedDeviceName || ''),
devicesResolvedAtMs: Number(state.activeSession?.devicesResolvedAtMs || 0),
};
state.currentWallet = state.activeSession?.currentWallet || null;
}
async function persistSettings(nextSettings = {}) {
state.settings = {
...state.settings,
...nextSettings,
};
if (!Array.isArray(state.settings.connectedOrigins)) {
state.settings.connectedOrigins = [];
}
await savePluginSettings(state.settings);
return state.settings;
}
function isOriginApproved(origin) {
const normalized = normalizeOrigin(origin);
return !!normalized && Array.isArray(state.settings.connectedOrigins) && state.settings.connectedOrigins.includes(normalized);
}
async function setOriginApproved(origin, approved) {
const normalized = normalizeOrigin(origin);
const current = new Set(Array.isArray(state.settings.connectedOrigins) ? state.settings.connectedOrigins : []);
if (approved) {
if (normalized) current.add(normalized);
} else if (normalized) {
current.delete(normalized);
}
await persistSettings({ connectedOrigins: [...current] });
}
async function resolveServerForLogin(login) {
const cleanLogin = String(login || state.settings.login || '').trim();
if (!cleanLogin) {
state.settings = {
...state.settings,
login: '',
serverLogin: '',
};
await savePluginSettings(state.settings);
return { ok: true, resolved: false };
}
const resolved = await resolveShineServerByUserLogin(cleanLogin);
state.settings = {
...state.settings,
login: cleanLogin,
serverLogin: resolved.serverLogin,
serverHttp: resolved.serverHttp,
serverUrl: resolved.serverUrl,
};
await savePluginSettings(state.settings);
return { ok: true, resolved: true, ...resolved };
}
async function saveActiveSessionRecord() {
if (!state.activeSession) return;
const nextRecord = {
...state.activeSession,
walletProfile: state.walletProfile,
selectedDeviceName: state.signing.selectedDeviceName,
devicesResolvedAtMs: state.signing.devicesResolvedAtMs,
currentWallet: state.currentWallet,
};
state.activeSession = nextRecord;
await saveSessionMaterial(nextRecord);
}
function shortKey(value = '', size = 10) {
const raw = String(value || '').trim();
return raw ? raw.slice(0, size) : '';
}
function extractErrorCode(message = '') {
const match = String(message || '').match(/\(([A-Z0-9_]+)\)\s*$/i);
return match ? String(match[1]).toUpperCase() : '';
}
function toWalletErrorMessage(error, fallback = 'Не удалось выполнить операцию кошелька.') {
const raw = String(error?.message || '').trim();
const code = String(error?.code || extractErrorCode(raw) || '').toUpperCase();
if (code === 'PAIRING_NOT_AVAILABLE') {
return 'Для этого логина ещё не включено подключение по коду. На доверенном устройстве откройте «Подключить по коду» и нажмите «Включить подключение по коду» или задайте дополнительный пароль.';
}
if (code === 'PAIRING_NO_TRUSTED_SESSION_ONLINE') {
return 'Сейчас нет ни одной онлайн доверенной сессии этого пользователя. Откройте SHiNE на другом уже подключённом устройстве и держите его в сети.';
}
if (code === 'PAIRING_PASSWORD_INVALID') {
return 'Дополнительный пароль подключения не подходит.';
}
return raw || fallback;
}
function homeserverSessionNameFromClientInfo(value = '') {
const raw = String(value || '').trim();
const match = raw.match(/^ESP32 homeserver:(.+)$/i);
return match ? String(match[1] || '').trim() : '';
}
function mergeHomeserverStatuses(publishedHomeservers = [], serverSessions = []) {
const published = Array.isArray(publishedHomeservers) ? publishedHomeservers : [];
const homeserverSessions = Array.isArray(serverSessions)
? serverSessions.filter((item) => Number(item?.sessionType || 0) === 100)
: [];
const onlineHomeservers = homeserverSessions.filter((item) => !!item?.onlineOnThisServer);
const byName = new Map();
onlineHomeservers.forEach((item) => {
const sessionName = homeserverSessionNameFromClientInfo(item?.clientInfoFromClient);
if (sessionName) {
byName.set(sessionName, item);
}
});
return published.map((item) => {
const matched = byName.get(String(item?.sessionName || '').trim()) || null;
let onlineState = matched ? 'online' : 'offline';
let activeSessionId = matched?.sessionId ? String(matched.sessionId) : '';
if (!matched && published.length === 1 && onlineHomeservers.length === 1) {
onlineState = 'online';
activeSessionId = String(onlineHomeservers[0]?.sessionId || '');
}
return {
...item,
activeSessionId,
onlineState,
onlineLabel: onlineState === 'online' ? 'online' : onlineState === 'offline' ? 'offline' : 'unknown',
};
});
}
async function hydrateWalletProfile(login) {
const cleanLogin = String(login || state.activeSession?.login || state.settings.login || '').trim();
if (!cleanLogin) throw new Error('Нет логина для чтения PDA кошелька.');
const profile = await readWalletProfileByLogin(cleanLogin);
const selectedDeviceName = state.signing.selectedDeviceName
|| String(profile?.homeserverSessions?.[0]?.sessionName || '');
state.walletProfile = {
...profile,
homeserverSessions: Array.isArray(profile.homeserverSessions) ? profile.homeserverSessions.map((item) => ({
...item,
onlineState: 'unknown',
onlineLabel: 'unknown',
activeSessionId: '',
})) : [],
};
state.signing = {
...state.signing,
selectedDeviceName,
};
await saveActiveSessionRecord();
return state.walletProfile;
}
async function resumeActiveSession({ keepConnected = false } = {}) {
const sessionRecord = await loadSessionMaterial();
state.activeSession = sessionRecord;
if (!sessionRecord) {
state.connectionOnline = false;
setStatus('Wallet-session ещё не подключена.', 'info');
return { ok: true, connected: false };
}
try {
await persistSettings({
serverLogin: String(sessionRecord?.serverLogin || state.settings.serverLogin || DEFAULT_SHINE_SERVER_LOGIN).trim(),
serverHttp: String(sessionRecord?.serverHttp || state.settings.serverHttp || buildHttpBase('shineup.me')).trim(),
serverUrl: String(sessionRecord?.serverUrl || state.settings.serverUrl || 'wss://shineup.me/ws').trim(),
login: String(sessionRecord?.login || state.settings.login || '').trim(),
});
const resumed = await ensureApi().resumeSession(sessionRecord);
state.connectionOnline = !!keepConnected;
if (!keepConnected) {
ensureApi().close();
state.api = null;
setStatus(`Wallet-session сохранена для @${resumed.login}. Подключение будет открываться только по действию.`, 'info');
} else {
setStatus(`Wallet-session активна для @${resumed.login}.`, 'info');
}
return { ok: true, connected: true, login: resumed.login, sessionId: resumed.sessionId };
} catch (error) {
state.connectionOnline = false;
setStatus(error.message || 'Не удалось восстановить wallet-session.', 'error');
return { ok: false, connected: false, error: state.statusText };
}
}
async function attachApprovedSession(payload) {
if (String(payload?.type || '') !== 'shine-esp-session-attach') {
throw new Error('Доверенное устройство вернуло неподдерживаемый payload. Для plugin нужен session-only approve.');
}
const login = String(payload?.login || state.settings.login || '').trim();
const approvedSession = payload?.session || {};
const sessionRecord = {
login,
sessionId: String(approvedSession?.sessionId || '').trim(),
sessionKey: state.requesterMaterial?.sessionKey || '',
sessionPrivPkcs8: state.requesterMaterial?.sessionPrivPkcs8 || '',
sessionType: Number(approvedSession?.sessionType || 50) || 50,
serverLogin: state.settings.serverLogin,
serverHttp: state.settings.serverHttp,
serverUrl: state.settings.serverUrl,
};
if (!sessionRecord.login || !sessionRecord.sessionId || !sessionRecord.sessionKey || !sessionRecord.sessionPrivPkcs8) {
throw new Error('Получен неполный session-only payload');
}
state.sessionAttachInProgress = true;
try {
state.activeSession = sessionRecord;
state.walletProfile = null;
state.currentWallet = null;
state.signing = {
...state.signing,
selectedDeviceName: '',
devicesResolvedAtMs: 0,
};
await saveActiveSessionRecord();
await hydrateWalletProfile(login);
await saveActiveSessionRecord();
await persistSettings({
login: sessionRecord.login,
serverLogin: sessionRecord.serverLogin,
serverHttp: sessionRecord.serverHttp,
serverUrl: sessionRecord.serverUrl,
});
state.connectionOnline = false;
state.currentWallet = null;
} finally {
state.sessionAttachInProgress = false;
}
}
async function pollPairingStatus() {
if (!state.pairingId || !state.requesterMaterial) return;
try {
const payload = await ensureApi().getTrustedDeviceLoginStatus(state.pairingId);
const stateValue = String(payload?.state || '');
if (stateValue === 'created') {
state.pollTimer = setTimeout(() => {
void pollPairingStatus();
}, 2200);
return;
}
if (stateValue === 'approved') {
const decoded = await decryptPairingPayloadFromEnvelope(payload?.encryptedPayload, state.requesterMaterial);
await attachApprovedSession(decoded);
clearPairingState();
setStatus('Wallet-session создана и сохранена. Кошелёк остаётся офлайн до запроса подписи.', 'info');
return;
}
if (stateValue === 'rejected') {
clearPairingState();
setStatus('Заявка отклонена на доверенном устройстве.', 'error');
return;
}
if (stateValue === 'expired' || stateValue === 'canceled') {
clearPairingState();
setStatus('Ожидание подключения завершено.', 'error');
return;
}
state.pollTimer = setTimeout(() => {
void pollPairingStatus();
}, 2200);
} catch (error) {
clearPairingState();
setStatus(error.message || 'Не удалось проверить pairing-статус.', 'error');
}
}
async function startPairing({ login, usePassword, password }) {
const cleanLogin = String(login || '').trim();
if (!cleanLogin) {
throw new Error('Введите логин.');
}
await persistSettings({ login: cleanLogin });
await resolveServerForLogin(cleanLogin);
clearPairingState();
setStatus('Проверяем пользователя и создаём wallet-session заявку...', 'info');
const api = ensureApi();
const user = await api.getUser(cleanLogin);
if (user?.exists !== true) {
throw new Error('Пользователь не найден.');
}
state.requesterMaterial = await createRequesterPairingMaterial();
const passwordHash = usePassword
? await deriveEspPairingPasswordHash(cleanLogin, String(password || ''))
: '';
const payload = await api.startTrustedDeviceLogin({
login: cleanLogin,
passwordHash,
requesterSessionKey: state.requesterMaterial.sessionKey,
payloadType: 1,
});
state.pairingId = String(payload?.pairingId || '').trim();
state.expiresAtMs = Number(payload?.expiresAtMs || 0);
state.shortCode = String(payload?.shortCode || '');
state.trustedSessionOnline = !!payload?.trustedSessionOnline;
if (!state.pairingId) {
throw new Error('Сервер не вернул pairingId.');
}
state.pollTimer = setTimeout(() => {
void pollPairingStatus();
}, 1800);
setStatus('Wallet-session заявка создана. Ожидаем подтверждение на доверенном устройстве.', 'info');
return {
pairingId: state.pairingId,
shortCode: String(payload?.shortCode || ''),
expiresAtMs: state.expiresAtMs,
trustedSessionOnline: !!payload?.trustedSessionOnline,
};
}
async function cancelPairing() {
if (!state.pairingId || !state.requesterMaterial?.sessionKey) {
clearPairingState();
return { ok: true };
}
await ensureApi().cancelTrustedDeviceLogin(state.pairingId, state.requesterMaterial.sessionKey);
clearPairingState();
setStatus('Ожидание подключения отменено.', 'info');
return { ok: true };
}
async function disconnectSession() {
ensureApi().close();
state.api = null;
await clearSessionMaterial();
state.activeSession = null;
state.connectionOnline = false;
state.walletProfile = null;
state.signing = {
selectedDeviceName: '',
devicesResolvedAtMs: 0,
};
state.currentWallet = null;
setStatus('Сохранённая wallet-session удалена из plugin.', 'info');
return { ok: true };
}
async function refreshWalletDevices() {
if (!state.activeSession?.login) {
throw new Error('Сначала подключите wallet-session.');
}
await hydrateWalletProfile(state.activeSession.login);
const resumed = await resumeActiveSession({ keepConnected: true });
if (!resumed.ok) {
throw new Error(resumed.error || 'Не удалось открыть wallet-session.');
}
try {
const sessions = await ensureApi().listSessions();
state.walletProfile = {
...state.walletProfile,
homeserverSessions: mergeHomeserverStatuses(state.walletProfile?.homeserverSessions, sessions),
};
state.signing.devicesResolvedAtMs = Date.now();
if (!state.signing.selectedDeviceName && state.walletProfile.homeserverSessions[0]?.sessionName) {
state.signing.selectedDeviceName = state.walletProfile.homeserverSessions[0].sessionName;
}
await saveActiveSessionRecord();
setStatus('Список доверенных homeserver-устройств обновлён.', 'info');
return {
ok: true,
devices: state.walletProfile.homeserverSessions,
};
} finally {
ensureApi().close();
state.api = null;
state.connectionOnline = false;
}
}
async function updateSigningSelection({ selectedDeviceName } = {}) {
state.signing = {
...state.signing,
selectedDeviceName: String(selectedDeviceName || state.signing.selectedDeviceName || ''),
};
await saveActiveSessionRecord();
return { ok: true };
}
async function resolveSelectedHomeserverSession() {
if (!state.activeSession?.login) {
throw new Error('Сначала подключите wallet-session.');
}
if (!state.signing.selectedDeviceName) {
throw new Error('Не выбрано устройство homeserver.');
}
let selectedDevice = (state.walletProfile?.homeserverSessions || []).find((item) => item.sessionName === state.signing.selectedDeviceName);
if (!selectedDevice) {
throw new Error('Выбранное устройство не найдено в PDA аккаунта.');
}
if (!selectedDevice.activeSessionId) {
await refreshWalletDevices();
selectedDevice = (state.walletProfile?.homeserverSessions || []).find((item) => item.sessionName === state.signing.selectedDeviceName);
}
if (!selectedDevice?.activeSessionId) {
throw new Error('Выбранный homeserver сейчас не найден онлайн на сервере SHiNE.');
}
return selectedDevice;
}
async function callWalletRpc(requestData, timeoutMs = 8000, abortSignal = null) {
const selectedDevice = await resolveSelectedHomeserverSession();
const resumed = await resumeActiveSession({ keepConnected: true });
if (!resumed.ok) {
throw new Error(resumed.error || 'Не удалось открыть wallet-session.');
}
const requestId = String(requestData?.requestId || `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`);
const callId = `wallet-rpc-${requestId}`;
const payload = {
...requestData,
requestId,
timeMs: Number(requestData?.timeMs || Date.now()),
};
try {
const response = await new Promise((resolve, reject) => {
let settled = false;
let timeoutId = 0;
let off = () => {};
let removeAbortListener = () => {};
const cleanup = () => {
if (settled) return;
settled = true;
if (timeoutId) clearTimeout(timeoutId);
off();
removeAbortListener();
};
timeoutId = setTimeout(() => {
cleanup();
reject(new Error('Таймаут ответа от ESP32.'));
}, timeoutMs);
off = ensureApi().onEvent('IncomingCallSignal', (evt) => {
const eventPayload = evt?.payload || {};
if (String(eventPayload?.callId || '') !== callId) return;
if (Number(eventPayload?.type || 0) !== WALLET_RPC_RESPONSE_TYPE) return;
cleanup();
try {
resolve(JSON.parse(String(eventPayload?.data || '{}')));
} catch {
reject(new Error('ESP32 вернул некорректный JSON.'));
}
});
if (abortSignal) {
const onAbort = () => {
cleanup();
reject(abortSignal.reason instanceof Error ? abortSignal.reason : new Error('Ожидание подписи отменено.'));
};
if (abortSignal.aborted) {
onAbort();
return;
}
abortSignal.addEventListener('abort', onAbort, { once: true });
removeAbortListener = () => {
abortSignal.removeEventListener('abort', onAbort);
};
}
ensureApi().callSignalToSession({
toLogin: state.activeSession.login,
targetSessionId: selectedDevice.activeSessionId,
callId,
type: WALLET_RPC_REQUEST_TYPE,
data: JSON.stringify(payload),
}).catch((error) => {
cleanup();
reject(error);
});
});
return { response, selectedDevice, requestId };
} finally {
ensureApi().close();
state.api = null;
state.connectionOnline = false;
}
}
function verifyWalletAgainstPda(wallet) {
const type = String(wallet?.type || '').trim();
const pub = String(wallet?.publicKeyBase58 || '').trim();
const rootKey = String(state.walletProfile?.publicKeys?.rootKeyBase58 || '').trim();
const clientKey = String(
state.walletProfile?.publicKeys?.clientKeyBase58 || '',
).trim();
if (type === 'client.key') {
return {
verified: !!clientKey && clientKey === pub,
verificationText: clientKey === pub ? 'Совпадает с clientKey из PDA.' : 'Не совпадает с clientKey из PDA.',
};
}
if (type === 'root.key') {
return {
verified: !!rootKey && rootKey === pub,
verificationText: rootKey === pub ? 'Совпадает с rootKey из PDA.' : 'Не совпадает с rootKey из PDA.',
};
}
return {
verified: null,
verificationText: 'Для custom-кошелька проверка через PDA пока не выполняется.',
};
}
async function requestCurrentWallet() {
const { response, selectedDevice, requestId } = await callWalletRpc({
v: 1,
operation: 'get_wallet_public_key',
requestId: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
});
if (!response?.ok) {
throw new Error(`ESP32 отклонил запрос: ${String(response?.error || 'unknown_error')}`);
}
state.currentWallet = {
type: String(response?.wallet?.type || '').trim(),
publicKeyBase58: String(response?.wallet?.publicKeyBase58 || '').trim(),
homeserverName: selectedDevice.sessionName,
requestId: String(response?.requestId || requestId),
timeMs: Number(response?.timeMs || 0),
...verifyWalletAgainstPda(response?.wallet || {}),
};
await saveActiveSessionRecord();
setStatus(`Кошелёк получен с ${selectedDevice.sessionName}.`, 'info');
return { ok: true, wallet: state.currentWallet };
}
async function cancelPendingSiteApproval() {
const pending = getCurrentPendingApproval();
if (!pending) {
setStatus('Сейчас нет активного ожидания подписи.', 'info');
return { ok: true };
}
removePendingApproval(pending.id, {
rejectError: makeCodeError('User canceled request in extension.', 'USER_REJECTED'),
});
setStatus('Ожидание подписи отменено в расширении.', 'info');
return { ok: true };
}
async function markPendingSiteApprovalResolved(pendingId) {
removePendingApproval(pendingId);
}
function enqueueSiteApproval(work) {
const run = state.siteApprovalChain.then(work, work);
state.siteApprovalChain = run.catch(() => {});
return run;
}
async function activatePendingApproval(pending, sender = null) {
const abortController = new AbortController();
pending.status = 'active';
pending.abortController = abortController;
pending.timeoutId = setTimeout(() => {
removePendingApproval(pending.id, {
rejectError: makeCodeError('Signing request timed out in extension.', 'USER_REJECTED'),
});
setStatus('Ожидание подписи истекло в расширении.', 'error');
}, 120000);
setStatus(`Сайт ${pending.origin} запросил подпись. Подтвердите или отмените на доверенном устройстве.`, 'info');
await openSidePanelForSender(sender);
return pending;
}
function beginSiteTransactionFlow(payload = {}) {
const pending = makePendingApprovalSnapshot({
...payload,
kind: 'sign_transaction',
id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
createdAtMs: Date.now(),
status: 'queued',
});
state.pendingApprovals.push({
...pending,
timeoutId: 0,
abortController: null,
});
return pending;
}
async function siteConnect({ origin, onlyIfTrusted = false } = {}) {
const normalizedOrigin = normalizeOrigin(origin);
if (!normalizedOrigin) {
throw makeCodeError('Site origin is missing.', 'BAD_ORIGIN');
}
if (onlyIfTrusted && !isOriginApproved(normalizedOrigin)) {
throw makeCodeError('Site is not trusted yet.', 'NOT_TRUSTED');
}
const result = await requestCurrentWallet();
const publicKeyBase58 = String(result?.wallet?.publicKeyBase58 || '').trim();
if (!publicKeyBase58) {
throw makeCodeError('Wallet public key is not available.', 'WALLET_UNAVAILABLE');
}
if (!isOriginApproved(normalizedOrigin)) {
await setOriginApproved(normalizedOrigin, true);
}
setStatus(`Site ${normalizedOrigin} connected to ${shortKey(publicKeyBase58, 8)}.`, 'info');
return {
ok: true,
publicKeyBase58,
walletType: String(result?.wallet?.type || '').trim(),
};
}
async function siteDisconnect({ origin } = {}) {
const normalizedOrigin = normalizeOrigin(origin);
setStatus(normalizedOrigin ? `Site ${normalizedOrigin} disconnected.` : 'Site disconnected.', 'info');
return { ok: true };
}
async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64, comment, transactionSummary } = {}, sender = null) {
const normalizedOrigin = normalizeOrigin(origin);
if (!normalizedOrigin) {
throw makeCodeError('Site origin is missing.', 'BAD_ORIGIN');
}
if (!isOriginApproved(normalizedOrigin)) {
throw makeCodeError('Site is not trusted yet.', 'NOT_TRUSTED');
}
const cleanPub = String(publicKeyBase58 || '').trim();
const cleanTx = String(transactionBase64 || '').trim();
if (!cleanPub || !cleanTx) {
throw makeCodeError('Transaction payload is incomplete.', 'BAD_REQUEST');
}
const pending = beginSiteTransactionFlow({
origin: normalizedOrigin,
publicKeyBase58: cleanPub,
comment: String(comment || '').trim(),
transactionSummary: transactionSummary || null,
});
return enqueueSiteApproval(async () => {
await activatePendingApproval(getCurrentPendingApproval() || pending, sender);
const activePending = getCurrentPendingApproval() || pending;
const requestId = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
const signComment = String(comment || '').trim() || `Site ${normalizedOrigin} requested transaction signature`;
try {
const { response } = await callWalletRpc({
v: 1,
operation: 'sign_transaction',
requestId,
publicKeyBase58: cleanPub,
transactionBase64: cleanTx,
comment: signComment,
}, 120000, activePending.abortController?.signal || null);
if (!response?.ok) {
const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase();
if (errorCode === 'REJECTED_BY_USER') {
throw makeCodeError('User rejected transaction signature on ESP32.', 'USER_REJECTED');
}
throw makeCodeError(`ESP32 rejected transaction signature: ${String(response?.error || 'unknown_error')}`, errorCode || 'RPC_REJECTED');
}
setStatus(`Подпись для ${normalizedOrigin} завершена.`, 'info');
return {
ok: true,
publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(),
signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(),
signatureBase58: String(response?.signatureBase58 || '').trim(),
};
} finally {
await markPendingSiteApprovalResolved(activePending.id);
}
});
}
function snapshot() {
return {
settings: { ...state.settings },
pairing: {
active: !!state.pairingId,
pairingId: state.pairingId,
expiresAtMs: state.expiresAtMs,
shortCode: state.shortCode,
trustedSessionOnline: state.trustedSessionOnline,
},
session: state.activeSession ? { ...state.activeSession } : null,
connectionOnline: !!state.activeSession,
walletProfile: state.walletProfile ? { ...state.walletProfile } : null,
currentWallet: state.currentWallet ? { ...state.currentWallet } : null,
pendingApproval: getCurrentPendingApproval() ? makePendingApprovalSnapshot(getCurrentPendingApproval()) : null,
signing: { ...state.signing },
status: {
text: state.statusText,
kind: state.statusKind,
},
};
}
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
(async () => {
const type = String(message?.type || '');
if (type === 'wallet:getState') {
await loadStateFromStorage();
sendResponse({ ok: true, state: snapshot() });
return;
}
if (type === 'wallet:saveSettings') {
await persistSettings(message?.payload || {});
sendResponse({ ok: true, state: snapshot() });
return;
}
if (type === 'wallet:resolveServerInfo') {
const result = await resolveServerForLogin(String(message?.payload?.login || '').trim());
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:startPairing') {
const result = await startPairing(message?.payload || {});
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:cancelPairing') {
const result = await cancelPairing();
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:resumeSession') {
const result = await resumeActiveSession();
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:refreshWalletDevices') {
const result = await refreshWalletDevices();
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:updateSigningSelection') {
const result = await updateSigningSelection(message?.payload || {});
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:requestCurrentWallet') {
const result = await requestCurrentWallet();
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:disconnectSession') {
const result = await disconnectSession();
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:cancelPendingSiteApproval') {
const result = await cancelPendingSiteApproval();
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:siteConnect') {
const result = await siteConnect(message?.payload || {});
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:siteDisconnect') {
const result = await siteDisconnect(message?.payload || {});
sendResponse({ ok: true, result, state: snapshot() });
return;
}
if (type === 'wallet:siteSignTransaction') {
const result = await siteSignTransaction(message?.payload || {}, _sender);
sendResponse({ ok: true, result, state: snapshot() });
return;
}
sendResponse({ ok: false, error: 'UNKNOWN_MESSAGE' });
})().catch((error) => {
const message = toWalletErrorMessage(error, 'Unknown error');
setStatus(message, 'error');
sendResponse({ ok: false, error: message, code: String(error?.code || ''), state: snapshot() });
});
return true;
});
chrome.runtime.onInstalled.addListener(() => {
void configureSidePanelBehavior();
});
chrome.runtime.onStartup.addListener(() => {
void configureSidePanelBehavior();
});
void configureSidePanelBehavior();
void loadStateFromStorage().then(async () => {
if (state.activeSession?.login) {
await hydrateWalletProfile(state.activeSession.login).catch(() => {});
setStatus(`Wallet-session сохранена для @${state.activeSession.login}. Подключение будет открываться только по действию.`, 'info');
}
}).catch((error) => {
setStatus(error?.message || 'Не удалось инициализировать wallet plugin.', 'error');
});
+20
View File
@@ -0,0 +1,20 @@
plugins {
id 'java'
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation platform('org.junit:junit-bom:6.0.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
test {
useJUnitPlatform()
}
@@ -0,0 +1,93 @@
const PAGE_REQUEST = 'shine-wallet-page-request';
const PAGE_RESPONSE = 'shine-wallet-page-response';
const PAGE_MESSAGE_TARGET_ORIGIN = '*';
function injectProviderBridge() {
const root = document.head || document.documentElement;
if (!root) return;
const script = document.createElement('script');
script.type = 'module';
script.src = chrome.runtime.getURL('provider-bridge.js');
script.dataset.shineWalletProvider = '1';
root.appendChild(script);
script.remove();
}
function respondToPage(id, ok, result, error, code) {
window.postMessage({
target: PAGE_RESPONSE,
id: String(id || ''),
ok: !!ok,
result: result || null,
error: error ? String(error) : '',
code: code ? String(code) : '',
}, PAGE_MESSAGE_TARGET_ORIGIN);
}
function sendRuntimeMessage(type, payload = {}) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({ type, payload }, (response) => {
if (chrome.runtime.lastError) {
const raw = String(chrome.runtime.lastError.message || 'Runtime message failed');
if (/Extension context invalidated/i.test(raw)) {
const error = new Error('Расширение было перезагружено или отключено. Обновите страницу и откройте кошелёк заново.');
error.code = 'EXTENSION_CONTEXT_INVALIDATED';
reject(error);
return;
}
reject(new Error(raw));
return;
}
if (!response?.ok) {
const error = new Error(String(response?.error || 'Wallet operation failed'));
error.code = String(response?.code || '');
reject(error);
return;
}
resolve(response);
});
});
}
window.addEventListener('message', (event) => {
if (event.source !== window) return;
const data = event.data || {};
if (data?.target !== PAGE_REQUEST) return;
const id = String(data?.id || '');
const method = String(data?.method || '');
const params = data?.params || {};
const origin = window.location.origin;
(async () => {
if (method === 'connect') {
const response = await sendRuntimeMessage('wallet:siteConnect', {
origin,
onlyIfTrusted: !!params?.onlyIfTrusted,
});
respondToPage(id, true, response.result || null);
return;
}
if (method === 'disconnect') {
const response = await sendRuntimeMessage('wallet:siteDisconnect', { origin });
respondToPage(id, true, response.result || null);
return;
}
if (method === 'signTransaction') {
const response = await sendRuntimeMessage('wallet:siteSignTransaction', {
origin,
publicKeyBase58: String(params?.publicKeyBase58 || '').trim(),
transactionBase64: String(params?.transactionBase64 || '').trim(),
comment: String(params?.comment || '').trim(),
transactionSummary: params?.transactionSummary || null,
});
respondToPage(id, true, response.result || null);
return;
}
respondToPage(id, false, null, 'Unsupported provider method', 'UNSUPPORTED_METHOD');
})().catch((error) => {
respondToPage(id, false, null, error?.message || 'Wallet bridge error', error?.code || '');
});
});
injectProviderBridge();
@@ -0,0 +1,6 @@
#Mon Apr 13 16:45:38 MSK 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+234
View File
@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

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