Last active
March 2, 2025 06:53
-
-
Save parkerlreed/b2e0e9f17434f304768abc0bebd0c6a4 to your computer and use it in GitHub Desktop.
Vivitar Studio Light RGBW control
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import bluepy.btle as btle | |
import crcmod | |
import sys | |
# Bluetooth device MAC address | |
DEVICE_MAC = "FC:58:FA:46:B1:77" | |
# Define CRC-16 function using crcmod | |
def calculate_crc(data): | |
crc16_func = crcmod.predefined.Crc("modbus") | |
crc16_func.update(data) | |
return crc16_func.digest()[::-1] # Reverse bytes for little-endian | |
# Global BLE connection | |
ble_connection = None | |
def connect_to_device(): | |
"""Establishes a persistent BLE connection.""" | |
global ble_connection | |
if ble_connection is None: | |
try: | |
print("Connecting to BLE device...") | |
ble_connection = btle.Peripheral(DEVICE_MAC) | |
print("Connected.") | |
except Exception as e: | |
print(f"Failed to connect: {e}") | |
sys.exit(1) | |
def send_command(command): | |
"""Sends a BLE command using the persistent connection.""" | |
global ble_connection | |
try: | |
if ble_connection is None: | |
connect_to_device() | |
s = ble_connection.getServiceByUUID("0000ffa0-0000-1000-8000-00805f9b34fb") | |
c = s.getCharacteristics()[0] | |
c.write(command) | |
print(f"Command sent: {command.hex()}") | |
except Exception as e: | |
print(f"Error sending command: {e}") | |
ble_connection = None # Reset connection in case of failure | |
# Map white level to correct bytes | |
def map_white_value(level): | |
"""Maps 0-255 to the correct 2-byte sequence for white temperature control.""" | |
warm_bytes = b'\xae\x54' # Full warm | |
cold_bytes = b'\xf9\xfb' # Full cold | |
warm_int = int.from_bytes(warm_bytes, 'big') | |
cold_int = int.from_bytes(cold_bytes, 'big') | |
mapped_value = int(warm_int + ((level / 255) * (cold_int - warm_int))) | |
return mapped_value.to_bytes(2, 'big') | |
# Set white mode with warmth selection | |
def set_white(): | |
while True: | |
white_level = input("Enter white level (0 = full warm, 255 = full cold): ").strip() | |
try: | |
white_val = int(white_level) | |
if 0 <= white_val <= 255: | |
break | |
else: | |
raise ValueError | |
except ValueError: | |
print("Invalid white level. Enter a number between 0 and 255.") | |
# Convert white level to correct bytes | |
white_bytes = map_white_value(white_val) | |
# Third-to-last byte must match warm/cold logic | |
control_byte = b'\x00' if white_val == 0 else b'\xff' if white_val == 255 else bytes([int((white_val / 255) * 255)]) | |
# Construct command | |
command = b'\xaa\x20\x07\xff' + white_bytes + control_byte | |
crc16 = calculate_crc(command) | |
send_command(command + crc16) | |
# Set color mode | |
def set_color(): | |
while True: | |
hex_color = input("Enter HEX color (RRGGBB): ").strip() | |
try: | |
color = bytearray.fromhex(hex_color) | |
if len(color) == 3: | |
break | |
else: | |
raise ValueError | |
except ValueError: | |
print("Invalid color format. Use 6 hex digits (e.g., FF0000 for red).") | |
command = b'\xaa\x17\x0a' + color + color + b'\x64' | |
crc16 = calculate_crc(command) | |
send_command(command + crc16) | |
# Set brightness | |
def set_brightness(): | |
while True: | |
brightness = input("Enter brightness (1-99): ").strip() | |
try: | |
brightness_val = int(brightness) | |
if 1 <= brightness_val <= 99: | |
break | |
else: | |
raise ValueError | |
except ValueError: | |
print("Invalid brightness. Enter a number between 1 and 99.") | |
command = b'\xaa\x13\x04' + bytes([brightness_val]) | |
crc16 = calculate_crc(command) | |
send_command(command + crc16) | |
# Turn off both color and white | |
def turn_off(): | |
print("Turning off...") | |
command_off = b'\xaa\x11\x04\x00\x73\x39' # New simplified off command | |
crc_off = calculate_crc(command_off) | |
send_command(command_off + crc_off) | |
# Main loop | |
connect_to_device() # Establish connection once | |
try: | |
while True: | |
print("\nOptions:") | |
print("1 - Set Color") | |
print("2 - Set White Level") | |
print("3 - Set Brightness") | |
print("4 - Turn Off") | |
print("5 - Exit") | |
choice = input("Select an option: ").strip() | |
if choice == "1": | |
set_color() | |
elif choice == "2": | |
set_white() | |
elif choice == "3": | |
set_brightness() | |
elif choice == "4": | |
turn_off() | |
elif choice == "5": | |
print("Exiting.") | |
break | |
else: | |
print("Invalid option. Try again.") | |
finally: | |
# Close the connection on exit | |
if ble_connection: | |
print("Disconnecting from BLE device...") | |
ble_connection.disconnect() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment