Added tower details
authorBrian Flowers <git-admn@bsflowers.net>
Thu, 6 May 2021 01:52:30 +0000 (21:52 -0400)
committerBrian Flowers <git-admn@bsflowers.net>
Thu, 6 May 2021 01:52:30 +0000 (21:52 -0400)
Tower/ClampMount.scad [new file with mode: 0644]
Tower/ElectronicsMount.scad [new file with mode: 0644]
Tower/LampMount.scad [new file with mode: 0644]
Tower/chatter.py [new file with mode: 0755]
Tower/chatter.py~ [new file with mode: 0755]
Tower/description [new file with mode: 0644]
Tower/preview.png [new file with mode: 0644]

diff --git a/Tower/ClampMount.scad b/Tower/ClampMount.scad
new file mode 100644 (file)
index 0000000..f0909af
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+#
+#
+#    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 (file)
index 0000000..df97267
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+#
+#
+#    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 (file)
index 0000000..cf391ba
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+#
+#
+#    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 (executable)
index 0000000..5684760
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+#
+#
+#    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 (executable)
index 0000000..7bf15b2
--- /dev/null
@@ -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 (file)
index 0000000..9fd0611
--- /dev/null
@@ -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 (file)
index 0000000..4ca22f6
Binary files /dev/null and b/Tower/preview.png differ