From: Brian Flowers Date: Thu, 6 May 2021 01:52:30 +0000 (-0400) Subject: Added tower details X-Git-Url: http://git.slightlycyberpunk.com%2C%20git.slightlycyberpunk.com/git/?a=commitdiff_plain;h=db266e0464f67e8b2658d9c06b020720d5f97cfe;p=JarLampSystem.git Added tower details --- diff --git a/Tower/ClampMount.scad b/Tower/ClampMount.scad new file mode 100644 index 0000000..f0909af --- /dev/null +++ b/Tower/ClampMount.scad @@ -0,0 +1,58 @@ +/*## COPYRIGHT NOTICE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# +# COPYRIGHT 2020 : Slightly Cyberpunk +# www.slightlycyberpunk.com +# +# + +### DESCRIPTION +# +# +# Mount a 1/4" threaded rod by clamping against a dresser top +*/ + +$fn=180; + +difference() { + cube([20,32,60]); + translate([4,5,0]) { + cube([20,22,60]); + } +} + +translate([0,-10,30]) { + rotate([0,90,0]) { + difference() { + cylinder(r=13,h=20); + cylinder(r=11.5,h=10,$fn=6); + cylinder(r=7,h=20); + } + } +} + +translate([50,0,0]) { + cube([5,50,60]); + translate([5,-10,30]) { + rotate([0,-90,0]) { + difference() { + cylinder(r=13,h=20); + cylinder(r=11.5,h=10,$fn=6); + cylinder(r=7,h=20); + } + } + } +} \ No newline at end of file diff --git a/Tower/ElectronicsMount.scad b/Tower/ElectronicsMount.scad new file mode 100644 index 0000000..df97267 --- /dev/null +++ b/Tower/ElectronicsMount.scad @@ -0,0 +1,178 @@ +/*## COPYRIGHT NOTICE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# +# COPYRIGHT 2020 : Slightly Cyberpunk +# www.slightlycyberpunk.com +# +# + +### DESCRIPTION +# +# +# Clamps a heatsink against a high power LED +# A piece of protoboard mounts on the top; the bottom mounts +# to the lid of the jar +# +*/ + +$fn=60; + +// === Main model === +rhalf(); +lhalf(); + +// === Fitment preview === +/* +lid(); +led(); +heatsink(); +*/ + +// === MODULES === +// Lid +module lid() { + translate([0,0,-5]) { + cylinder(r=43,h=4); + } +} + +// LED +module led() { + translate([0,0,-1]) { + cylinder(r=4,h=1); + } + cylinder(r=10,h=2); +} + +// Heatsink +module heatsink() { + translate([-8.25,-13,2]) { + cube([16.5,26,4]); + cube([1.5,26,16.5]); + translate([5,0,0]) { + cube([1.5,26,16.5]); + } + translate([10,0,0]) { + cube([1.5,26,16.5]); + } + translate([15,0,0]) { + cube([1.5,26,16.5]); + } + } +} + +// Heatsink "fingers" +module fingers() { + translate([3.5,-15,6]) { + cube([3,10,3]); + } + translate([-1.5,-15,6]) { + cube([3,10,3]); + } + translate([-6.5,-15,6]) { + cube([3,10,3]); + } + translate([3.5,5,6]) { + cube([3,10,3]); + } + translate([-1.5,5,6]) { + cube([3,10,3]); + } + translate([-6.5,5,6]) { + cube([3,10,3]); + } +} + +// Resistor tray +module tray() { + translate([-26,-18,20]) { + difference() { + cube([52,36,4]); + translate([2,2,2]) { + cube([48,32,2]); + } + } + } + + translate([5,-18,24]) { + difference() { + cube([10,36,10]); + translate([0,2,0]) { + cube([10,32,8]); + } + translate([0,16,0]) { + cube([10,4,10]); + } + } + } + translate([-15,-18,24]) { + difference() { + cube([10,36,10]); + translate([0,2,0]) { + cube([10,32,8]); + } + translate([0,16,0]) { + cube([10,4,10]); + } + } + } +} + +// Side walls +module swalls() { + translate([0,19,0]) { + difference() { + union() { + translate([-6,-5,6]) { + cube([12,5,10]); + } + cylinder(r=5,h=22); + } + cylinder(r=2,h=22); + } + } + + translate([0,-19,0]) { + difference() { + union() { + translate([-6,0,6]) { + cube([12,5,10]); + } + cylinder(r=5,h=22); + } + cylinder(r=2,h=22); + } + } +} + +module rhalf() { + intersection() { + translate([-50,0,0]) { + cube([100,40,50]); + } + union() { + fingers(); + swalls(); + } + } +} + +module lhalf() { + union() { + fingers(); + swalls(); + } +} \ No newline at end of file diff --git a/Tower/LampMount.scad b/Tower/LampMount.scad new file mode 100644 index 0000000..cf391ba --- /dev/null +++ b/Tower/LampMount.scad @@ -0,0 +1,89 @@ +/*## COPYRIGHT NOTICE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# +# COPYRIGHT 2020 : Slightly Cyberpunk +# www.slightlycyberpunk.com +# +# + +### DESCRIPTION +# +# +# The ring fits around the lid of an Ovaltine jar; the mount holds +# a bolt to mount to a 1/4" threaded rod. The ring can mount +# vertical or horizontal +*/ + +$fn=60; + +// === Main model === +postMount(); +translate([0,50,0]) { + lampMount(); +} + +// === MODULES === +module postMount() { + difference() { + union() { + translate([0,12,0]) { + cube([24,18,24]); + } + translate([12,12,0]) { + cylinder(r=12,h=24); + } + } + translate([12,12,0]) { + cylinder(r=11,h=12, $fn=6); + } + translate([12,12,0]) { + cylinder(r=6.3,h=25); + } + + translate([12,28,19]) { + rotate([90,0,0]) { + cylinder(r=8,h=4); + } + } + translate([6,24,6]) { + cube([12,4,18]); + } + translate([12,30,12]) { + rotate([90,0,0]) { + cylinder(r=3,h=4); + } + } + translate([9,26,12]) { + cube([6,4,18]); + } + } +} + +module lampMount() { + cube([12,4,12]); + translate([6,8,6]) { + rotate([90,0,0]) { + cylinder(r=3,h=4); + } + } + + translate([0,52,0]) { + difference() { + cylinder(r=45,h=10); + cylinder(r=43.4,h=10); + } + } +} \ No newline at end of file diff --git a/Tower/chatter.py b/Tower/chatter.py new file mode 100755 index 0000000..5684760 --- /dev/null +++ b/Tower/chatter.py @@ -0,0 +1,532 @@ +#!/usr/bin/python3 +### COPYRIGHT NOTICE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# +# COPYRIGHT 2020 : Slightly Cyberpunk +# www.slightlycyberpunk.com +# +# + +### DESCRIPTION +# +# Small script to control multiple jar lamps, with lamp colors +# being driven by system statuses which are read from a file +# that is written by a separate application (CNS). Lamps are +# connected to a Raspberry Pi through an sx1509 i/o expander +# which connects via i2c + +import RPi.GPIO as GPIO +import sys +import threading +from time import sleep +from time import strftime +import time +import SX1509 +import IO_Types +from adafruit_bus_device.i2c_device import I2CDevice +import board +import busio +from itertools import chain + +### CONFIGURATION VARIABLES + +# Define the lamps +# Each lamp is an array of pins, R/G/B +# Lamps should be active when pins are LOW +# These are pins on the sx1509 board, NOT on the Pi +pins = [[4,5,6],[2,1,0],[10,9,8]] + +# Pin compensation +# My first few lamps were built with whatever I had laying around +# Not all the resistors are the same value, so the brightness varies +# These arrays allow tuning brightness compensation for each lamp +# pincomp is a static value added to each pin +# pinmul is a multiplier applied to each pin +pincomp = [[100,100,100],[70,70,70],[90,90,90]] +pinmul = [[0.5,0.5,0.5],[0.5,0.5,0.5],[0.5,0.5,0.5]] + +# An array of expected hosts +# This is used to map hosts to colors +# It is a 2D array, and each host has 2 colors +# Recommend the sub-arrays are grouped by type +# None of the arrays can be longer than the colorInts array +hostList = [ [ "category1-host1", "category1-host2" ], + [ "category2-host1", "category2-host2" ] + ] +sensorlist = [ "audio", "camera", "PRESENCE", "sessions", "sensors" ] + +# Files where the lamp data will be written +sysfilename = "/home/pi/framework/lamp.lst" +secfilename = "/home/pi/framework/lampsec.lst" + +### GLOBAL VARIABLES +# Device address of sx1509 +DEVICE_ADDRESS = 0x3E + +#Set up I2C +comm_port = busio.I2C(board.SCL, board.SDA) +device = I2CDevice(comm_port, DEVICE_ADDRESS) + +#Initialize the expander +IOExpander = SX1509.SX1509(comm_port) +IOExpander.clock(oscDivider = 4) +IOExpander.debounceTime(32) + +#Set up pins +for set in pins: + for pin in set: + IOExpander.pinMode(pin, IO_Types.PIN_TYPE_ANALOG_OUTPUT) + +# Global status variables +# Maximum lamp brightness as a value between 0 and 1 +brightness = 1 +# Current color of each lamp +colors = [[0,0,0],[0,0,0],[0,0,0]] +# Flags for halting threads +hold = 0 +stop = 0 +# Array specifying which thread is currently allowed to control each lamp +controlThreads = [] +# Constant color list (for int to color conversions) +colorInts = [ [100,0,0], + [100,50,0], + [100,100,0], + [0,100,0], + [0,50,100], + [0,100,100], + [0,0,100], + [100,0,100], + [100,100,100] ] + +### FUNCTIONS +# Set a particular lamp to a particular color +# PARAMETERS: lamp = which lamp to set (integer) +# thread = thread ID which is sending this command (integer) +# r = red component OR array of three colors +# g/b = green/blue components (if r is not an array) +def setColor(lamp,thread,r,g=-1,b=-1): + global controlThreads + if thread != controlThreads[lamp]: + return + if g == -1: + setColor(lamp,thread,r[0],r[1],r[2]) + else: + global colors + colors[lamp] = [r,g,b] + set = pins[lamp] + comp = pincomp[lamp] + mul = pinmul[lamp] + color = [r,g,b] + for j in range(0,3): + color[j] = int(color[j] * brightness * float(mul[j]) + int(comp[j])) + IOExpander.analogWrite(set[j], color[j]) + +# Slowly fade a particular lamp to a particular color +# PARAMETERS: lamp = which lamp to set (integer) +# thread = which thread is sending the command (integer) +# speed = speed of the fade (delay between steps; should be a small decimal) +# dip = 1 to fade total brightness between color; 0 to keep total brightness constant +# (this is particularly helpful if both colors are the same or similar) +# r/g/b = red/green/blue (or just r as an array) +def fadeTo(lamp,thread,speed, dip, r,g=-1,b=-1): + if g == -1: + fadeTo(lamp,thread,speed, dip, r[0],r[1],r[2]) + else: + fade(lamp,thread,speed, colors[lamp], [r, g, b], dip) + +def fade(lamp,thread,speed, c1, c2, dip): + for a in range(0,100): + if dip == 1: + if a < 10: + pct1 = (90-a)/90 + pct2 = 0 + elif a < 90: + pct1 = (90-a)/90 + pct2 = (a-10)/90 + else: + pct1 = 0 + pct2 = (a-10)/90 + + else: + pct1 = (100-a)/100 + pct2 = (a)/100 + + + setColor(lamp,thread,c1[0]*pct1+c2[0]*pct2, c1[1]*pct1+c2[1]*pct2, c1[2]*pct1+c2[2]*pct2) + sleep(speed) + +# Old "loading" function -- cycles through a couple colors +#def loading(speed, cycles, lamp): +# for red in range(0,100): +# setColor(lamp,red,0,0) +# sleep(speed) +# for x in range(0,cycles): +# for green in range(0,100): +# setColor(lamp,100-green,green,0) +# sleep(speed) +# for blue in range(0,100): +# setColor(lamp,0,100-blue,blue) +# sleep(speed) +# for red in range(0,100): +# setColor(lamp,red,0,100-red) +# sleep(speed) +# for red in range(0,100): +# setColor(lamp,100-red,0,0) +# sleep(speed) +# +# Old "rainbow" function -- cycles through rainbow colors +#def rainbow(speed, cycles, lamp): +# for red in range(0,100): +# setColor(lamp,red,0,0) +# sleep(speed) +# sleep(1) +# for x in range(0,cycles): +# for orange in range(0,50): +# setColor(lamp,100,orange*0.9,0) +# sleep(speed*2) +# sleep(1) +# for yellow in range(0,50): +# setColor(lamp,100,50+yellow,0) +# sleep(speed*2) +# sleep(1) +# for green in range(0,100): +# setColor(lamp,100-green,100,12-0.12*green) +# sleep(speed) +# sleep(1) +# for blue in range(0,100): +# setColor(lamp,0,100-blue,blue) +# sleep(speed) +# sleep(1) +# for purple in range(0,100): +# setColor(lamp,purple,0,100) +# sleep(speed) +# sleep(1) +# for red in range(0,100): +# setColor(lamp,100,0,100-red) +# sleep(speed) +# sleep(1) +# for clear in range(0,100): +# setColor(lamp,100-clear,0,0) +# sleep(speed) + +# Looping fade between two colors +# PARAMETERS: lamp = which lamp to set (integer) +# thread = which thread is sending the command (integer) +# speed = speed of the fade (delay between steps; should be a small decimal) +# cycles = number of cycles to repeat +# c1 = color 1 (array of r/g/b) +# c2 = color 2 (array of r/g/b) +# dip = 1 to fade total brightness between color; 0 to keep total brightness constant +def pair(lamp,thread,speed, cycles, c1, c2, dip): + global hold + for x in range(0,cycles): + if hold == 0: + fadeTo(lamp,thread,speed, dip, c1) + sleep(0.7) + fadeTo(lamp,thread,speed, dip, c2) + sleep(0.3) + +# Looping fade between three colors +# PARAMETERS: lamp = which lamp to set (integer) +# thread = which thread is sending the command (integer) +# speed = speed of the fade (delay between steps; should be a small decimal) +# cycles = number of cycles to repeat +# c1 = color 1 (array of r/g/b) +# c2 = color 2 (array of r/g/b) +# c3 = color 3 (array of r/g/b) +# dip = 1 to fade total brightness between color; 0 to keep total brightness constant +def trio(lamp,thread,speed, c1,c2,c3, dip): + global stop + if stop == 0: + fadeTo(lamp, thread, speed, dip, c1) + sleep(0.3) + fadeTo(lamp, thread, speed, dip, [int(c2[0]*0.9), int(c2[1]*0.9), int(c2[2]*0.9)]) + sleep(0.3) + fadeTo(lamp, thread, speed, dip, c3) + sleep(0.3) + +# Try to display the opposite color as a specified set of lamps +# PARAMETERS: lamp = which lamp to set (integer) +# delay = delay between updating the color (ie, speed of fade) +# threadid = which thread is sending the command (integer) +# opplamps = array of lamp IDs to oppose +def opposeChatter(lamp,delay,threadid, opplamps): + global stop + global colors + while stop == 0: + color = [200,200,200] + for i in opplamps: + color[0] = color[0] - colors[i][0] + color[1] = color[1] - colors[i][1] + color[2] = color[2] - colors[i][2] + setColor(lamp, threadid, color) + sleep(delay) + +# Use a lamp to display the status of security sensors +# PARAMETERS: lamp = which lamp to set (integer) +# delay = delay between updating the color (ie, speed of fade) +# cycles = number of cycles to repeat each measurement +# threadid = which thread is sending the command (integer) +def securityChatter(lamp,delay, cycles, threadid): + global stop + global secdata + while stop == 0: + for i in range(0,len(secdata)): + for j in range(0, len(secdata[i])): + if stop == 1: + break + colors = secdata[i][j] + if colors is None or colors == "" or colors == []: + continue + trio(lamp,threadid,delay, colorInts[i],colorInts[j],colors, 0) + fadeTo(lamp,threadid,delay,0, 100,100,100) + +# Use a lamp to display the status of servers and systems +# PARAMETERS: lamp = which lamp to set (integer) +# delay = delay between updating the color (ie, speed of fade) +# cycles = number of cycles to repeat each measurement +# threadid = which thread is sending the command (integer) +def systemsChatter(lamp,delay, cycles, threadid): + global stop + while stop == 0: + for i in range(0,len(sysdata)): + if stop == 1: + break + if sysdata[i] is None or sysdata[i] == '': + continue + if sysdata[i].cpu == -1: + continue + if sysdata[i].mem == -1: + continue + if sysdata[i].fs == -1: + continue + + r = sysdata[i].mem + g = sysdata[i].fs + b = sysdata[i].cpu + + for j in range(0,len(hostList)): + if sysdata[i].host in hostList[j]: + id1 = colorInts[j] + id2 = colorInts[hostList[j].index(sysdata[i].host)] + trio(lamp,threadid,delay, id1,id2,[r,g,b], 0) + fadeTo(lamp,threadid,delay,0, 100,100,100) + +# "Gasp" a lamp -- fade to black,then fade to a particular color +# (used for summary statuses) +# PARAMETERS: lamp = which lamp to set (integer) +# delay = delay between updating the color (ie, speed of fade) +# r/g/b = color to 'gasp' with +# threadid = which thread is sending the command (integer) +def gasp(lamp,delay, r, g, b, threadid): + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(0.3) + fadeTo(lamp,threadid,delay,0, r,g,b) + sleep(1) + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(0.3) + +# "Chime" a lamp -- fade to black, then pulse to count the hour +# PARAMETERS: lamp = which lamp to set (integer) +# delay = delay between updating the color (ie, speed of fade) +# threadid = which thread is sending the command (integer) +def chime(lamp,delay, threadid): + hours = int(strftime("%I")) + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(2) + for i in range(0,hours): + fadeTo(lamp,threadid,delay,0, 100,100,100) + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(0.1) + sleep(1) + +# Load the system data from a file +# Format of this file is: hostname|key|value +syssumm = [0,0,0] +sysdata = [] +def refreshSysData(): + global syssumm + global sysdata + sysdata = [type('',(object,),{"cpu": -1, "mem": -1, "fs": -1})() for i in range(0,25)] + syssumm = [0,0,0] + lampfile = open(sysfilename, "r") + lampdata = lampfile.read() + lampdata = lampdata.split("\n") + for i in range(0,len(lampdata)): + if lampdata[i] == "": + continue + + line = lampdata[i].split("|") + + host = 0 + for j in range(0,len(hostList)): + if line[0] in hostList[j]: + host += hostList[j].index(line[0]) + else: + host += len(hostList[j]) + + sysobj = sysdata[host] + sysobj.host = line[0] + + if line[1] == "cpu": + sysobj.cpu = int(line[2]) + elif line[1] == "mem": + sysobj.mem = int(float(line[2])*100) + elif 'fs' in line[1] and sysobj.fs < int(line[2]): + sysobj.fs = int(line[2]) + + sysdata[host] = sysobj; + + warn = 0 + crit = 0 + for i in range(0,len(sysdata)): + if sysdata[i].cpu > 50 or sysdata[i].mem > 50 or sysdata[i].fs > 70: + warn+=1 + if sysdata[i].cpu > 85 or sysdata[i].mem > 85 or sysdata[i].fs > 90: + crit+=1 + if crit > 0: + syssumm = [100,0,0] + elif warn > 0: + syssumm = [100,100,0] + else: + syssumm = [0,100,0] + +seccnts = [0,0,0,0] +secsumm = [100,100,0] +flathostList = list(chain.from_iterable(hostList)) +secdata = [["" for j in range(0, len(flathostList))] for i in range(0, len(sensorlist))] +# Load the security data from a file +# Format of this file is: sensor|hostname|level|timestamp +def refreshSecData(): + global secsumm + global secdata + + lampfile = open(secfilename, "r") + lampdata = lampfile.read() + lampdata = lampdata.split("\n") + for line in lampdata: + if line == "": + continue + + print(line) + + line = line.split("|") + sensor = line[0] + host = line[1] + value = line[2] + timestamp = int(line[3]) + tdiff = timestamp - int(time.time()) - 100 + if tdiff > 100: + tdiff = 100 + if tdiff < 0: + tdiff = 0 + + if value == "WARN": + secdata[sensorlist.index(sensor)][flathostList.index(host)] = [100-tdiff, 100, 0] + if secsumm[2] > tdiff: + secsumm[2] = tdiff + elif value == "CRIT": + secdata[sensorlist.index(sensor)][flathostList.index(host)] = [100-tdiff, tdiff, 0] + if secsumm[1] > tdiff: + secsumm[1] = tdiff + elif value == "OK": + secdata[sensorlist.index(sensor)][flathostList.index(host)] = [tdiff, 100-tdiff, 0] + if secsumm[1] > 0: + secsumm = [100 - secsumm[1], secsumm[1], 0] + elif secsumm[2] > 0: + secsumm = [100 - secsumm[2], 100, 0] + else: + secsumm = [0, 100, 0] + +mode="off" +# Set the lamp mode from the file +# Modes can be "on", "off", or "chatter" +def refreshMode(): + global mode + modefile = open("mode", "r") + modedata = modefile.read() + mode = modedata.split("\n")[0] + +### MAIN SCRIPT START ### +try: + refreshSysData() + refreshSecData() + refreshMode() + controlThreads = [0,1,2] + chat = [ threading.Thread(target=systemsChatter,args=(0,0.02,2,0)), + threading.Thread(target=securityChatter,args=(1,0.022,2,1)), + threading.Thread(target=opposeChatter,args=(2,0.015,2,[0,1])) ] + chat[0].start() + chat[1].start() + chat[2].start() + + while stop == 0: + if mode == "off": + controlThreads = [3,3,3] + setColor(0,3,0,0,0) + setColor(1,3,0,0,0) + setColor(2,3,0,0,0) + elif mode == "on": + controlThreads = [4,4,4] + setColor(0,4,100,100,100) + setColor(1,4,100,100,100) + setColor(2,4,100,100,100) + elif mode == "chatter": + controlThreads = [0,1,2] + if int(strftime("%S")) == 0: + if int(strftime("%M")) == 0: + controlThreads = [10,11,12] + chimes = [ threading.Thread(target=chime,args=(0,0.003,10)), + threading.Thread(target=chime,args=(1,0.003,11)), + threading.Thread(target=chime,args=(2,0.003,12)) ] + chimes[0].start() + chimes[1].start() + chimes[2].start() + chimes[0].join() + chimes[1].join() + chimes[2].join() + controlThreads = [0,1,2] + else: + gasps = [ threading.Thread(target=gasp,args=(0,0.01,syssumm[0],syssumm[1],syssumm[2],20)), + threading.Thread(target=gasp,args=(1,0.01,0,100,0,21)), + threading.Thread(target=gasp,args=(2,0.01,100,0,100,22)) ] + controlThreads = [20,21,22] + gasps[0].start() + gasps[1].start() + gasps[2].start() + gasps[0].join() + gasps[1].join() + gasps[2].join() + controlThreads = [0,1,2] + sleep(1) + refreshSysData() + refreshSecData() + refreshMode() + + print("Waiting for threads to terminate...\n") + chat[0].join() + print("One left...\n") + chat[1].join() + print("DONE!\n") + chat[2].join() + +except KeyboardInterrupt: + stop = 1 + print("Interrupted") + +finally: + GPIO.cleanup() + diff --git a/Tower/chatter.py~ b/Tower/chatter.py~ new file mode 100755 index 0000000..7bf15b2 --- /dev/null +++ b/Tower/chatter.py~ @@ -0,0 +1,473 @@ +#!/usr/bin/python3 +import RPi.GPIO as GPIO +import sys +import threading +from time import sleep +from time import strftime +import time +import SX1509 +import IO_Types +from adafruit_bus_device.i2c_device import I2CDevice +import board +import busio +from itertools import chain + +pins = [[4,5,6],[2,1,0],[10,9,8]] +#pincomp = [[1.2,1.2,1.2],[.7,.7,.7],[1,1,1]] + +DEVICE_ADDRESS = 0x3E # device address of SX1509 + +#Set up I2C +comm_port = busio.I2C(board.SCL, board.SDA) +device = I2CDevice(comm_port, DEVICE_ADDRESS) + +#Initialize the expander +IOExpander = SX1509.SX1509(comm_port) +IOExpander.clock(oscDivider = 4) +IOExpander.debounceTime(32) + +#Set up pins +for set in pins: + for pin in set: + IOExpander.pinMode(pin, IO_Types.PIN_TYPE_ANALOG_OUTPUT) +#print('IO Expander Initialized') + +brightness = 1 +colors = [[0,0,0],[0,0,0],[0,0,0]] +hold = 0 +stop = 0 +controlThreads = [] + +colorInts = [ [100,0,0], + [100,50,0], + [100,100,0], + [0,100,0], + [0,50,100], + [0,100,100], + [0,0,100], + [100,0,100], + [100,100,100] ] + + +def setColor(lamp,thread,r,g=-1,b=-1): + global controlThreads + if thread != controlThreads[lamp]: + return +# print("setting "+str(lamp)+" to "+str([r,g,b])+" by "+str(thread)+" (with "+str(controlThreads)+")") + if g == -1: + setColor(lamp,thread,r[0],r[1],r[2]) + else: + global colors + colors[lamp] = [r,g,b] + pincomp = [1,2,3] + compfile = open("comp", "r") + compdata = compfile.read() + compdata = compdata.split("\n") + for i in range(0,3): + pincomp[i] = compdata[i].split(",") + pinmul = [1,2,3] + mulfile = open("mul", "r") + muldata = mulfile.read() + muldata = muldata.split("\n") + for i in range(0,3): + pinmul[i] = muldata[i].split(",") + + set = pins[lamp] + comp = pincomp[lamp] + mul = pinmul[lamp] + color = [r,g,b] + for j in range(0,3): + color[j] = int(color[j] * brightness * float(mul[j]) + int(comp[j])) + IOExpander.analogWrite(set[j], color[j]) + + +def fadeTo(lamp,thread,speed, dip, r,g=-1,b=-1): + if g == -1: + fadeTo(lamp,thread,speed, dip, r[0],r[1],r[2]) + else: + fade(lamp,thread,speed, colors[lamp], [r, g, b], dip) + +def fade(lamp,thread,speed, c1, c2, dip): + for a in range(0,100): + if dip == 1: +# if a < 33: +# pct1 = (67-a)/67 +# pct2 = 0 +# elif a < 67: +# pct1 = (67-a)/67 +# pct2 = (a-33)/67 +# else: +# pct1 = 0 +# pct2 = (a-33)/67 + if a < 10: + pct1 = (90-a)/90 + pct2 = 0 + elif a < 90: + pct1 = (90-a)/90 + pct2 = (a-10)/90 + else: + pct1 = 0 + pct2 = (a-10)/90 + + else: + pct1 = (100-a)/100 + pct2 = (a)/100 + + + setColor(lamp,thread,c1[0]*pct1+c2[0]*pct2, c1[1]*pct1+c2[1]*pct2, c1[2]*pct1+c2[2]*pct2) + sleep(speed) + + +def loading(speed, cycles, lamp): + for red in range(0,100): + setColor(lamp,red,0,0) + sleep(speed) + for x in range(0,cycles): + for green in range(0,100): + setColor(lamp,100-green,green,0) + sleep(speed) + for blue in range(0,100): + setColor(lamp,0,100-blue,blue) + sleep(speed) + for red in range(0,100): + setColor(lamp,red,0,100-red) + sleep(speed) + for red in range(0,100): + setColor(lamp,100-red,0,0) + sleep(speed) + +def rainbow(speed, cycles, lamp): + for red in range(0,100): + setColor(lamp,red,0,0) + sleep(speed) + sleep(1) + for x in range(0,cycles): + for orange in range(0,50): + setColor(lamp,100,orange*0.9,0) + sleep(speed*2) + sleep(1) + for yellow in range(0,50): + setColor(lamp,100,50+yellow,0) + sleep(speed*2) + sleep(1) + for green in range(0,100): + setColor(lamp,100-green,100,12-0.12*green) + sleep(speed) + sleep(1) + for blue in range(0,100): + setColor(lamp,0,100-blue,blue) + sleep(speed) + sleep(1) + for purple in range(0,100): + setColor(lamp,purple,0,100) + sleep(speed) + sleep(1) + for red in range(0,100): + setColor(lamp,100,0,100-red) + sleep(speed) + sleep(1) + for clear in range(0,100): + setColor(lamp,100-clear,0,0) + sleep(speed) + +def pair(lamp,thread,speed, cycles, c1, c2, dip): + global hold + for x in range(0,cycles): + if hold == 0: + fadeTo(lamp,thread,speed, dip, c1) + sleep(0.7) + fadeTo(lamp,thread,speed, dip, c2) + sleep(0.3) + +def trio(lamp,thread,speed, c1,c2,c3, dip): + global stop + if stop == 0: + fadeTo(lamp, thread, speed, dip, c1) + sleep(0.3) + fadeTo(lamp, thread, speed, dip, [int(c2[0]*0.9), int(c2[1]*0.9), int(c2[2]*0.9)]) + sleep(0.3) + fadeTo(lamp, thread, speed, dip, c3) + sleep(0.3) + +def opposeChatter(lamp,delay,threadid, opplamps): + global stop + global colors + while stop == 0: + color = [200,200,200] + for i in opplamps: + color[0] = color[0] - colors[i][0] + color[1] = color[1] - colors[i][1] + color[2] = color[2] - colors[i][2] + setColor(lamp, threadid, color) + sleep(delay) + +def securityChatter(lamp,delay, cycles, threadid): + global stop + global secdata + while stop == 0: +# secfile = open("security.lst", "r") +# secdata = secfile.read() +# secdataarr = secdata.split("\n") + for i in range(0,len(secdata)): + for j in range(0, len(secdata[i])): + if stop == 1: + break + colors = secdata[i][j] + if colors is None or colors == "" or colors == []: +# print("No color for: "+str(secdata[i])) + continue +# print("pair "+str(lamp)+","+str(threadid)+","+str(delay)+","+str(cycles)+","+str(i)+","+str(colors)+",0"); +# print(colors) +# pair(lamp,threadid,delay,cycles,colorInts[i],colorInts[j], 1) +# fadeTo(lamp,threadid,delay,0, colors[0],colors[1],colors[2]) + trio(lamp,threadid,delay, colorInts[i],colorInts[j],colors, 0) + fadeTo(lamp,threadid,delay,0, 100,100,100) + +def systemsChatter(lamp,delay, cycles, threadid): + global stop + while stop == 0: + for i in range(0,len(sysdata)): + if stop == 1: + break + if sysdata[i] is None or sysdata[i] == '': + continue + if sysdata[i].cpu == -1: + continue + if sysdata[i].mem == -1: + continue + if sysdata[i].fs == -1: + continue + + r = sysdata[i].mem + g = sysdata[i].fs + b = sysdata[i].cpu + + for j in range(0,len(hostList)): + if sysdata[i].host in hostList[j]: + id1 = colorInts[j] + id2 = colorInts[hostList[j].index(sysdata[i].host)] +# id2 = [ int(id2[0]*0.6), int(id2[1]*0.6), int(id2[2]*0.6)] + +# pair(lamp,threadid,delay, 1, id1, id2, 1) +# fadeTo(lamp,threadid,delay,0, r,g,b) +# sleep(0.5) +# fadeTo(lamp,threadid,delay,0, 0,0,0) + trio(lamp,threadid,delay, id1,id2,[r,g,b], 0) + fadeTo(lamp,threadid,delay,0, 100,100,100) + +def gasp(lamp,delay, r, g, b, threadid): +# print("gasp: "+str(lamp)+": )"+str(r)+","+str(g)+","+str(b)+")") + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(0.3) + fadeTo(lamp,threadid,delay,0, r,g,b) + sleep(1) + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(0.3) + +def chime(lamp,delay, threadid): + hours = int(strftime("%I")) + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(2) + for i in range(0,hours): + fadeTo(lamp,threadid,delay,0, 100,100,100) + fadeTo(lamp,threadid,delay,0, 0,0,0) + sleep(0.1) + sleep(1) + + +hostList = [ ["cns-master", "cns-bedroom", "cns-fdoor", "001", "fdoor" ], + ["cns-memory", "www.slightlycyberpunk.com", "mail.bsflowers.net", "git.slightlycyberpunk.com", "mastodon", "collabora", "nextcloud" ], + ["pfSense.slightlycyberpunk.com", "vhost1", "vhost2", "vhost3"] + ] +syssumm = [0,0,0] +sysdata = [] +def refreshSysData(): + global syssumm + global sysdata + sysdata = [type('',(object,),{"cpu": -1, "mem": -1, "fs": -1})() for i in range(0,25)] + syssumm = [0,0,0] + lampfile = open("/home/pi/framework/lamp.lst", "r") + lampdata = lampfile.read() + lampdata = lampdata.split("\n") + for i in range(0,len(lampdata)): + if lampdata[i] == "": + continue + + line = lampdata[i].split("|") + + host = 0 + for j in range(0,len(hostList)): + if line[0] in hostList[j]: + host += hostList[j].index(line[0]) + else: + host += len(hostList[j]) + + sysobj = sysdata[host] + sysobj.host = line[0] + + if line[1] == "cpu": + sysobj.cpu = int(line[2]) + elif line[1] == "mem": + sysobj.mem = int(float(line[2])*100) + elif 'fs' in line[1] and sysobj.fs < int(line[2]): + sysobj.fs = int(line[2]) + + sysdata[host] = sysobj; + + warn = 0 + crit = 0 + for i in range(0,len(sysdata)): + if sysdata[i].cpu > 50 or sysdata[i].mem > 50 or sysdata[i].fs > 70: + warn+=1 + if sysdata[i].cpu > 85 or sysdata[i].mem > 85 or sysdata[i].fs > 90: + crit+=1 + if crit > 0: + syssumm = [100,0,0] + elif warn > 0: + syssumm = [100,100,0] + else: + syssumm = [0,100,0] + +sensorlist = [ "audio", "camera", "PRESENCE", "sessions", "sensors" ] +seccnts = [0,0,0,0] +secsumm = [100,100,0] +flathostList = list(chain.from_iterable(hostList)) +secdata = [["" for j in range(0, len(flathostList))] for i in range(0, len(sensorlist))] +def refreshSecData(): + global secsumm + global secdata + +# flathostList = list(chain.from_iterable(hostList)) +# for i in range(0, len(flathostList)): +# secdata[i] = ["","",""] + + lampfile = open("/home/pi/framework/lampsec.lst", "r") + lampdata = lampfile.read() + lampdata = lampdata.split("\n") + for line in lampdata: + if line == "": + continue + + print(line) + + line = line.split("|") + sensor = line[0] + host = line[1] + value = line[2] + timestamp = int(line[3]) + tdiff = timestamp - int(time.time()) - 100 + if tdiff > 100: + tdiff = 100 + if tdiff < 0: + tdiff = 0 + +# flathostList = list(chain.from_iterable(hostList)) +# print("SECDATA|SENSOR|SENSOR-IND|FLATLIST|HOST|HOST-IND") +# print(secdata) +# print(sensor) +# print(sensorlist.index(sensor)) +# print(flathostList) +# print(host) +# print(flathostList.index(host)) + if value == "WARN": + secdata[sensorlist.index(sensor)][flathostList.index(host)] = [100-tdiff, 100, 0] + if secsumm[2] > tdiff: + secsumm[2] = tdiff + elif value == "CRIT": + secdata[sensorlist.index(sensor)][flathostList.index(host)] = [100-tdiff, tdiff, 0] + if secsumm[1] > tdiff: + secsumm[1] = tdiff + elif value == "OK": + secdata[sensorlist.index(sensor)][flathostList.index(host)] = [tdiff, 100-tdiff, 0] + if secsumm[1] > 0: + secsumm = [100 - secsumm[1], secsumm[1], 0] + elif secsumm[2] > 0: + secsumm = [100 - secsumm[2], 100, 0] + else: + secsumm = [0, 100, 0] + +mode="off" +def refreshMode(): + global mode + modefile = open("mode", "r") + modedata = modefile.read() + mode = modedata.split("\n")[0] + +try: + refreshSysData() + refreshSecData() + refreshMode() + controlThreads = [0,1,2] + #if mode == "chatter": + chat = [ threading.Thread(target=systemsChatter,args=(0,0.02,2,0)), + threading.Thread(target=securityChatter,args=(1,0.022,2,1)), + threading.Thread(target=opposeChatter,args=(2,0.015,2,[0,1])) ] + chat[0].start() + chat[1].start() + chat[2].start() + #elif mode == "off": + # setColor(0,0,0,0,0) + # setColor(1,1,0,0,0) + # setColor(2,2,0,0,0) + #elif mode == "on": + # setColor(0,0,90,90,90) + # setColor(1,1,90,90,90) + # setColor(1,1,90,90,90) + + while stop == 0: + if mode == "off": + controlThreads = [3,3,3] + setColor(0,3,0,0,0) + setColor(1,3,0,0,0) + setColor(2,3,0,0,0) + elif mode == "on": + controlThreads = [4,4,4] + setColor(0,4,100,100,100) + setColor(1,4,100,100,100) + setColor(2,4,100,100,100) + elif mode == "chatter": + controlThreads = [0,1,2] + if int(strftime("%S")) == 0: + if int(strftime("%M")) == 0: + controlThreads = [10,11,12] + chimes = [ threading.Thread(target=chime,args=(0,0.003,10)), + threading.Thread(target=chime,args=(1,0.003,11)), + threading.Thread(target=chime,args=(2,0.003,12)) ] + chimes[0].start() + chimes[1].start() + chimes[2].start() + chimes[0].join() + chimes[1].join() + chimes[2].join() + controlThreads = [0,1,2] + else: + gasps = [ threading.Thread(target=gasp,args=(0,0.01,syssumm[0],syssumm[1],syssumm[2],20)), + threading.Thread(target=gasp,args=(1,0.01,0,100,0,21)), + threading.Thread(target=gasp,args=(2,0.01,100,0,100,22)) ] + controlThreads = [20,21,22] + gasps[0].start() + gasps[1].start() + gasps[2].start() + gasps[0].join() + gasps[1].join() + gasps[2].join() + controlThreads = [0,1,2] + sleep(1) + refreshSysData() + refreshSecData() + refreshMode() + + print("Waiting for threads to terminate...\n") + chat[0].join() + print("One left...\n") + chat[1].join() + print("DONE!\n") + chat[2].join() + +except KeyboardInterrupt: + stop = 1 + print("Interrupted") + +finally: + GPIO.cleanup() + diff --git a/Tower/description b/Tower/description new file mode 100644 index 0000000..9fd0611 --- /dev/null +++ b/Tower/description @@ -0,0 +1 @@ +Mounts to hold an RGB jar lamp on a quarter inch threaded rod; controlled by a Raspberry Pi through a Sparkfun SX1509 I/O expander board. Colors are used to display the current status of various servers and sensors. diff --git a/Tower/preview.png b/Tower/preview.png new file mode 100644 index 0000000..4ca22f6 Binary files /dev/null and b/Tower/preview.png differ