equipment:light:noo-psyche
Различия
Показаны различия между двумя версиями страницы.
| Предыдущая версия справа и слеваПредыдущая версияСледующая версия | Предыдущая версия | ||
| equipment:light:noo-psyche [2025/10/22 12:57] – удалено - внешнее изменение (Дата неизвестна) 127.0.0.1 | equipment:light:noo-psyche [2025/10/28 08:50] (текущий) – Вадим Мартовский | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| + | ==== Редактор для Noo-psyche K7 PRO и K7 Mini ==== | ||
| + | < | ||
| + | <html lang=" | ||
| + | < | ||
| + | <meta charset=" | ||
| + | <meta name=" | ||
| + | < | ||
| + | <script src=" | ||
| + | <script src=" | ||
| + | <script src=" | ||
| + | <script src=" | ||
| + | < | ||
| + | < | ||
| + | body { | ||
| + | font-family: | ||
| + | margin: 0; | ||
| + | background: # | ||
| + | display: flex; | ||
| + | justify-content: | ||
| + | } | ||
| + | .container { | ||
| + | max-width: 1200px; | ||
| + | width: 100%; | ||
| + | padding: 0px; | ||
| + | text-align: center; | ||
| + | } | ||
| + | h2 { margin-top: 0; } | ||
| + | table { border-collapse: | ||
| + | th, td { border: 1px solid #ccc; padding: 3px; text-align: center; } | ||
| + | input { width: 40px; padding: 2px; text-align: center; } | ||
| + | textarea { width: 100%; height: 100px; margin-top: 10px; font-family: | ||
| + | #qrcode { margin-top: 20px; padding: 10px; background: white; display: inline-block; | ||
| + | # | ||
| + | #channels { flex: 1; overflow-x: auto; } | ||
| + | # | ||
| + | # | ||
| + | #chartzone {width: 100%; height: 450px; | ||
| + | # | ||
| + | #fileInput { display: none; } | ||
| + | .pad {width: 100%;} | ||
| + | .upload-btn { | ||
| + | display: inline-block; | ||
| + | padding: 6px 14px; | ||
| + | background: #007bff; | ||
| + | color: white; | ||
| + | border-radius: | ||
| + | cursor: pointer; | ||
| + | font-size: 14px; | ||
| + | } | ||
| + | .upload-btn: | ||
| + | .upload-btn2 { | ||
| + | .upload-btn2: | ||
| + | @media (max-width: 900px) { | ||
| + | # | ||
| + | # | ||
| + | } | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | ⚠️ Для < | ||
| + | Все изменения вы делаете < | ||
| + | </ | ||
| + | <div id=" | ||
| + | |||
| + | <div id=" | ||
| + | <div id=" | ||
| + | <div> | ||
| + | <a href=" | ||
| + | <label for=" | ||
| + | <input type=" | ||
| + | <button id=" | ||
| + | <button id=" | ||
| + | <canvas id=" | ||
| + | | ||
| + | </ | ||
| + | |||
| + | <div style=" | ||
| + | <label for=" | ||
| + | <select id=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | <option value=" | ||
| + | </ | ||
| + | <button id=" | ||
| + | <button id=" | ||
| + | <button id=" | ||
| + | <button id=" | ||
| + | <button id=" | ||
| + | <button id=" | ||
| + | </ | ||
| + | |||
| + | <div id=" | ||
| + | </ | ||
| + | |||
| + | |||
| + | </ | ||
| + | | ||
| + | | ||
| + | | ||
| + | < | ||
| + | <button id=" | ||
| + | <div id=" | ||
| + | <div id=" | ||
| + | <div style=" | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | |||
| + | </ | ||
| + | </ | ||
| + | <table style=" | ||
| + | < | ||
| + | <tr style=" | ||
| + | <th style=" | ||
| + | <th style=" | ||
| + | <th style=" | ||
| + | </tr> | ||
| + | </ | ||
| + | < | ||
| + | <tr> | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | </tr> | ||
| + | <tr style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | </tr> | ||
| + | <tr> | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | </tr> | ||
| + | <tr style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | </tr> | ||
| + | <tr> | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | </tr> | ||
| + | <tr style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | <td style=" | ||
| + | </tr> | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | const toggleBtn = document.getElementById(' | ||
| + | const tableContainer = document.getElementById(' | ||
| + | const fileInput = document.getElementById(' | ||
| + | const hexOutput = document.getElementById(' | ||
| + | const channelsDiv = document.getElementById(' | ||
| + | const qrCanvas = document.getElementById(' | ||
| + | const generateBtn = document.getElementById(' | ||
| + | const downloadBtn = document.getElementById(' | ||
| + | const chartCtx = document.getElementById(' | ||
| + | |||
| + | const NUM_CHANNELS = 6; | ||
| + | const NUM_HOURS = 24; | ||
| + | |||
| + | // ====== Основные данные ====== | ||
| + | let baseMatrix = Array.from({length: | ||
| + | let displayedMatrix = Array.from({length: | ||
| + | let transforms = Array.from({length: | ||
| + | |||
| + | const qrPrefix = " | ||
| + | |||
| + | // ====== Применение трансформаций ====== | ||
| + | function applyTransforms(channel){ | ||
| + | for(let h=0; | ||
| + | let t = transforms[channel]; | ||
| + | // смещение по времени | ||
| + | let pos = ((h - t.shift + NUM_HOURS) % NUM_HOURS); | ||
| + | // растяжение по X (шире/ | ||
| + | if(t.stretchX !== 0){ | ||
| + | pos = NUM_HOURS/2 + (pos - NUM_HOURS/ | ||
| + | pos = ((pos % NUM_HOURS) + NUM_HOURS) % NUM_HOURS; | ||
| + | } | ||
| + | const i0 = Math.floor(pos); | ||
| + | const i1 = (i0 + 1) % NUM_HOURS; | ||
| + | const frac = pos - i0; | ||
| + | // растяжение по Y (высота) | ||
| + | const v0 = baseMatrix[channel][i0]; | ||
| + | const v1 = baseMatrix[channel][i1]; | ||
| + | const val = (v0*(1-frac)+v1*frac) * (1 + t.stretchY/ | ||
| + | displayedMatrix[channel][h] = Math.min(100, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Применяем трансформации ко всем каналам | ||
| + | function updateDisplayedMatrix(){ | ||
| + | for(let c=0; | ||
| + | drawTable(); | ||
| + | updateChart(); | ||
| + | updateHex(); | ||
| + | } | ||
| + | |||
| + | // Сохраняем все изменения из displayedMatrix в baseMatrix | ||
| + | function syncBaseWithDisplayed() { | ||
| + | for (let c1 = 0; c1 < baseMatrix.length; | ||
| + | for (let h1 = 0; h1 < baseMatrix[c1].length; | ||
| + | baseMatrix[c1][h1] = displayedMatrix[c1][h1]; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Сброс коэффициентов трансформации для всех каналов | ||
| + | function resetAllTransformCoefficients() { | ||
| + | for (let c = 0; c < 6; c++) { | ||
| + | resetTransforms(c); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // ====== Таблица ====== | ||
| + | function drawTable(){ | ||
| + | channelsDiv.innerHTML=''; | ||
| + | const table = document.createElement(' | ||
| + | const header = document.createElement(' | ||
| + | [' | ||
| + | const th=document.createElement(' | ||
| + | }); | ||
| + | table.appendChild(header); | ||
| + | |||
| + | for(let h=0; | ||
| + | const tr=document.createElement(' | ||
| + | const tdHour=document.createElement(' | ||
| + | |||
| + | for(let c=0; | ||
| + | const td=document.createElement(' | ||
| + | const input=document.createElement(' | ||
| + | input.type=' | ||
| + | input.value = displayedMatrix[c][h]; | ||
| + | input.min=0; | ||
| + | input.dataset.hour=h; | ||
| + | |||
| + | input.addEventListener(' | ||
| + | syncBaseWithDisplayed(); | ||
| + | resetAllTransformCoefficients() | ||
| + | const val = Math.min(100, | ||
| + | baseMatrix[c][h] = val; // фиксируем базу | ||
| + | displayedMatrix[c][h] = val; | ||
| + | | ||
| + | updateDisplayedMatrix(); | ||
| + | }); | ||
| + | |||
| + | td.appendChild(input); | ||
| + | tr.appendChild(td); | ||
| + | } | ||
| + | table.appendChild(tr); | ||
| + | } | ||
| + | channelsDiv.appendChild(table); | ||
| + | } | ||
| + | |||
| + | // ====== График ====== | ||
| + | let channelChart = new Chart(chartCtx, | ||
| + | type: ' | ||
| + | data: { | ||
| + | labels: Array.from({length: | ||
| + | datasets: [ | ||
| + | { label:' | ||
| + | { label:' | ||
| + | { label:' | ||
| + | { label:' | ||
| + | { label:' | ||
| + | { label:' | ||
| + | ] | ||
| + | }, | ||
| + | options: { | ||
| + | responsive: | ||
| + | maintainAspectRatio: | ||
| + | scales:{ y:{ min:0, max:100, title:{ display: | ||
| + | plugins:{ | ||
| + | legend:{ position:' | ||
| + | dragData:{ | ||
| + | round: | ||
| + | showTooltip: | ||
| + | onDrag: | ||
| + | baseMatrix[datasetIndex][index] = Math.round(displayedMatrix[datasetIndex][index]); | ||
| + | displayedMatrix[datasetIndex][index] = Math.min(100, | ||
| + | drawTable(); | ||
| + | updateHex(); | ||
| + | }, | ||
| + | onDragEnd: | ||
| + | syncBaseWithDisplayed(); | ||
| + | resetAllTransformCoefficients() | ||
| + | baseMatrix[datasetIndex][index] = Math.round(displayedMatrix[datasetIndex][index]); | ||
| + | updateDisplayedMatrix(); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | function updateChart(){ | ||
| + | for(let c=0; | ||
| + | channelChart.data.datasets[c].data = displayedMatrix[c].slice(); | ||
| + | } | ||
| + | channelChart.update(); | ||
| + | } | ||
| + | |||
| + | // ====== Кнопки трансформаций ====== | ||
| + | function resetTransforms(channel){ | ||
| + | transforms[channel] = {shift:0, stretchX:0, stretchY: | ||
| + | } | ||
| + | function transformShift(channel, | ||
| + | if(channel===' | ||
| + | else transforms[channel].shift += delta; | ||
| + | updateDisplayedMatrix(); | ||
| + | } | ||
| + | function transformStretchX(channel, | ||
| + | if(channel===' | ||
| + | else transforms[channel].stretchX += delta; | ||
| + | updateDisplayedMatrix(); | ||
| + | } | ||
| + | function transformStretchY(channel, | ||
| + | if(channel===' | ||
| + | else transforms[channel].stretchY += delta; | ||
| + | updateDisplayedMatrix(); | ||
| + | } | ||
| + | |||
| + | // ====== Привязка кнопок ====== | ||
| + | function getTargetChannel(){ | ||
| + | const val = document.getElementById(' | ||
| + | return val===' | ||
| + | } | ||
| + | |||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | |||
| + | // ====== HEX ====== | ||
| + | function updateHex(){ | ||
| + | let hex=''; | ||
| + | for(let h=0; | ||
| + | hex += h.toString(16).padStart(2,' | ||
| + | for(let c=0; | ||
| + | hex += displayedMatrix[c][h].toString(16).padStart(2,' | ||
| + | } | ||
| + | } | ||
| + | hexOutput.value = hex; | ||
| + | } | ||
| + | |||
| + | // ====== QR ====== | ||
| + | let qrDataURL = ''; | ||
| + | function generateQR(){ | ||
| + | const data = hexOutput.value.trim(); | ||
| + | if(!data) return; | ||
| + | const fullHex = qrPrefix + data; | ||
| + | QRCode.toCanvas(qrCanvas, | ||
| + | if(err){ console.error(err); | ||
| + | const ctx = qrCanvas.getContext(' | ||
| + | const size = qrCanvas.width; | ||
| + | const moduleCount = 61; | ||
| + | const moduleSize = size / moduleCount; | ||
| + | const logoModules = 17; | ||
| + | const start = Math.floor((moduleCount - logoModules)/ | ||
| + | const x = start * moduleSize, y = start*moduleSize; | ||
| + | const w = logoModules*moduleSize, | ||
| + | ctx.fillStyle="# | ||
| + | const logo = new Image(); logo.src=" | ||
| + | logo.onload = ()=>{ ctx.drawImage(logo, | ||
| + | const qrDiv = document.getElementById(' | ||
| + | const img = new Image(); img.src=qrCanvas.toDataURL(); | ||
| + | qrDataURL = qrCanvas.toDataURL(); | ||
| + | }; | ||
| + | }); | ||
| + | } | ||
| + | function downloadQR(){ | ||
| + | if(!qrDataURL) return; | ||
| + | const a=document.createElement(' | ||
| + | a.href = qrDataURL; | ||
| + | a.download=' | ||
| + | a.click(); | ||
| + | } | ||
| + | generateBtn.addEventListener(' | ||
| + | downloadBtn.addEventListener(' | ||
| + | |||
| + | // ====== Загрузка файла ====== | ||
| + | fileInput.addEventListener(' | ||
| + | const file = event.target.files[0]; | ||
| + | if(!file) return; | ||
| + | const reader = new FileReader(); | ||
| + | reader.onload = function(e){ | ||
| + | const img = new Image(); | ||
| + | img.onload = function(){ | ||
| + | const canvas = document.createElement(' | ||
| + | const ctx = canvas.getContext(' | ||
| + | canvas.width = img.width; | ||
| + | canvas.height = img.height; | ||
| + | ctx.drawImage(img, | ||
| + | const imageData = ctx.getImageData(0, | ||
| + | const code = jsQR(imageData.data, | ||
| + | if(code){ | ||
| + | const hexData = code.data.split('#' | ||
| + | hexOutput.value = hexData; | ||
| + | parseHexToBase(hexData); | ||
| + | | ||
| + | syncBaseWithDisplayed(); | ||
| + | resetAllTransformCoefficients() | ||
| + | updateDisplayedMatrix(); | ||
| + | | ||
| + | | ||
| + | console.log(' | ||
| + | } else alert(' | ||
| + | }; | ||
| + | img.src = e.target.result; | ||
| + | }; | ||
| + | reader.readAsDataURL(file); | ||
| + | }); | ||
| + | |||
| + | // ====== UI ====== | ||
| + | toggleBtn.addEventListener(' | ||
| + | if (tableContainer.style.display === ' | ||
| + | tableContainer.style.display = ' | ||
| + | toggleBtn.textContent = ' | ||
| + | } else { | ||
| + | tableContainer.style.display = ' | ||
| + | toggleBtn.textContent = ' | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | // ====== Инициализация ====== | ||
| + | const defaultHex = " | ||
| + | window.onload=function(){ | ||
| + | parseHexToBase(defaultHex); | ||
| + | updateDisplayedMatrix(); | ||
| + | hexOutput.value = defaultHex; | ||
| + | }; | ||
| + | |||
| + | function parseHexToBase(hex){ | ||
| + | for(let h=0; h< | ||
| + | for(let c=0; | ||
| + | const offset = h*8*2 + 4 + c*2; | ||
| + | baseMatrix[c][h] = parseInt(hex.substr(offset, | ||
| + | displayedMatrix[c][h] = baseMatrix[c][h]; | ||
| + | } | ||
| + | } | ||
| + | transforms = Array.from({length: | ||
| + | } | ||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | </ | ||
| + | </ | ||
| + | < | ||
