Handle defined exits from python scripts

This commit is contained in:
Lord-Grey 2024-11-20 21:03:41 +01:00
parent 07bf243a44
commit f808b43573
2 changed files with 112 additions and 40 deletions

View File

@ -1,5 +1,3 @@
# Two projectiles are sent from random positions and collide with each other
# Template from https://github.com/nickpesce/lit/blob/master/lit/effects/collision.py
import hyperion, time, colorsys, random import hyperion, time, colorsys, random
# Get parameters # Get parameters
@ -7,6 +5,11 @@ sleepTime = max(0.02, float(hyperion.args.get('speed', 100))/1000.0)
trailLength = max(3, int(hyperion.args.get('trailLength', 5))) trailLength = max(3, int(hyperion.args.get('trailLength', 5)))
explodeRadius = int(hyperion.args.get('explodeRadius', 8)) explodeRadius = int(hyperion.args.get('explodeRadius', 8))
# Ensure that the range for pixel indices stays within bounds
maxPixelIndex = hyperion.ledCount - 1
if trailLength > maxPixelIndex or explodeRadius > maxPixelIndex:
exit(f"Error: Color length ({trailLength}) and detonation range ({explodeRadius}) must be less than number of LEDs configured ({hyperion.ledCount})")
# Create additional variables # Create additional variables
increment = None increment = None
projectiles = [] projectiles = []
@ -14,47 +17,64 @@ projectiles = []
# Initialize the led data # Initialize the led data
ledData = bytearray() ledData = bytearray()
for i in range(hyperion.ledCount): for i in range(hyperion.ledCount):
ledData += bytearray((0,0,0)) ledData += bytearray((0,0,0))
# Start the write data loop # Start the write data loop
while not hyperion.abort(): while not hyperion.abort():
if (len(projectiles) != 2): if len(projectiles) != 2:
projectiles = [ [0, 1, random.uniform(0.0, 1.0)], [hyperion.ledCount-1, -1, random.uniform(0.0, 1.0)] ] projectiles = [
increment = -random.randint(0, hyperion.ledCount-1) if random.choice([True, False]) else random.randint(0, hyperion.ledCount-1) [0, 1, random.uniform(0.0, 1.0)], # Start positions of projectiles
[hyperion.ledCount-1, -1, random.uniform(0.0, 1.0)]
]
increment = -random.randint(0, hyperion.ledCount-1) if random.choice([True, False]) else random.randint(0, hyperion.ledCount-1)
ledDataBuf = ledData[:] # Backup the LED data
for i, v in enumerate(projectiles): ledDataBuf = ledData[:]
projectiles[i][0] = projectiles[i][0]+projectiles[i][1] for i, v in enumerate(projectiles):
for t in range(0, trailLength): # Update projectile positions
pixel = v[0] - v[1]*t projectiles[i][0] = projectiles[i][0] + projectiles[i][1]
if pixel + 2 < 0:
pixel += hyperion.ledCount
if pixel + 2 > hyperion.ledCount-1:
pixel -= hyperion.ledCount-1
rgb = colorsys.hsv_to_rgb(v[2], 1, (trailLength - 1.0*t)/trailLength)
ledDataBuf[3*pixel ] = int(255*rgb[0])
ledDataBuf[3*pixel + 1] = int(255*rgb[1])
ledDataBuf[3*pixel + 2] = int(255*rgb[2])
hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment]) for t in range(0, trailLength):
# Calculate pixel index for the trail
pixel = v[0] - v[1] * t
if pixel < 0:
pixel += hyperion.ledCount
if pixel >= hyperion.ledCount:
pixel -= hyperion.ledCount
for i1, p1 in enumerate(projectiles): # Make sure pixel is within bounds
for i2, p2 in enumerate(projectiles): if pixel < 0 or pixel >= hyperion.ledCount:
if (p1 is not p2): continue
prev1 = p1[0] - p1[1]
prev2 = p2[0] - p2[1]
if (prev1 - prev2 < 0) != (p1[0] - p2[0] < 0):
for d in range(0, explodeRadius):
for pixel in range(p1[0] - d, p1[0] + d):
rgb = colorsys.hsv_to_rgb(random.choice([p1[2], p2[2]]), 1, (1.0 * explodeRadius - d) / explodeRadius)
ledDataBuf[3*pixel ] = int(255*rgb[0])
ledDataBuf[3*pixel + 1] = int(255*rgb[1])
ledDataBuf[3*pixel + 2] = int(255*rgb[2])
hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment]) rgb = colorsys.hsv_to_rgb(v[2], 1, (trailLength - 1.0 * t) / trailLength)
time.sleep(sleepTime) ledDataBuf[3*pixel] = int(255 * rgb[0])
ledDataBuf[3*pixel + 1] = int(255 * rgb[1])
ledDataBuf[3*pixel + 2] = int(255 * rgb[2])
projectiles.remove(p1) hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
projectiles.remove(p2)
time.sleep(sleepTime) # Check for collision and handle explosion
for i1, p1 in enumerate(projectiles):
for i2, p2 in enumerate(projectiles):
if p1 is not p2:
prev1 = p1[0] - p1[1]
prev2 = p2[0] - p2[1]
if (prev1 - prev2 < 0) != (p1[0] - p2[0] < 0):
for d in range(0, explodeRadius):
for pixel in range(p1[0] - d, p1[0] + d):
# Check if pixel is out of bounds
if pixel < 0 or pixel >= hyperion.ledCount:
continue
rgb = colorsys.hsv_to_rgb(random.choice([p1[2], p2[2]]), 1, (1.0 * explodeRadius - d) / explodeRadius)
ledDataBuf[3 * pixel] = int(255 * rgb[0])
ledDataBuf[3 * pixel + 1] = int(255 * rgb[1])
ledDataBuf[3 * pixel + 2] = int(255 * rgb[2])
hyperion.setColor(ledDataBuf[-increment:] + ledDataBuf[:-increment])
time.sleep(sleepTime)
projectiles.remove(p1)
projectiles.remove(p2)
time.sleep(sleepTime)

View File

@ -102,16 +102,66 @@ void PythonProgram::execute(const QByteArray& python_code)
{ {
if (PyErr_Occurred()) if (PyErr_Occurred())
{ {
Error(_log, "###### PYTHON EXCEPTION ######");
Error(_log, "## In effect '%s'", QSTRING_CSTR(_name));
PyObject* errorType = NULL, * errorValue = NULL, * errorTraceback = NULL; PyObject* errorType = NULL, * errorValue = NULL, * errorTraceback = NULL;
PyErr_Fetch(&errorType, &errorValue, &errorTraceback); PyErr_Fetch(&errorType, &errorValue, &errorTraceback);
PyErr_NormalizeException(&errorType, &errorValue, &errorTraceback); PyErr_NormalizeException(&errorType, &errorValue, &errorTraceback);
// Check if the exception is a SystemExit
PyObject* systemExitType = PyExc_SystemExit;
bool isSystemExit = PyObject_IsInstance(errorValue, systemExitType);
if (isSystemExit)
{
// Extract the exit argument
PyObject* exitArg = PyObject_GetAttrString(errorValue, "code");
if (exitArg)
{
QString logErrorText;
if (PyTuple_Check(exitArg)) {
PyObject* errorMessage = PyTuple_GetItem(exitArg, 0); // Borrowed reference
PyObject* exitCode = PyTuple_GetItem(exitArg, 1); // Borrowed reference
if (exitCode && PyLong_Check(exitCode))
{
logErrorText = QString("[%1]: ").arg(PyLong_AsLong(exitCode));
}
if (errorMessage && PyUnicode_Check(errorMessage)) {
logErrorText.append(PyUnicode_AsUTF8(errorMessage));
}
}
else if (PyUnicode_Check(exitArg)) {
// If the code is just a string, treat it as an error message
logErrorText.append(PyUnicode_AsUTF8(exitArg));
}
else if (PyLong_Check(exitArg)) {
// If the code is just an integer, treat it as an exit code
logErrorText = QString("[%1]").arg(PyLong_AsLong(exitArg));
}
Error(_log, "Effect '%s' failed with error %s", QSTRING_CSTR(_name), QSTRING_CSTR(logErrorText));
Py_DECREF(exitArg); // Release the reference
}
else
{
Debug(_log, "No 'code' attribute found on SystemExit exception.");
}
// Clear the error so it won't propagate
PyErr_Clear();
Py_DECREF(systemExitType);
return;
}
Py_DECREF(systemExitType);
if (errorValue) if (errorValue)
{ {
Error(_log, "###### PYTHON EXCEPTION ######");
Error(_log, "## In effect '%s'", QSTRING_CSTR(_name));
QString message; QString message;
if (PyObject_HasAttrString(errorValue, "__class__")) if (PyObject_HasAttrString(errorValue, "__class__"))
{ {
@ -166,6 +216,8 @@ void PythonProgram::execute(const QByteArray& python_code)
} }
Error(_log, "###### EXCEPTION END ######"); Error(_log, "###### EXCEPTION END ######");
} }
// Clear the error so it won't propagate
PyErr_Clear();
} }
else else
{ {