Compare commits

..

38 Commits
master ... dev

Author SHA1 Message Date
Tempest
6a21816e42 Ready for delivery 2025-10-04 19:11:03 +07:00
Tempest
197c3c20e2 Readded numerical input. 2025-09-22 14:37:30 +07:00
Tempest
91d4f59a9f Updated for single BLE Characteristic communication, replacing for 1 Char 1 Lamp 2025-09-19 20:35:25 +07:00
Tempest
b4cfbbe60b Removing older version and temporary tests from repo 2025-09-19 20:30:34 +07:00
Tempest
bda937a889 Remove misc file and modified firmware flow to commit data to BLE buffer 2025-09-18 21:44:13 +07:00
Tempest
6bf9ede793 Changing firmware for modifying the whole lamp at the same time. 2025-09-17 12:47:12 +07:00
Tempest
f3c2529394 Handled Ctrl C gracefully... I hope. 2025-08-08 15:52:38 +07:00
Tempest
ef12927410 Fixed phone interface vertical overflow 2025-08-08 15:22:32 +07:00
Tempest
89677ac283 Corrected the web page behavior on widescreen to get matrix and control panel side by side. 2025-08-08 15:16:47 +07:00
Tempest
1819bbfa2e Modified app.py to migrate to server-side rendering 2025-08-08 00:02:33 +07:00
Tempest
ebb25aee9c Updated to a workable interface right now. 2025-08-07 18:17:29 +07:00
Tempest
c27e3d661d Modified for correct value sending 2025-08-06 17:07:55 +07:00
ce0639863c Added to prepare for web interface of application. 2025-08-06 15:22:46 +07:00
bddc6c99fc Modified to add flask into app requirements 2025-08-06 15:21:38 +07:00
d79fc79519 Preparing Web Interface for Lamp Matrix Controller 2025-08-05 15:44:05 +07:00
2aaffd8ba1 Re-sorted service list to ensure correct processing order 2025-08-04 17:48:14 +07:00
54a02ad149 Corrected the characteristic order to properly address lamps 2025-08-04 17:42:26 +07:00
208643a286 Modified to accomodate bleak on Jetson AGX 2025-08-04 17:31:20 +07:00
f258fced41 Added requirements.txt for portability 2025-08-04 17:27:14 +07:00
Tempest
b7cd8d18e8 Final Commit for TUI 2025-07-25 18:15:11 +07:00
Tempest
528ca5da5b Splitted Center into its own value... ugly solution 2025-07-25 17:54:38 +07:00
Tempest
cf9a3d7c93 Implemented a basic Text-based User Interface 2025-07-25 16:57:16 +07:00
Tempest
4e4adeb581 Added test script for controlling Billboard over BLE 2025-07-15 20:06:42 +07:00
Tempest
7fa219a7f3 Remove Leftover MacOS file... again 2024-11-15 21:25:53 +07:00
Tempest
20d65d5b56 Removed MacOS leftover file 2024-11-15 21:24:01 +07:00
Tempest
ed9270663d Preparing for the RDM module 2024-11-15 20:08:40 +07:00
Tempest
245eb45ff1 Finalized BLE communication 2024-11-15 11:46:47 +07:00
2a30a98144 testing multiple characteristics 2024-11-13 16:49:57 +07:00
f5c2200e79 Remove string print from BLE intepreter 2024-11-05 09:31:34 +07:00
9f03a3c477 Merge branch 'dev' of https://git.spirals.cc/shudder5305/pupilometer into dev 2024-11-05 09:31:18 +07:00
Tempest
785a4212f5 Prototype for 4-byte BLE communication 2024-11-05 09:28:31 +07:00
Tempest
b2e0851c77 Prototype for 4-byte BLE communication 2024-11-04 21:14:20 +07:00
359eb64f30 Adjusting BLE communication 2024-11-02 12:14:06 +07:00
Tempest
703a3e0c38 Created a basic interface for BLE on ESP32 2024-11-01 21:11:44 +07:00
2aad32f6f3 Finished with prototyping pCharacteristic communication 2024-11-01 16:47:39 +07:00
Tempest
8f601ec030 Added Example for Simple BLE device 2024-10-31 18:55:33 +07:00
Tempest
8b2a97b364 Changing Control Scheme to Serial Reading 2024-10-31 14:56:55 +07:00
a7cf62b9eb Update README.md 2024-10-25 16:34:51 +00:00
7 changed files with 1198 additions and 258 deletions

View File

@ -1,2 +1,5 @@
# pupilometer
### Pupilometer
## Introduction
This repository houses programs and documents related to Pupilometer project by Vietnam Academy of Science and Technology. The project aims to introduce a benchmark and researches into the interaction between light intensity and temperature to the eye strain disorder.

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
bleak>="1.0.0"
flask>="3.1.1"

View File

@ -0,0 +1,298 @@
from flask import Flask, render_template, request, jsonify
import asyncio
from bleak import BleakScanner, BleakClient
import threading
import time
import json
import sys
import signal
import os
# =================================================================================================
# APP CONFIGURATION
# =================================================================================================
# Set to True to run without a physical BLE device for testing purposes.
# Set to False to connect to the actual lamp matrix.
DEBUG_MODE = False
# --- BLE Device Configuration (Ignored in DEBUG_MODE) ---
DEVICE_NAME = "Pupilometer LED Billboard"
global ble_client
global ble_characteristics
ble_client = None
ble_characteristics = None
ble_event_loop = None # Will be initialized if not in debug mode
# =================================================================================================
# BLE HELPER FUNCTIONS (Used in LIVE mode)
# =================================================================================================
lampAmount = 25
def create_spiral_map(n=5):
if n % 2 == 0:
raise ValueError("Matrix size must be odd for a unique center point.")
spiral_map = [[0] * n for _ in range(n)]
r, c = n // 2, n // 2
address = 0
spiral_map[r][c] = address
# Updated directions to start moving UP first instead of right
dr = [-1, 0, 1, 0] # Change in row: Up, Right, Down, Left
dc = [0, 1, 0, -1] # Change in col: Up, Right, Down, Left
direction = 0
segment_length = 1
steps = 0
while address < n * n - 1:
for _ in range(segment_length):
address += 1
r += dr[direction]
c += dc[direction]
if 0 <= r < n and 0 <= c < n:
spiral_map[r][c] = address
direction = (direction + 1) % 4
steps += 1
if steps % 2 == 0:
segment_length += 1
return spiral_map
def get_spiral_address(row, col, spiral_map):
n = len(spiral_map)
if 0 <= row < n and 0 <= col < n:
return spiral_map[row][col]
else:
return -1
SPIRAL_MAP_5x5 = create_spiral_map(5)
async def set_full_matrix_on_ble(colorSeries):
global ble_client
global ble_characteristics
if not ble_client or not ble_client.is_connected:
print("BLE client not connected. Attempting to reconnect...")
await connect_to_ble_device()
if not ble_client or not ble_client.is_connected:
print("Failed to reconnect to BLE client.")
return
else:
print("Confirmed BLE connection status. Proceeding with lamp update.")
# =====================================================================
# SNIPPET TO PATCH SWAPPED LAMP POSITIONS
# =====================================================================
#print("Patching lamp positions 3 <-> 7 and 12 <-> 24.")
# Swap data for lamps at positions 3 and 7
#temp_color_3 = colorSeries[3]
#colorSeries[3] = colorSeries[7]
#colorSeries[7] = temp_color_3
# Swap data for lamps at positions 12 and 24
#temp_color_12 = colorSeries[12]
#colorSeries[12] = colorSeries[24]
#colorSeries[24] = temp_color_12
# =====================================================================
if DEBUG_MODE:
# Ensure all characteristics are available before writing
print(f"Confirmed DEBUG set to true.")
if len(ble_characteristics) != lampAmount:
print(f"Mismatch in lamp amount. Expected {lampAmount}, got {len(ble_characteristics)}.")
return
print(f"Constructed the following matrix data: {colorSeries}")
# Write each byte string to its corresponding characteristic
for i, char in enumerate(ble_characteristics):
value_to_write = colorSeries[i]
print(f"Setting Lamp {i} ({char.uuid}) to {value_to_write.hex()}")
await ble_client.write_gatt_char(char.uuid, value_to_write)
else:
print(f"Confirmed DEBUG set to false.")
value_to_write = b"".join([color for color in colorSeries])
print(value_to_write)
print(f"Setting lamps to {value_to_write.hex()}")
await ble_client.write_gatt_char(ble_characteristics[0].uuid, value_to_write)
async def connect_to_ble_device():
global ble_client
global ble_characteristics
print(f"Scanning for device: {DEVICE_NAME}...")
devices = await BleakScanner.discover()
target_device = next((d for d in devices if d.name == DEVICE_NAME), None)
if not target_device:
print(f"Device '{DEVICE_NAME}' not found.")
return False
print(f"Found device: {target_device.name} ({target_device.address})")
try:
ble_client = BleakClient(target_device.address)
await ble_client.connect()
if ble_client.is_connected:
print(f"Connected to {target_device.name}")
services = [service for service in ble_client.services if service.handle != 1]
# The previous logic for filtering services seems incorrect; let's grab all characteristics
characteristics = [
char for service in services for char in service.characteristics
]
ble_characteristics = sorted(characteristics, key=lambda char: char.handle)
print(f"Found {len(ble_characteristics)} characteristics for lamps.")
return True
else:
print(f"Failed to connect to {target_device.name}")
return False
except Exception as e:
print(f"An error occurred during BLE connection: {e}")
return False
# =================================================================================================
# COLOR MIXING
# =================================================================================================
def calculate_rgb(ww, cw, blue):
"""
Calculates the combined RGB color from warm white, cool white, and blue light values.
This function is a Python equivalent of the JavaScript color mixer in index.html.
"""
# Define the RGB components for each light source based on slider track colors
warm_white_r, warm_white_g, warm_white_b = 255, 192, 128
cool_white_r, cool_white_g, cool_white_b = 192, 224, 255
blue_r, blue_g, blue_b = 0, 0, 255
# Normalize the slider values (0-255) and apply them to the base colors
r = (ww / 255) * warm_white_r + (cw / 255) * cool_white_r + (blue / 255) * blue_r
g = (ww / 255) * warm_white_g + (cw / 255) * cool_white_g + (blue / 255) * blue_g
b = (ww / 255) * warm_white_b + (cw / 255) * cool_white_b + (blue / 255) * blue_b
# Clamp the values to 255 and convert to integer
r = int(min(255, round(r)))
g = int(min(255, round(g)))
b = int(min(255, round(b)))
return r, g, b
def rgb_to_hex(r, g, b):
"""
Converts RGB color values to a hex color string.
"""
# Ensure values are within the valid range (0-255) and are integers
r = int(max(0, min(255, r)))
g = int(max(0, min(255, g)))
b = int(max(0, min(255, b)))
# Convert each component to a two-digit hexadecimal string
return f'#{r:02x}{g:02x}{b:02x}'
# =================================================================================================
# FLASK APPLICATION
# =================================================================================================
app = Flask(__name__)
# In-memory matrix for DEBUG_MODE
lamp_matrix = [['#000000' for _ in range(5)] for _ in range(5)]
@app.route('/')
def index():
print(f"Getting current lamp matrix info: {lamp_matrix}")
if DEBUG_MODE:
return render_template('index.html', matrix=lamp_matrix)
else:
# In live mode, we'll pass a default black matrix.
initial_matrix = [['#000000' for _ in range(5)] for _ in range(5)]
return render_template('index.html', matrix=initial_matrix)
@app.route('/set_matrix', methods=['POST'])
def set_matrix():
data = request.get_json()
full_matrix = data.get('matrix', [])
matrixDataSerialized = []
if not full_matrix or len(full_matrix) != 5 or len(full_matrix[0]) != 5:
return jsonify(success=False, message="Invalid matrix data received"), 400
else:
print(f"Received the following matrix data: {full_matrix}")
#Creating empty byte array
serial_colors = [b'\x00\x00\x00'] * lampAmount
try:
for row in range(5):
for col in range(5):
lamp_data = full_matrix[row][col]
ww = int(lamp_data['ww'])
cw = int(lamp_data['cw'])
blue = int(lamp_data['blue'])
#Preparing byte data for control command
color_bytes = bytes([ww, cw, blue])
spiral_pos = get_spiral_address(row, col, SPIRAL_MAP_5x5)
print(f"Constructed data for {spiral_pos}: {color_bytes}")
if spiral_pos != -1:
serial_colors[spiral_pos] = color_bytes
#Preparing hex color data for frontend
lampColorR, lampColorG, lampColorB = calculate_rgb(ww,cw,blue)
lamp_matrix[row][col] = rgb_to_hex(lampColorR, lampColorG, lampColorB)
if DEBUG_MODE:
# === DEBUG MODE: Update in-memory matrix ===
return jsonify(success=True)
else:
# === LIVE MODE: Communicate with the BLE device ===
asyncio.run_coroutine_threadsafe(
set_full_matrix_on_ble(serial_colors),
ble_event_loop
)
return jsonify(success=True)
except Exception as e:
print(f"Error in set_matrix route: {e}")
return jsonify(success=False, message=str(e)), 500
print(f"Getting current lamp matrix info: {lamp_matrix}")
# =================================================================================================
# APP STARTUP
# =================================================================================================
def signal_handler(signum, frame):
print("Received shutdown signal, gracefully shutting down...")
if not DEBUG_MODE and ble_client and ble_client.is_connected:
print("Disconnecting BLE client...")
disconnect_future = asyncio.run_coroutine_threadsafe(ble_client.disconnect(), ble_event_loop)
try:
# Wait for the disconnect to complete with a timeout
disconnect_future.result(timeout=5)
print("BLE client disconnected successfully.")
except Exception as e:
print(f"Error during BLE disconnect: {e}")
if not DEBUG_MODE and ble_event_loop and ble_event_loop.is_running():
print("Stopping BLE event loop...")
# Schedule a stop and wait for the thread to finish
ble_event_loop.call_soon_threadsafe(ble_event_loop.stop)
ble_thread.join(timeout=1)
print("BLE event loop stopped.")
os._exit(0)
if __name__ == '__main__':
# Register the signal handler before running the app
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
if not DEBUG_MODE:
print("Starting BLE event loop in background thread...")
ble_event_loop = asyncio.new_event_loop()
ble_thread = threading.Thread(target=ble_event_loop.run_forever, daemon=True)
ble_thread.start()
# Connect to the device as soon as the app starts
future = asyncio.run_coroutine_threadsafe(connect_to_ble_device(), ble_event_loop)
future.result(timeout=10) # Wait up to 10 seconds for connection
app.run(debug=True, use_reloader=False, host="0.0.0.0")

View File

@ -0,0 +1,151 @@
:root {
--matrix-width: calc(5 * 70px + 4 * 20px);
}
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
background-color: #f0f0f0;
min-height: 100vh;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.main-content {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 40px;
}
.matrix-grid {
display: grid;
grid-template-columns: repeat(5, 70px);
grid-template-rows: repeat(5, 70px);
gap: 20px;
padding: 20px;
background-color: #333;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
margin-bottom: 20px;
}
.lamp {
width: 70px;
height: 70px;
border-radius: 10%;
background-color: #000;
transition: box-shadow 0.2s, transform 0.1s;
cursor: pointer;
border: 2px solid transparent;
}
.lamp.on {
box-shadow: 0 0 15px currentColor, 0 0 25px currentColor;
}
.lamp.selected {
border: 2px solid #fff;
transform: scale(1.1);
}
h1 {
color: #333;
margin-bottom: 20px;
}
.region-control {
margin-bottom: 20px;
text-align: center;
}
.region-control select {
padding: 10px 15px;
font-size: 14px;
cursor: pointer;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #fff;
width: 200px;
}
.control-panel, .center-lamp-control {
background-color: #444;
padding: 20px;
border-radius: 10px;
width: var(--matrix-width); /* Fixed width for consistency */
max-width: var(--matrix-width);
margin-bottom: 20px;
}
.control-panel.inactive-control {
background-color: #333;
filter: saturate(0.2);
}
.control-panel.inactive-control .slider-row {
pointer-events: none;
}
.control-panel h2, .center-lamp-control h2 {
color: #fff;
font-size: 16px;
margin-bottom: 10px;
text-align: center;
}
.slider-group {
width: 100%;
display: flex;
flex-direction: column;
gap: 5px;
}
.slider-row {
display: grid;
grid-template-columns: 150px 1fr 50px;
gap: 10px;
align-items: center;
}
.slider-group input[type="range"] {
-webkit-appearance: none;
height: 8px;
border-radius: 5px;
outline: none;
cursor: pointer;
}
.slider-group input[type="number"] {
width: 100%;
font-size: 14px;
text-align: center;
border: none;
border-radius: 5px;
padding: 5px;
}
.slider-group input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #fff;
cursor: pointer;
box-shadow: 0 0 5px rgba(0,0,0,0.5);
margin-top: 2px;
}
.slider-group input[type="range"]::-webkit-slider-runnable-track {
height: 24px;
border-radius: 12px;
}
input.white-3000k::-webkit-slider-runnable-track { background: linear-gradient(to right, #000, #ffc080); }
input.white-6500k::-webkit-slider-runnable-track { background: linear-gradient(to right, #000, #c0e0ff); }
input.blue::-webkit-slider-runnable-track { background: linear-gradient(to right, #000, #00f); }
.slider-label {
color: #fff;
font-size: 14px;
text-align: left;
white-space: nowrap;
width: 120px;
}
.inactive-control .slider-label {
color: #888;
}
@media (max-width: 1000px) {
.main-content {
flex-direction: column;
align-items: center;
}
}

View File

@ -0,0 +1,342 @@
<!DOCTYPE html>
<html>
<head>
<title>Lamp Matrix Control</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
// State for the entire 5x5 matrix, storing {ww, cw, blue} for each lamp
var lampMatrixState = Array(5).fill(null).map(() => Array(5).fill({ww: 0, cw: 0, blue: 0}));
var selectedLamps = [];
// Function to calculate a visual RGB color from the three light values using a proper additive model
function calculateRgb(ww, cw, blue) {
// Define the RGB components for each light source based on slider track colors
const warmWhiteR = 255;
const warmWhiteG = 192;
const warmWhiteB = 128;
const coolWhiteR = 192;
const coolWhiteG = 224;
const coolWhiteB = 255;
const blueR = 0;
const blueG = 0;
const blueB = 255;
// Normalize the slider values (0-255) and apply them to the base colors
var r = (ww / 255) * warmWhiteR + (cw / 255) * coolWhiteR + (blue / 255) * blueR;
var g = (ww / 255) * warmWhiteG + (cw / 255) * coolWhiteG + (blue / 255) * blueG;
var b = (ww / 255) * warmWhiteB + (cw / 255) * coolWhiteB + (blue / 255) * blueB;
// Clamp the values to 255 and convert to integer
r = Math.min(255, Math.round(r));
g = Math.min(255, Math.round(g));
b = Math.min(255, Math.round(b));
// Convert to hex string
var toHex = (c) => ('0' + c.toString(16)).slice(-2);
return '#' + toHex(r) + toHex(g) + toHex(b);
}
function updateLampUI(lamp, colorState) {
var newColor = calculateRgb(colorState.ww, colorState.cw, colorState.blue);
var lampElement = $(`.lamp[data-row="${lamp.row}"][data-col="${lamp.col}"]`);
lampElement.css('background-color', newColor);
if (newColor === '#000000') {
lampElement.removeClass('on');
lampElement.css('box-shadow', `inset 0 0 5px rgba(0,0,0,0.5)`);
} else {
lampElement.addClass('on');
lampElement.css('box-shadow', `0 0 15px ${newColor}, 0 0 25px ${newColor}`);
}
}
// Function to update the UI and send the full matrix state to the backend
function sendFullMatrixUpdate(lampsToUpdate, isRegionUpdate = false) {
var fullMatrixData = lampMatrixState.map(row => row.map(lamp => ({
ww: lamp.ww,
cw: lamp.cw,
blue: lamp.blue
})));
$.ajax({
url: '/set_matrix',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ matrix: fullMatrixData }),
success: function(response) {
if (response.success) {
if (isRegionUpdate) {
// On a region button click, update the entire matrix UI
for (var r = 0; r < 5; r++) {
for (var c = 0; c < 5; c++) {
updateLampUI({row: r, col: c}, lampMatrixState[r][c]);
}
}
} else {
// Otherwise, just update the lamps that changed
lampsToUpdate.forEach(function(lamp) {
updateLampUI(lamp, lampMatrixState[lamp.row][lamp.col]);
});
}
}
}
});
}
function updateSliders(ww, cw, blue, prefix = '') {
$(`#${prefix}ww-slider`).val(ww);
$(`#${prefix}cw-slider`).val(cw);
$(`#${prefix}blue-slider`).val(blue);
$(`#${prefix}ww-number`).val(ww);
$(`#${prefix}cw-number`).val(cw);
$(`#${prefix}blue-number`).val(blue);
}
$(document).ready(function() {
var regionMaps = {
'Upper': [
{row: 0, col: 0}, {row: 0, col: 1}, {row: 0, col: 2}, {row: 0, col: 3}, {row: 0, col: 4},
{row: 1, col: 0}, {row: 1, col: 1}, {row: 1, col: 2}, {row: 1, col: 3}, {row: 1, col: 4},
],
'Lower': [
{row: 3, col: 0}, {row: 3, col: 1}, {row: 3, col: 2}, {row: 3, col: 3}, {row: 3, col: 4},
{row: 4, col: 0}, {row: 4, col: 1}, {row: 4, col: 2}, {row: 4, col: 3}, {row: 4, col: 4},
],
'Left': [
{row: 0, col: 0}, {row: 1, col: 0}, {row: 2, col: 0}, {row: 3, col: 0}, {row: 4, col: 0},
{row: 0, col: 1}, {row: 1, col: 1}, {row: 2, col: 1}, {row: 3, col: 1}, {row: 4, col: 1},
],
'Right': [
{row: 0, col: 3}, {row: 1, col: 3}, {row: 2, col: 3}, {row: 3, col: 3}, {row: 4, col: 3},
{row: 0, col: 4}, {row: 1, col: 4}, {row: 2, col: 4}, {row: 3, col: 4}, {row: 4, col: 4},
],
'Inner ring': [
{row: 1, col: 1}, {row: 1, col: 2}, {row: 1, col: 3},
{row: 2, col: 1}, {row: 2, col: 3},
{row: 3, col: 1}, {row: 3, col: 2}, {row: 3, col: 3}
],
'Outer ring': [
{row: 0, col: 0}, {row: 0, col: 1}, {row: 0, col: 2}, {row: 0, col: 3}, {row: 0, col: 4},
{row: 1, col: 0}, {row: 1, col: 4},
{row: 2, col: 0}, {row: 2, col: 4},
{row: 3, col: 0}, {row: 3, col: 4},
{row: 4, col: 0}, {row: 4, col: 1}, {row: 4, col: 2}, {row: 4, col: 3}, {row: 4, col: 4},
],
'All': [
{row: 0, col: 0}, {row: 0, col: 1}, {row: 0, col: 2}, {row: 0, col: 3}, {row: 0, col: 4},
{row: 1, col: 0}, {row: 1, col: 1}, {row: 1, col: 2}, {row: 1, col: 3}, {row: 1, col: 4},
{row: 2, col: 0}, {row: 2, col: 1}, {row: 2, col: 3}, {row: 2, col: 4},
{row: 3, col: 0}, {row: 3, col: 1}, {row: 3, col: 2}, {row: 3, col: 3}, {row: 3, col: 4},
{row: 4, col: 0}, {row: 4, col: 1}, {row: 4, col: 2}, {row: 4, col: 3}, {row: 4, col: 4},
]
};
// Exclude the center lamp from the 'All' region
var allRegionWithoutCenter = regionMaps['All'].filter(lamp => !(lamp.row === 2 && lamp.col === 2));
regionMaps['All'] = allRegionWithoutCenter;
// Initialize lampMatrixState from the initial HTML colors
$('.lamp').each(function() {
var row = $(this).data('row');
var col = $(this).data('col');
var color = $(this).css('background-color');
var rgb = color.match(/\d+/g);
lampMatrixState[row][col] = {
ww: rgb[0], cw: rgb[1], blue: rgb[2]
};
});
$('#region-select').on('change', function() {
var region = $(this).val();
// Toggle the inactive state of the control panel based on selection
if (region) {
$('.control-panel').removeClass('inactive-control');
} else {
$('.control-panel').addClass('inactive-control');
}
var newlySelectedLamps = regionMaps[region];
// Clear selected class from all lamps
$('.lamp').removeClass('selected');
// Get the current slider values to use as the new default
var ww = parseInt($('#ww-slider').val());
var cw = parseInt($('#cw-slider').val());
var blue = parseInt($('#blue-slider').val());
// Reset all lamps except the center to black in our state
var lampsToUpdate = [];
var centerLampState = lampMatrixState[2][2];
lampMatrixState = Array(5).fill(null).map(() => Array(5).fill({ww: 0, cw: 0, blue: 0}));
lampMatrixState[2][2] = centerLampState; // Preserve center lamp state
// Set newly selected lamps to the current slider values
selectedLamps = newlySelectedLamps;
selectedLamps.forEach(function(lamp) {
$(`.lamp[data-row="${lamp.row}"][data-col="${lamp.col}"]`).addClass('selected');
lampMatrixState[lamp.row][lamp.col] = {ww: ww, cw: cw, blue: blue};
});
if (selectedLamps.length > 0) {
// Update sliders to reflect the state of the first selected lamp
var firstLamp = selectedLamps[0];
var firstLampState = lampMatrixState[firstLamp.row][firstLamp.col];
updateSliders(firstLampState.ww, firstLampState.cw, firstLampState.blue, '');
}
// Send the full matrix state
sendFullMatrixUpdate(lampsToUpdate, true);
});
// Event listener for the region sliders and number inputs
$('.region-slider-group input').on('input', function() {
if (selectedLamps.length === 0) return;
var target = $(this);
var originalVal = target.val();
var value = parseInt(originalVal, 10);
// Clamp value
if (isNaN(value) || value < 0) { value = 0; }
if (value > 255) { value = 255; }
if (target.is('[type="number"]') && value.toString() !== originalVal) {
target.val(value);
}
var id = target.attr('id');
if (target.is('[type="range"]')) {
$(`#${id.replace('-slider', '-number')}`).val(value);
} else if (target.is('[type="number"]')) {
$(`#${id.replace('-number', '-slider')}`).val(value);
}
var ww = parseInt($('#ww-slider').val());
var cw = parseInt($('#cw-slider').val());
var blue = parseInt($('#blue-slider').val());
var lampsToUpdate = [];
selectedLamps.forEach(function(lamp) {
lampMatrixState[lamp.row][lamp.col] = {ww: ww, cw: cw, blue: blue};
lampsToUpdate.push(lamp);
});
sendFullMatrixUpdate(lampsToUpdate);
});
// Event listener for the center lamp sliders and number inputs
$('.center-slider-group input').on('input', function() {
var target = $(this);
var originalVal = target.val();
var value = parseInt(originalVal, 10);
// Clamp value
if (isNaN(value) || value < 0) { value = 0; }
if (value > 255) { value = 255; }
if (target.is('[type="number"]') && value.toString() !== originalVal) {
target.val(value);
}
var id = target.attr('id');
if (target.is('[type="range"]')) {
$(`#${id.replace('-slider', '-number')}`).val(value);
} else if (target.is('[type="number"]')) {
$(`#${id.replace('-number', '-slider')}`).val(value);
}
var ww = parseInt($('#center-ww-slider').val());
var cw = parseInt($('#center-cw-slider').val());
var blue = parseInt($('#center-blue-slider').val());
var centerLamp = {row: 2, col: 2};
lampMatrixState[centerLamp.row][centerLamp.col] = {ww: ww, cw: cw, blue: blue};
sendFullMatrixUpdate([centerLamp]);
});
// Initial check to set the inactive state
if (!$('#region-select').val()) {
$('.control-panel').addClass('inactive-control');
}
});
</script>
</head>
<body>
<div class="container">
<h1>Lamp Matrix Control</h1>
<div class="region-control">
<label for="region-select">Select Region:</label>
<select id="region-select">
<option value="" disabled selected>-- Select a region --</option>
<option value="Upper">Upper</option>
<option value="Lower">Lower</option>
<option value="Left">Left</option>
<option value="Right">Right</option>
<option value="Inner ring">Inner ring</option>
<option value="Outer ring">Outer ring</option>
<option value="All">All</option>
</select>
</div>
<div class="main-content">
<div class="matrix-grid">
{% for row in range(5) %}
{% for col in range(5) %}
<div class="lamp" data-row="{{ row }}" data-col="{{ col }}" style="background-color: {{ matrix[row][col] }}; box-shadow: {{ '0 0 15px ' + matrix[row][col] + ', 0 0 25px ' + matrix[row][col] if matrix[row][col] != '#000000' else 'inset 0 0 5px rgba(0,0,0,0.5)' }}"></div>
{% endfor %}
{% endfor %}
</div>
<div class="slider-controls">
<div class="center-lamp-control">
<h2>Center Lamp</h2>
<div class="slider-group center-slider-group">
<div class="slider-row">
<span class="slider-label">Warm White (3000K)</span>
<input type="range" id="center-ww-slider" min="0" max="255" value="0" class="white-3000k">
<input type="number" id="center-ww-number" min="0" max="255" value="0">
</div>
<div class="slider-row">
<span class="slider-label">Cool White (6500K)</span>
<input type="range" id="center-cw-slider" min="0" max="255" value="0" class="white-6500k">
<input type="number" id="center-cw-number" min="0" max="255" value="0">
</div>
<div class="slider-row">
<span class="slider-label">Blue</span>
<input type="range" id="center-blue-slider" min="0" max="255" value="0" class="blue">
<input type="number" id="center-blue-number" min="0" max="255" value="0">
</div>
</div>
</div>
<div class="control-panel">
<h2>Selected Region</h2>
<div class="slider-group region-slider-group">
<div class="slider-row">
<span class="slider-label">Warm White (3000K)</span>
<input type="range" id="ww-slider" min="0" max="255" value="0" class="white-3000k">
<input type="number" id="ww-number" min="0" max="255" value="0">
</div>
<div class="slider-row">
<span class="slider-label">Cool White (6500K)</span>
<input type="range" id="cw-slider" min="0" max="255" value="0" class="white-6500k">
<input type="number" id="cw-number" min="0" max="255" value="0">
</div>
<div class="slider-row">
<span class="slider-label">Blue</span>
<input type="range" id="blue-slider" min="0" max="255" value="0" class="blue">
<input type="number" id="blue-number" min="0" max="255" value="0">
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,257 +0,0 @@
// Include Section
#include "esp_dmx.h"
// Define Section
#define ledPin 2
#define INTERRUPT_PIN 0
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
// Defining BOOT button on ESP32 as our built-in button.
Button button1 = {INTERRUPT_PIN, 0, false};
int mode = 0;
const int modeAmount = 16;
uint8_t brightnessMax = 20;
uint8_t universalBrightness = 10;
uint8_t data[DMX_PACKET_SIZE] = {0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0
};
uint8_t dataSeq[modeAmount][DMX_PACKET_SIZE] =
{
{
0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
universalBrightness,0,0,0, // Orange
0,universalBrightness,0,0, // White
0,universalBrightness,0,0, // White
0,0,universalBrightness,0, // Blue
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,universalBrightness,0,0,
//End Inner Round
//Start Outer Round
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
//Start Inner Round
universalBrightness,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
//End Inner Round
//Start Outer Round
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0
}
}
;
void IRAM_ATTR isr() {
button1.pressed = true;
};
void ledBlink(int interval, int pinNumber) {
digitalWrite(
pinNumber,
!digitalRead(pinNumber)
);
delay(interval);
};
void dmxSetup() {
const dmx_port_t dmx_num = DMX_NUM_1;
Serial.printf("\nSetting up DMX Port %d", dmx_num);
// First, use the default DMX configuration...
dmx_config_t config = DMX_CONFIG_DEFAULT;
// Declare Personality RGBW
const int personality_count = 1;
Serial.print("\nDefining DMX Personality... ");
dmx_personality_t personalities[] = {
{4, "RGBW"}
};
Serial.print("Done");
Serial.print("\nInstalling DMX Driver... ");
// ...install the DMX driver...
dmx_driver_install(dmx_num, &config, personalities, personality_count);
Serial.print("Done");
// ...and then set the communication pins!
const int tx_pin = 18;
const int rx_pin = 5;
const int rts_pin = 21;
Serial.printf("\nSetting up pin %d as Transmit Pin, pin %d as Receive Pin and pin %d as RTS Pin... ", tx_pin, rx_pin, rts_pin);
dmx_set_pin(dmx_num, tx_pin, rx_pin, rts_pin);
Serial.print("Done\n");
}
int serialRead(){
int incomingByte;
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
// say what you got:
Serial.print("I received: ");
Serial.println(incomingByte, DEC);
}
return incomingByte;
}
void setup() {
Serial.begin(115200);
pinMode(ledPin,OUTPUT);
pinMode(INTERRUPT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), isr, RISING);
delay(2000);
Serial.println("If you receive this message, ESP32 module has finished setting up Serial Interface for communication.");
dmxSetup();
const dmx_port_t dmx_num = DMX_NUM_1;
}
void loop() {
// Send the DMX packet.
dmx_send(DMX_NUM_1);
// Preparing next packet
if (button1.pressed){
if (mode < modeAmount - 1){mode++;} else {mode = 0;};
Serial.printf("\n Changing to mode %d", mode); // Increment the value of each slot, excluding the start code.
button1.pressed = false; // Reset button status to FALSE
};
// Wait until the packet is finished being sent before proceeding.
dmx_wait_sent(DMX_NUM_1, DMX_TIMEOUT_TICK);
// Now write the packet synchronously!
dmx_write(DMX_NUM_1, dataSeq[mode], 100);
}

View File

@ -0,0 +1,401 @@
// Include Section
#include "esp_dmx.h"
#include "rdm/controller.h"
#include "rdm/responder.h"
#include "UUID.h"
#include "EEPROM.h"
#define INTERRUPT_PIN 0
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
bool debugMode = true;
int bleCharCount;
const int channelPerLamp = 4;
const int expectedLampCount = 25;
const int dmxPacketSize = channelPerLamp * expectedLampCount + 1; //
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
uint8_t dmxData[DMX_PACKET_SIZE] = {0};
BLEServer* pServer = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint16_t SERVICE_UUID = 20241115;
const int panelAmount = 25;
BLECharacteristic* pCharacteristics[panelAmount];
char* CHARACTERISTIC_UUIDS[panelAmount];
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
// Defining BOOT button on ESP32 as our built-in button.
Button button1 = {INTERRUPT_PIN, 0, false};
int mode = 0;
const int modeAmount = 16;
uint8_t brightnessMax = 20;
uint8_t universalBrightness = 10;
uint8_t dataSeq[modeAmount][DMX_PACKET_SIZE] =
{
{
0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,universalBrightness,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
universalBrightness,0,0,0, // Orange
0,universalBrightness,0,0, // White
0,universalBrightness,0,0, // White
0,0,universalBrightness,0, // Blue
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,0,universalBrightness,0,
0,universalBrightness,0,0,
//End Inner Round
//Start Outer Round
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
},
{
0,
//Start Inner Round
0,0,universalBrightness,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
//End Inner Round
//Start Outer Round
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0,
universalBrightness,0,0,0
}
}
;
void IRAM_ATTR isr() {
button1.pressed = true;
};
void ledBlink(int interval, int pinNumber) {
digitalWrite(
pinNumber,
!digitalRead(pinNumber)
);
delay(interval);
};
void dmxSetup() {
const dmx_port_t dmx_num = DMX_NUM_1;
Serial.printf("\nSetting up DMX Port %d", dmx_num);
// First, use the default DMX configuration...
dmx_config_t config = DMX_CONFIG_DEFAULT;
// Declare Personality RGBW
const int personality_count = 1;
Serial.print("\nDefining DMX Personality... ");
dmx_personality_t personalities[] = {
{4, "RGBW"}
};
Serial.print("Done");
Serial.print("\nInstalling DMX Driver... ");
// ...install the DMX driver...
dmx_driver_install(dmx_num, &config, personalities, personality_count);
Serial.print("Done");
// ...and then set the communication pins!
const int tx_pin = 23;
const int rx_pin = 22;
const int rts_pin = 21;
Serial.printf("\nSetting up pin %d as Transmit Pin, pin %d as Receive Pin and pin %d as RTS Pin... ", tx_pin, rx_pin, rts_pin);
dmx_set_pin(dmx_num, tx_pin, rx_pin, rts_pin);
Serial.print("Done\n");
}
void serialRead(){
String incomingByte;
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.readStringUntil('\r\n');
Serial.print("\nI received: ");
Serial.print(incomingByte);
mode = incomingByte.toInt();
}
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.print("\nIf you receive this message, ESP32 module has finished setting up Serial Interface for communication.");
pinMode(INTERRUPT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), isr, RISING);
//Begin of the DMX Setup
const dmx_port_t dmx_num = DMX_NUM_1;
dmxSetup();
Serial.println("Welcome to Pupilometer LED Billboard!");
const int array_size = 25;
rdm_uid_t uids[array_size];
// This function blocks and may take some time to complete!
Serial.printf("Attempting to Discover the Existing DMX Network... ");
int num_uids = rdm_discover_devices_simple(DMX_NUM_1, uids, array_size);
Serial.printf("Done!\n");
Serial.printf("Discovery found %i UIDs as following:\n", num_uids);
for (int i = 0; i < num_uids; i++){
printf(UIDSTR "\n", UID2STR(uids[i]));
};
// Create the BLE Device
BLEDevice::init("Pupilometer LED Billboard");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID,52);
const bool debugMode = false;
// Serial.printf(debugMode);
// Create a BLE Characteristic
Serial.printf("\nCalculating BLE Charateristic Count");
bleCharCount = (panelAmount * debugMode) + !debugMode;
Serial.printf("\nCalculating BLE MTU ...");
uint16_t bleMTU = ((panelAmount * 3) / bleCharCount) + 3;
Serial.printf("\nSetting BLE MTU to %i bytes... ", bleMTU);
BLEDevice::setMTU(bleMTU + 3);
Serial.printf("Done!\n");
for (uint32_t i = 0; i < bleCharCount; i++){
//UUID uuid;
//uuid.seed(i+1);
//uuid.generate();
//Serial.printf("Creating BLE Characteristic with UUID %s ...", BLEUUID(i+1));
pCharacteristics[i] = pService->createCharacteristic(
i+1,
// BLEUUID(uuid.toCharArray()),
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
Serial.printf("Created BLE Characteristic with UUID %s ...", pCharacteristics[i]->getUUID().toString().c_str());
// pCharacteristics[i]->addDescriptor(new BLE2902());
// Serial.printf("Done\n");
};
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
}
void loop() {
// Save Old Mode
int modeOld = mode;
int msgSize;
uint8_t* btMessage[bleCharCount];
// uint8_t dmxData[DMX_PACKET_SIZE] = {0};
// notify changed value
if (deviceConnected) {
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("Start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
// Serial.printf("\nConstructing Payload using ");
// Serial.printf("Bluetooth Data ...");
if (button1.pressed){
if (mode < modeAmount - 1){mode++;} else {mode = 0;};
// Increment the value of each slot, excluding the start code.
button1.pressed = false; // Reset button status to FALSE
};
serialRead();
if (modeOld != mode){
Serial.printf("\nChanging Lighting Preset to Preset %d", mode);
uint8_t lampData[DMX_PACKET_SIZE / 4 * 3];
Serial.printf("\nDetected preset %i size: %i", mode, sizeof(dataSeq[mode]));
for (int i = 0; i < sizeof(dataSeq[mode]); i++){
dmxData[i] = dataSeq[mode][i];
int sublampIndex = i % 4;
//Serial.printf("[%i]", sublampIndex, j);
if (sublampIndex > 0) {
int j = (i / 4) * 3 + sublampIndex - 1;
Serial.printf("[%i](%i)", j, sublampIndex);
lampData[j] = dataSeq[mode][i];
}
};
pCharacteristics[0]->setValue(lampData, expectedLampCount * 3);
}
Serial.printf("\nConstructing DMX Payload with size ");
for (int i = 0; i < bleCharCount; i++){
btMessage[i] = pCharacteristics[i]->getData();
msgSize = pCharacteristics[i]->getLength();
Serial.printf("%i bytes ", msgSize);
for (int j = 0; j < msgSize; j++){
int packet = btMessage[i][j];
int lampSum = i*3 + j;
int dmxAddress = (lampSum / 3) * 4 + lampSum % 3 + 1;
dmxData[dmxAddress] = packet;
// Serial.printf("[[%i,%i] %i - %i] ",i , j, dmxAddress, packet);
};
};
Serial.printf("\n");
// Serial.printf(" Done");
// Wait until the packet is finished being sent before proceeding.
dmx_wait_sent(DMX_NUM_1, DMX_TIMEOUT_TICK);
// Now write the packet synchronously!
dmx_write(DMX_NUM_1, dmxData, DMX_PACKET_SIZE);
dmx_send(DMX_NUM_1);
}