From 9aeb4f21c00f7cac8e962206e219a3eb23bfa34d Mon Sep 17 00:00:00 2001 From: Innovation Science Date: Thu, 26 May 2022 18:55:50 -0500 Subject: [PATCH] Initial commit --- elitelights.py | 384 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 elitelights.py diff --git a/elitelights.py b/elitelights.py new file mode 100644 index 0000000..95d97ab --- /dev/null +++ b/elitelights.py @@ -0,0 +1,384 @@ +# A (mostly) standalone script to control lights according to events in Elite: Dangerous. +# Coded in two afternoons with lots of coffee by Katie. +# This is meant to be a personal project, so I didn't really bother with expandability and such. +# Perhaps I'll revisit this in the future and make it so it can be expended upon easier. Don't count on it. +# +# (C) Innovation Science 2022 + +try: + import tinytuya +except ModuleNotFoundError: + print("You MUST install tinytuya first.") + print("pip install tinytuya OR python -m pip install tinytuya") + exit() + +import time +import random +import json +import os + +Version = "0.1" + +# Use the classes below to configure your lights. +# You MUST go through the following setup process: https://github.com/jasonacox/tinytuya#setup-wizard---getting-local-keys +class PortLight: + # Actual light config. + DeviceID: str = "DEVICEID HERE" + IP: str = "LOCAL.IP.ADDRESS.HERE" + LocalID: str = "LOCALID HERE" + # Color config. I've left it as my own preferences, but commented out the defaults as well. + red: int = 230 + green: int = 100 + blue: int = 235 + +class StarboardLight: + DeviceID: str = "DEVICEID HERE" + IP: str = "LOCAL.IP.ADDRESS.HERE" + LocalID: str = "LOCALID HERE" + red: int = 230 + green: int = 100 + blue: int = 235 + +class HazardLight: + DeviceID: str = "DEVICEID HERE" + IP: str = "LOCAL.IP.ADDRESS.HERE" + LocalID: str = "LOCALID HERE" + red: int = 255 + green: int = 0 + blue: int = 0 + +# Below are the colors for the default hud colors. I'm estimating again, but it's close enough. +# red: int = 255 +# green: int = 100 +# blue: int = 0 + + +# Elite Dangerous flags + +#ED_Docked = 0x0000000000000001 +#ED_Landed = 0x0000000000000002 +#ED_LandingGearDown = 0x0000000000000004 +#ED_ShieldsUp = 0x0000000000000008 +#ED_Supercruise = 0x0000000000000010 +#ED_FlightAssistOff = 0x0000000000000020 +#ED_HardpointsDeployed = 0x0000000000000040 +#ED_InWing = 0x0000000000000080 + +#ED_LightsOn = 0x0000000000000100 +#ED_CargoScoopDeployed = 0x0000000000000200 +#ED_SilentRunning = 0x0000000000000400 +#ED_ScoopingFuel = 0x0000000000000800 +#ED_SRVHandbrake = 0x0000000000001000 +#ED_SRVTurret = 0x0000000000002000 +#ED_SRVTurretRetracted = 0x0000000000004000 +#ED_SRVDriveAssist = 0x0000000000008000 + +#ED_FSDMassLocked = 0x0000000000010000 +#ED_FSDCharging = 0x0000000000020000 +#ED_FSDCooldown = 0x0000000000040000 +#ED_LowFuel = 0x0000000000080000 +#ED_OverHeating = 0x0000000000100000 +#ED_HasLatLong = 0x0000000000200000 +#ED_IsInDanger = 0x0000000000400000 +#ED_BeingInterdicted = 0x0000000000800000 + +#ED_InMainShip = 0x0000000001000000 +#ED_InFighter = 0x0000000002000000 +#ED_InSRV = 0x0000000004000000 +#ED_HudInAnalysisMode = 0x0000000008000000 +#ED_NightVision = 0x0000000010000000 + +# Status file stuff +# StatusDir will usually be: "C:\\Users\\{YOUR USERNAME HERE}\\Saved Games\\Frontier Developments\\Elite Dangerous" +StatusDir = "C:\\Users\\Sam\\Saved Games\\Frontier Developments\\Elite Dangerous" +CurrentLog = "" +StatusJSON = "" +StatusFlags = 0x00000000 +StatusFlagsStr = "" + +# Makes the infinate loop... loop. +ContinueFlag = True + +# States +DoFlicker = False +DoHazards = False +LightsOn = True +LightsDim = False +LastHullHealth = 0 +CurrentHullHealth = 0 + + +# Initialize light variables and set version +port = tinytuya.BulbDevice(PortLight.DeviceID, PortLight.IP, PortLight.LocalID, dev_type="default") +starboard = tinytuya.BulbDevice(StarboardLight.DeviceID, StarboardLight.IP, StarboardLight.LocalID, dev_type="default") +hazards = tinytuya.BulbDevice(HazardLight.DeviceID, HazardLight.IP, HazardLight.LocalID, dev_type="default") + +# Set Tuya versions +port.set_version(3.3) +starboard.set_version(3.3) +hazards.set_version(3.3) + +def SetupLights(): # Initializes lights + # Set light modes to color + port.set_mode(mode='colour') + starboard.set_mode(mode='colour') + hazards.set_mode(mode='colour') + + # Turn on the lights + port.turn_on() + starboard.turn_on() + hazards.turn_on() + + # Set light colors + port.set_colour(PortLight.red, PortLight.green, PortLight.blue) + starboard.set_colour(StarboardLight.red, StarboardLight.green, StarboardLight.blue) + hazards.set_colour(HazardLight.red, HazardLight.green, HazardLight.blue) + + # Set light brightness (port and starboard are dim, whereas hazard is bright.) + port.set_brightness_percentage(brightness=1) + starboard.set_brightness_percentage(brightness=1) + hazards.set_brightness_percentage(brightness=100) + + # Turn off hazard. + hazards.turn_off() + +def NormalLights(): # Returns lights to normal operation (used when exiting) + # Change lights back to white mode. + port.set_mode(mode='white') + starboard.set_mode(mode='white') + hazards.set_mode(mode='white') + + # Set brightness to full to blind the user. + port.set_brightness_percentage(brightness=100) + starboard.set_brightness_percentage(brightness=100) + hazards.set_brightness_percentage(brightness=100) + +def FlickerLights(): + # So far, I've made it so there are four types of flickers possible (0 being no flicker.) + choices = [0, 1, 2, 3, 4] + choice = random.choice(choices) # Randomly choose a flicker type + flickertime = random.random() # Randomly choose a flicker time (0 to 1 sec) + + # Welcome to if-else hell! I'm using python 3.9.6 so no pattern matching for me, and I don't care enough to deal with dictionaries. + # If you would like to add more flicker patterns, add them here and add a new number to the choices list. + if(choice==0): + # Nothing happens. + pass + elif(choice==1): + # Flicker port + port.turn_off() + time.sleep(flickertime) + port.turn_on() + elif(choice==2): + # Flicker starboard + starboard.turn_off() + time.sleep(flickertime) + starboard.turn_on() + elif(choice==3): + # Flicker both, port first + port.turn_off() + starboard.turn_off() + time.sleep(flickertime) + port.turn_on() + starboard.turn_on() + elif(choice==4): + starboard.turn_off() + port.turn_off() + time.sleep(flickertime) + starboard.turn_on() + port.turn_on() + else: + print("How did we get here?") + +def MainLightsOn(): # Turns main (port and starboard) lights on + port.turn_on() + starboard.turn_on() + +def MainLightsOff(): # Turns main (port and starboard) lights off + port.turn_off() + starboard.turn_off() + +def DimAllLights(): # Dims all lights. Used when FSD is charging to make it look like it's heavily loading the powerplant. + port.set_brightness_percentage(brightness=0) + starboard.set_brightness_percentage(brightness=0) + hazards.set_brightness_percentage(brightness=80) + +def LightsStandard(): # Standard brightness configuration for lights. + port.set_brightness_percentage(brightness=1) + starboard.set_brightness_percentage(brightness=1) + hazards.set_brightness_percentage(brightness=100) + +def HazardsOn(): # Turns the hazard light on + hazards.turn_on() + +def HazardsOff(): # Turns the hazard light off + hazards.turn_off() + +def ReloadFlags(): # Reloads Elite: Dangerous status flags and sets flicker, hazard on/off, lights on/off, and dimming variables for later processing + global StatusDir + global StatusJSON + global StatusFlags + global StatusFlagsStr + global DoFlicker + global DoHazards + global LightsOn + global LightsDim + global ContinueFlag + + # Open the status file + f = open(StatusDir + "\\Status.json", "r") + try: + StatusJSON = json.loads(f.read()) # Read the json from the Status.json file. + except: + pass # Sometimes it guffs up reading (JSONDecodeError), and I can't really fix this. + else: # This makes the logic below use the previous status flags if an exception is raised + StatusFlags = "0x%8x" % StatusJSON["Flags"] # Extrapolate the flags from the json file and make it a known length (8 hex digits) + f.close() # Close Status.json + + try: + # Convert the ship state (3rd from right) byte (LightsOn, Cargo Scoop Deployed, Silent Running, Scooping Fuel) + ShipStateByte = bin(int(StatusFlagsStr[-3]))[2:].zfill(4) + ShipStateByteStr = str(ShipStateByte) + + # Convert the FSD (5th from right) byte (FSD MassLocked, FSD Charging, FSD Cooldown, Low Fuel (<25%)) + FSDByte = bin(int(StatusFlagsStr[-5]))[2:].zfill(4) + FSDByteStr = str(FSDByte) + + # Convert the hazards (6th from right) byte (Overheating (>100%), Has Lat Long, IsInDanger, Being Interdicted) + HazardByte = bin(int(StatusFlagsStr[-6]))[2:].zfill(4) + HazardByteStr = str(HazardByte) + + # If you would like to add more effects to the lights, add them here. + + if(HazardByteStr[-1] == "1" or LastHullHealth > CurrentHullHealth): + DoFlicker = True # Overheating and taking damage causes lights to flicker + else: + DoFlicker = False + + if(int(StatusFlagsStr[-6]) > 0): # If there's any hazard, hazard light is turned on + DoHazards = True + else: + DoHazards = False + + if(ShipStateByteStr[-3] == "1"): # Port and starboard lights turn off when rigged for silent running + LightsOn = False + else: + LightsOn = True + + if(FSDByteStr[-2] == "1"): # Lights dim when the FSD is charging + LightsDim = True + else: + LightsDim = False + except ValueError: + print("ValueError! More than likely, Elite: Dangerous is closed. Exiting.") + ContinueFlag = False + +def GetCurrentLog(): # Gets the most recent log file left by Elite: Dangerous. + global StatusDir + + + LogName = "" + # Changes working dir to the saved games dir temporarily, remembering the original working dir + OriginalDir = os.getcwd() + os.chdir(StatusDir) + StatusDirContents = sorted(os.listdir(StatusDir), key=os.path.getmtime) # Get and sort the "saved game" directory from newest first. + os.chdir(OriginalDir) + + for f in (StatusDirContents): + if(f[-4:] == ".log"): # Search for the newest .log file. + LogName = f # We found the file! + + print("Discovered current log file: " + f) + + return LogName # Return the newest log file. + +def GetDamageStats(): # Extrapolates damage taken from the log file. I'm not sure if this works quite yet. + global StatusDir + global CurrentLog + try: + f = open(StatusDir + "\\" + CurrentLog, "r") # Open the log file + except: + print("Error opening log. Continuing.") # Error checking for if the log file cannot be opened. + return + try: + Lines = f.readlines() # This should never happen. + except: + print("Error reading lines from log. Continuing") + f.close() # Close the log file + + i = 0 + tempjson = "" + for line in reversed(Lines): # Scan in reversed for the newest loadout readings + i += 1 + try: + tempjson = json.loads(line) # Attempt to load the line as JSON. + except: + continue # I'm willing to bet that we will encounter a similar JSONDecodeError here on occasion, so we skip that line. + # This will probably lead to issues, but I'm not too worried about it. + + if(tempjson["event"] == "Loadout"): + return tempjson["HullHealth"] # Returns HullHealth from Loadout event. + + + + +def RunStates(): # Processes light effect variables set by ReloadFlags() + if(DoFlicker): + FlickerLights() + + if(DoHazards): + HazardsOn() + else: + HazardsOff() + + if(LightsOn): + MainLightsOn() + else: + MainLightsOff() + + if(LightsDim): + DimAllLights() + else: + LightsStandard() + + +# Program initialization + +print("Elite Lighting v. " + Version) +print("If the program crashes in this stage, you either need to open Elite: Dangerous first or configure the program.") +print("To configure the program, follow the instructions at: https://git.innovation-inc.org/Innovation/elitelighting") +print("You will also need to read alongside: https://github.com/jasonacox/tinytuya#setup-wizard---getting-local-keys") +print("Failure to do so will cause a crash and may kill your cat(s).") +print("\n") + +print("Setting up lights") +SetupLights() # Set up lights + +print("Lights set up.") + +print("Getting current Elite: Dangerous log") +CurrentLog = GetCurrentLog() # Get current Elite: Dangerous log + +print("Ready. Press CTRL+C at any time to set lights back to normal operation.") + +while ContinueFlag == True: + try: + LastHullHealth = CurrentHullHealth + CurrentHullHealth = GetDamageStats() + ReloadFlags() + RunStates() + time.sleep(0.25) + except KeyboardInterrupt: # This makes it so CTRL+C begins the program exiting sequence, and so the program exits gracefully at any point during execution. + break + +HazardsOn() # Turn on hazards so that it isn't off when lights return to normal operation. + +lightschoice = input("Would you like your lights off? Y/n > ") # Would you like to be light mode'd? +if(lightschoice != "n" and lightschoice != "N"): + print("Turning off lights.") # No, I like having eyes. + MainLightsOff() + HazardsOff() + +print("Returning lights to normal operation.") +NormalLights() # Return lights to normal operation (white mode, full brightness) +print("Goodbye.")