Ljusstyrning

Själva styrningen av ljuset består av två saker. Dels en webbaserad frontend där man anger villkoren (vilka skrivs till databasen) och dels en backend som läser databasen och skickar rätt kommandon till servern som styr själva LED-stripen. I front-enden väljer man mellan automatiskt läge där man skapar ett schema för hur ljuset ska variera över dygnet och ett manuellt läge där man kan ange värdena för varje färg och intensitet. Jag har även lagt med en liten Kelvin till RGB-omvandlare för att enkelt kunna ange olika färgtemperaturer. Känsliga tittare varnas, det finns ingen avancerad layout på sidorna utan vi pratar ren HTML, tänk sent 90-tal. Men vill man slänga på en CSS som gör det lite snyggare är det förstås valfritt.

Först kommer front-enden. Den ska ligga i /var/www/cgi-bin och har man följt överkursen i Repetition av temperatur fungerar det direkt, annars får man gå tillbaka dit och fixa så att Apache pratar Python.

#!/usr/bin/python

# Import needed libraries
import mysql.connector
import cgi, cgitb 
import sys
import math

# Initalize values
konvKelvin = 0
konvR = 0
konvG = 0
konvB = 0

# Open the connection to the database
mydb = mysql.connector.connect(
  host="localhost",
  user="username",
  passwd="password",
  database="aqua-light"
)

# Start output of HTML
print("Content-Type: text/html")
print()
print("<html><head><title>Aqua-Light för kar 3</title></head><body>")

# Check if something is passed to the script
form = cgi.FieldStorage()

# Check mode switch and shortcuts
if (form.getvalue('mode_select') == 'Automatiskt'):
	sql = "UPDATE config SET Value = 1 WHERE Name = 'automatic'"
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()
if (form.getvalue('mode_select') == 'Manuellt'):
	sql = "UPDATE config SET Value = 0 WHERE Name = 'automatic'"
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit() 
if (form.getvalue('max_select') == 'Max'):
	sql = "UPDATE config SET Value = 0 WHERE Name = 'automatic'"
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()
	sql = "UPDATE manual SET R = 255, G = 255, B = 255, I = 255 WHERE ID = " + form.getvalue("ID")
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()
if (form.getvalue('min_select') == 'Min'):
        sql = "UPDATE config SET Value = 0 WHERE Name = 'automatic'"
        mycursor = mydb.cursor()
        mycursor.execute(sql)
        mydb.commit()
        sql = "UPDATE manual SET R = 0, G = 0, B = 0, I = 0 WHERE ID = " + form.getvalue("ID")
        mycursor = mydb.cursor()
        mycursor.execute(sql)
        mydb.commit()

# Save new manual values
if (form.getvalue('save_manual') == 'Spara'):
	sql = "UPDATE manual SET R = " + form.getvalue('R')
	sql = sql + ", G = " + form.getvalue('G')
	sql = sql + ", B = " + form.getvalue('B')
	sql = sql + ", I = " + form.getvalue('I')
	sql = sql + " WHERE ID = " + form.getvalue('ID')
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()

# Add new row for automatic values
if (form.getvalue('auto_addnew') == 'Skapa'):
	sql = "INSERT INTO basedata (H, M, S, R, G, B, I) VALUES ("
	sql = sql + form.getvalue('autoH')
	sql = sql + ", "+ form.getvalue('autoM')
	sql = sql + ", "+ form.getvalue('autoS')
	sql = sql + ", "+ form.getvalue('autoR')
	sql = sql + ", "+ form.getvalue('autoG')
	sql = sql + ", "+ form.getvalue('autoB')
	sql = sql + ", "+ form.getvalue('autoI')
	sql = sql + ")"
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()

# Delete row for automatic values
if (form.getvalue('auto_del') == 'Ta bort'):
	sql = "DELETE FROM basedata WHERE ID=" + form.getvalue('ID')
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()

# Change row for automatic values
if (form.getvalue('auto_change') == 'Modifiera'):
	sql = "UPDATE basedata SET"
	sql = sql + " H=" + form.getvalue('autoH')
	sql = sql + ", M="+ form.getvalue('autoM')
	sql = sql + ", S="+ form.getvalue('autoS')
	sql = sql + ", R="+ form.getvalue('autoR')
	sql = sql + ", G="+ form.getvalue('autoG')
	sql = sql + ", B="+ form.getvalue('autoB')
	sql = sql + ", I="+ form.getvalue('autoI')
	sql = sql + " WHERE ID=" + form.getvalue('ID')
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()

# Fill the database
if (form.getvalue('fill_db') == 'Fyll databasen'):
	sql = "DELETE FROM automatic"
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	mydb.commit()
	Sec = 0
	R = 0
	G = 0
	B = 0
	I = 0
	sql = "SELECT * FROM basedata ORDER BY H, M, S"
	mycursor = mydb.cursor()
	mycursor.execute(sql)
	myresult = mycursor.fetchall()
	for result in myresult:
		targetSec = result[1] * 3600 + result[2] * 60 + result[3]
		deltaR = (result[4] - R) / (targetSec - Sec)
		deltaG = (result[5] - G) / (targetSec - Sec)
		deltaB = (result[6] - B) / (targetSec - Sec)
		deltaI = (result[7] - I) / (targetSec - Sec)
		for n in range(Sec, targetSec):
			sql = "INSERT INTO automatic (Sec, R, G, B, I) VALUES ("
			sql = sql + str(n)
			sql = sql + ", " + str(int(R))
			sql = sql + ", " + str(int(G))
			sql = sql + ", " + str(int(B))
			sql = sql + ", " + str(int(I)) + ")"
			mycursor = mydb.cursor()
			mycursor.execute(sql)
			mydb.commit()
			R = R + deltaR
			G = G + deltaG
			B = B + deltaB
			I = I + deltaI
		R = result[4]
		G = result[5]
		B = result[6]
		I = result[7]
		Sec = targetSec
	targetSec = 86400
	deltaR = (0 - R) / (targetSec - Sec)
	deltaG = (0 - G) / (targetSec - Sec)
	deltaB = (0 - B) / (targetSec - Sec)
	deltaI = (0 - I) / (targetSec - Sec)
	for n in range(Sec, 86400):
		sql = "INSERT INTO automatic (Sec, R, G, B, I) VALUES ("
		sql = sql + str(n)
		sql = sql + ", " + str(int(R))
		sql = sql + ", " + str(int(G))
		sql = sql + ", " + str(int(B))
		sql = sql + ", " + str(int(I)) + ")"
		mycursor = mydb.cursor()
		mycursor.execute(sql)
		mydb.commit()
		R = R + deltaR
		G = G + deltaG
		B = B + deltaB
		I = I + deltaI

# Konvert K to RGB
if (form.getvalue('save_konvert') == 'Konvertera'):
	konvKelvin = float(form.getvalue('konvKelvin'))
	if konvKelvin < 1000:
		konvKelvin = 1000
	if konvKelvin > 40000:
		konvKelvin = 40000
	tmpKelvin = konvKelvin / 100
	if tmpKelvin <= 66:
		konvR = 255
	else:
		tmpCalc = tmpKelvin - 60
		tmpCalc = 329.698727446 * (tmpCalc ** -0.1332047592)
		konvR = int(tmpCalc)
		if konvR < 0: konvR = 0
		elif konvR > 255: konvR = 255
	if tmpKelvin <= 66:
		tmpCalc = tmpKelvin
		tmpCalc = 99.4708025861 * math.log(tmpCalc) - 161.1195681661
		konvG = int(tmpCalc)
	else:
		tmpCalc = tmpKelvin - 60
		tmpCalc = 288.1221695283 * (tmpCalc ** -0.0755148492)
		konvG = int(tmpCalc)
	if konvG < 0: konvG = 0
	elif konvG > 255: konvG = 255
	if tmpKelvin >= 66:
		konvB = 255
	elif tmpKelvin <= 19:
		konvB = 0
	else:
		tmpCalc = tmpKelvin - 10
		tmpCalc = 138.5177312231 * math.log(tmpCalc) - 305.0447927307
		konvB = int(tmpCalc)
		if konvB < 0: konvB = 0
		elif konvB > 255: konvB = 255

# Show mode of operation
mycursor = mydb.cursor()
mycursor.execute("SELECT * FROM config WHERE Name='automatic'")
myresult = mycursor.fetchone()
print("<h1>Aktuellt läge: ")
if (int(myresult[2]) == 0): 
	print("Manuell styrning")
	mode = 0
else: 
	print("Automatiskt")
	mode = 1
print("</h1>")

# Fetch manual values
mycursor = mydb.cursor()
mycursor.execute("SELECT * FROM manual")
myresult = mycursor.fetchone()
manID = str(myresult[0])
manR = str(myresult[1])
manG = str(myresult[2])
manB = str(myresult[3])
manI = str(myresult[4])

# Selection of mode and shortcuts
print("<table><tr>")
strHTML = "<td><form action='light.py' method='post'><input type='submit' name='mode_select' value='"
if (mode == 0): strHTML = strHTML + "Automatiskt"
else: strHTML = strHTML + "Manuellt"
strHTML = strHTML + "'></form></td>"
print(strHTML)
print("<td><form action='light.py' method='post'><input type='hidden' name='ID' value='"+ manID + "'><input type='submit' name='max_select' value='Max'></form></td>")
print("<td><form action='light.py' method='post'><input type='hidden' name='ID' value='"+ manID + "'><input type='submit' name='min_select' value='Min'></form></td>")
print("</tr></table>")

# Manual values
print("<h2>Inställningar för manuellt läge:</h2>")
print("<form action='light.py' method='post'><input type='hidden' name='ID' value='{}'><table>".format(manID))
print("<tr><th>R</th><th>G</th><th>B</th><th>I</th><th></th></tr><tr>")
print("<td><input type='text' maxlength=3 size=3 name='R' value='{}'></td>".format(manR))
print("<td><input type='text' maxlength=3 size=3 name='G' value='{}'></td>".format(manG))
print("<td><input type='text' maxlength=3 size=3 name='B' value='{}'></td>".format(manB))
print("<td><input type='text' maxlength=3 size=3 name='I' value='{}'></td>".format(manI))
print("<td><input type='submit' name='save_manual' value='Spara'></td>")
print("</tr></table></form>")

# Scheme for automatic mode
print("<h2>Schema för automatiskt läge</h2>")
print("<table>")
print("<tr><th>H</th><th>M</th><th>S</th><th>R</th><th>G</th><th>B</th><th>I</th><th></th></tr>")

sql = "SELECT * FROM basedata ORDER BY H, M, S"
mycursor = mydb.cursor()
mycursor.execute(sql)
myresult = mycursor.fetchall()
for result in myresult:
	print("<form action='light.py' method='post'><tr><td><input type='hidden' name='ID' value='{}'><input type='text' size=2 maxlength=2 name='autoH' value='{}'></td><td><input type='text' size=2 maxlength=2 name='autoM' value='{}'></td><td><input type='text' size=2 maxlength=2 name='autoS' value='{}'></td><td><input type='text' size=3 maxlength=3 name='autoR' value='{}'></td><td><input type='text' size=3 maxlength=3 name='autoG' value='{}'></td><td><input type='text' size=3 maxlength=3 name='autoB' value='{}'></td><td><input type='text' size=3 maxlength=3 name='autoI' value='{}'></td><td><input type='submit' name='auto_change' value='Modifiera'><input type='submit' name='auto_del' value='Ta bort'></td></tr></form>".format(result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7]))

print("<form action='light.py' method='post'><tr><td><input type='text' size=2 maxlength=2 name='autoH'></td><td><input type='text' size=2 maxlength=2 name='autoM'></td><td><input type='text' size=2 maxlength=2 name='autoS'></td><td><input type='text' size=3 maxlength=3 name='autoR'></td><td><input type='text' size=3 maxlength=3 name='autoG'></td><td><input type='text' size=3 maxlength=3 name='autoB'></td><td><input type='text' size=3 maxlength=3 name='autoI'></td><td><input type='submit' name='auto_addnew' value='Skapa'></td></tr></form>")
print("</table>")


print("<form action='light.py' method='post'><input type='submit' name='fill_db' value='Fyll databasen'></form>")

# Kelvin to RGB converter
print("<h2>Kelvin till RGB</h2>")
print("<form action='light.py' method='post'><table>")
print("<tr><th>Kelvin</th><th>R</th><th>G</th><th>B</th><th></th></tr>")
print("<tr><td><input type='text' name='konvKelvin' value='{}' maxlength=7 size=7></td><td>{}</td><td>{}</td><td>{}</td><td><input type='submit' name='save_konvert' value='Konvertera'></td></tr>".format(konvKelvin, konvR, konvG, konvB))
print("</table></form>")

print("</body></html>")
mydb.close

Sedan kommer back-end, betydligt kortare. Den kan man placera var man vill, jag har min i /usr/local/bin/aquacontrol.

#!/usr/bin/python

# Import needed libraries
import mysql.connector
import socket
import time
import datetime

# Initalize values
NUM_LEDS = 588;
LED_TYPE = 3;

# Open the connection to the database
mydb = mysql.connector.connect(
  host="localhost",
  user="username",
  passwd="password",
  database="aqua-light"
)

# Initialize the LED server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 9999))
message = "setup 1," + str(NUM_LEDS) + "," + str(LED_TYPE) + ";init;"
s.send(message.encode())

# Main loop
while True:
	# Check running mode in database
	mycursor = mydb.cursor()
	mycursor.execute("SELECT * FROM config WHERE Name='automatic'")
	myresult = mycursor.fetchone()
	mydb.commit()
	if (int(myresult[2]) == 0):
		# manual mode
		mycursor = mydb.cursor()
		mycursor.execute("SELECT * FROM manual")
		myresult = mycursor.fetchone()
		mydb.commit()
		message = "fill 1,"+format(myresult[1], "02x")+format(myresult[2], "02x")+format(myresult[3], "02x")+";brightness 1,"+str(myresult[4])+";render;"
		s.send(message.encode())
	else:
		# automatic mode
		now = datetime.datetime.now()
		midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
		seconds = (now - midnight).seconds	
		mycursor = mydb.cursor()
		mycursor.execute("SELECT * FROM automatic WHERE Sec=" + str(seconds))
		myresult = mycursor.fetchone()
		mydb.commit()	
		message = "fill 1,"+format(myresult[2], "02x")+format(myresult[3], "02x")+format(myresult[4], "02x")+";brightness 1,"+str(myresult[5])+";render;"
		s.send(message.encode())

	time.sleep(1)

För att back-end ska starta automatiskt behöver man skapa filen aqua-light.service i /lib/systemd/system med följande innehåll:

[Unit]
Description=Aqua-Light
After=multi-user.target
 
[Service]
Type=idle
ExecStart=/usr/local/bin/aquacontrol/aqua-light.py
 
[Install]
WantedBy=multi-user.target

Kör sedan

sudo chmod +x /usr/local/bin/aquacontrol/aqua-light.py 
sudo chmod 644 /lib/systemd/system/aqua-light.service 
sudo systemctl daemon-reload 
sudo systemctl enable aqua-light.service

så ska det starta automatiskt vid omstart. Att fylla tabellen med de 86400 raderna för den automatiska styrningen tar närmare en halvtimma på min Raspberry Pi. För att inte Apache ska tröttna på att vänta har jag lagt till

TimeOut 2400

i /etc/apache2/sites-available/000-default.conf. När man surfar till http://adress_till_din_paj/cgi-bin/light.py ser det ut ungefär som följer:

I mitt fall ovan börjar det hända saker 11:00:00 då det fram till 11:10:00 tänds ett månljus som är tänt till 12:00:00. Då byter det till en soluppgång fram till 12:10:00 som sedan tonar upp till full styrka fram till 13:00:00. Det är tänt fram till 21:00:00 då en solnedgång börjar och håller på till 21:50:00. Då byter den till ett månljus under tio minuter fram till 22:00:00. Detta är tänt till 22:50:00 då det börjar tona ut till helt släckt 23:00:00.

Share

Grundläggande ljusstyrning

För att enkelt kunna styra LED-listerna från olika miljöer har jag valt att använda en server som man kan skicka kommandon till. Den finns på https://github.com/tom-2015/rpi-ws2812-server och installeras enligt följande.

Först måste vi slå av ljudfunktionerna i vår paj då det är den PWM-utgången (normalt GPIO 18) som används. Lägg till

blacklist snd_bcm2835

i /etc/modprobe.d/snd-blacklist.conf (skapa filen om den inte finns) och kommentera bort

# Enable audio (loads snd_bcm2835)
# dtparam=audio=on

i /boot/config.txt. Starta sedan om pajen.

Nu kan man installera servern med

sudo apt-get update
sudo apt-get install git
cd /usr/local/bin
sudo git clone https://github.com/tom-2015/rpi-ws2812-server.git
cd rpi-ws2812-server
sudo make NO_JPEG=1 NO_PNG=1
sudo chmod +x ws2812svr

För att servern ska starta automatiskt får man skapa filen ws2812svr.service i /lib/systemd/system med följande innehåll:

[Unit]
Description=ws2812svr
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/local/bin/rpi-ws2812-server/ws2812svr -tcp 9999

[Install]
WantedBy=multi-user.target

Kör sedan

sudo chmod 644 /lib/systemd/system/ws2812svr.service
sudo systemctl daemon-reload
sudo systemctl enable ws2812svr.service

så ska den starta automatiskt vid omstart. För att testa skriver man

nc 127.0.0.1 9999

och kan sedan skicka kommandon till servern.

Share