Compare commits

...

4 Commits

Author SHA1 Message Date
wjones b82c9af8c4 Add frigate config.yaml file 2026-03-21 15:45:04 -04:00
wjones f4e87910ab Create credentials 2026-03-21 10:13:31 -04:00
wjones b8b585d242 Update set_fanspeed.py 2026-03-21 10:05:52 -04:00
wjones b38067dd8c Add new power cycle scripts for servers 2026-03-17 07:13:23 -04:00
7 changed files with 1169 additions and 24 deletions
+637
View File
@@ -0,0 +1,637 @@
mqtt:
enabled: false
detectors:
coral:
type: edgetpu
device: pci
ffmpeg:
hwaccel_args: preset-nvidia
record:
enabled: true
alerts:
retain:
days: 30
mode: motion
detections:
retain:
days: 30
mode: motion
continuous:
days: 0
motion:
days: 3
go2rtc:
streams:
# Backbasement:
# - ffmpeg:rtsp://tpadmin:Optimus0329@192.168.128.222:554/live/ch0#video=copy#audio=copy#audio=aac#hardware
# SubBackBasement:
# - ffmpeg:rtsp://tpadmin:Optimus0329@192.168.128.222:554/live/ch1#video=copy#audio=copy#audio=aac#hardware
# FrontBasement:
# - ffmpeg:rtsp://tpadmin:Optimus0329@192.168.128.223:554/live/ch0?token=e6cf2225e8bf5fb028543898fab9a545#video=copy#audio=copy#audio=aac#hardware
# SubFrontBasement:
# - ffmpeg:rtsp://tpadmin:Optimus0329@192.168.128.223:554/live/ch1?token=e6cf2225e8bf5fb028543898fab9a545#video=copy#audio=copy#audio=aac#hardware
# Eningeering:
# - ffmpeg:rtsp://tpadmin:Optimus0329@192.168.128.221:554/live/ch0?token=e6cf2225e8bf5fb028543898fab9a545#video=copy#audio=copy#audio=aac#hardware
# SubEngineering:
# - ffmpeg:rtsp://tpadmin:Optimus0329@192.168.128.221:554/live/ch1?token=e6cf2225e8bf5fb028543898fab9a545#video=copy#audio=copy#audio=aac#hardware
Driveway:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch12_0.264#video=copy#audio=copy#audio=aac#hardware
SubDriveway:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch12_1.264#video=copy#audio=copy#audio=aac#hardware
Porch:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch8_0.264#video=copy#audio=copy#audio=aac#hardware
SubPorch:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch8_1.264#video=copy#audio=copy#audio=aac#hardware
FenceGate:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch1_0.264#video=copy#audio=copy#audio=aac#hardware
SubFenceGate:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch1_1.264#video=copy#audio=copy#audio=aac#hardware
GardenArea:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch2_0.264#video=copy#audio=copy#audio=aac#hardware
SubGardenArea:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch2_1.264#video=copy#audio=copy#audio=aac#hardware
Backyard1:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch3_0.264#video=copy#audio=copy#audio=aac#hardware
SubBackyard1:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch3_1.264#video=copy#audio=copy#audio=aac#hardware
Backyard2:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch4_0.264#video=copy#audio=copy#audio=aac#hardware
SubBackyard2:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch4_1.264#video=copy#audio=copy#audio=aac#hardware
Backyard3:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch0_0.264#video=copy#audio=copy#audio=aac#hardware
SubBackyard3:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch0_1.264#video=copy#audio=copy#audio=aac#hardware
Backyard4:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch10_0.264#video=copy#audio=copy#audio=aac#hardware
SubBackyard4:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch10_1.264#video=copy#audio=copy#audio=aac#hardware
Backyard5:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch7_0.264#video=copy#audio=copy#audio=aac#hardware
SubBackyard5:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch7_1.264#video=copy#audio=copy#audio=aac#hardware
PatioArea:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch6_0.264#video=copy#audio=copy#audio=aac#hardware
SubPatioArea:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch6_1.264#video=copy#audio=copy#audio=aac#hardware
PoolArea:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch9_0.264#video=copy#audio=copy#audio=aac#hardware
SubPoolArea:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch9_1.264#video=copy#audio=copy#audio=aac#hardware
SideHouse:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch11_0.264#video=copy#audio=copy#audio=aac#hardware
SubSideHouse:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch11_1.264#video=copy#audio=copy#audio=aac#hardware
BackSlider:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch5_0.264#video=copy#audio=copy#audio=aac#hardware
SubBackSlider:
- ffmpeg:rtsp://admin:Optimus0329@192.168.128.11:80/ch5_1.264#video=copy#audio=copy#audio=aac#hardware
cameras:
# name_of_your_camera: # <------ Name the camera
# enabled: True
# ffmpeg:
# inputs:
# - path: rtsp://10.0.10.10:554/rtsp # <----- The stream you want to use for detection
# roles:
# - detect
# detect:
# enabled: False # <---- disable detection until you have a working camera feed
# width: 1280
# height: 720
# back_basement:
# enabled: true
# ffmpeg:
# inputs:
# # - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/BackBasement
# #input_args: preset-rtsp-restream
# roles:
# - record
# - path: rtsp://127.0.0.1:8554/SubBackBasement
# #input_args: preset-rtsp-restream
# roles:
# - detect
# detect:
# enabled: true # <---- disable detection until you have a working camera feed
# width: 1280
# height: 720
# fps: 5
# motion:
# enabled: true
# threshold: 30
# contour_area: 30
# mask: 0.683,0.006,0.683,0.056,0.766,0.055,0.996,0.054,0.996,0.006
# improve_contrast: true
# snapshots:
# enabled: true
# timestamp: true
# bounding_box: true
# retain:
# objects:
# person: 7
# front_basement:
# enabled: true
# ffmpeg:
# inputs:
# # - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/FrontBasement
# #input_args: preset-rtsp-restream
# roles:
# - record
# - path: rtsp://127.0.0.1:8554/SubFrontBasement
# #input_args: preset-rtsp-restream
# roles:
# - detect
# detect:
# enabled: true # <---- disable detection until you have a working camera feed
# width: 1280
# height: 720
# fps: 5
# motion:
# enabled: true
# threshold: 30
# contour_area: 30
# mask: 0.723,0,0.724,0.05,1,0.048,0.999,0.001
# improve_contrast: true
# snapshots:
# enabled: true
# timestamp: true
# bounding_box: true
# retain:
# objects:
# person: 7
# record:
# enabled: false
# engineering:
# enabled: true
# ffmpeg:
# inputs:
# # - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Engineering
# #input_args: preset-rtsp-restream
# roles:
# - record
# - path: rtsp://127.0.0.1:8554/SubEngineering
# #input_args: preset-rtsp-restream
# roles:
# - detect
# detect:
# enabled: true # <---- disable detection until you have a working camera feed
# width: 1280
# height: 720
# fps: 5
# motion:
# enabled: true
# threshold: 14
# contour_area: 15
# improve_contrast: true
# snapshots:
# enabled: true
# timestamp: true
# bounding_box: true
# retain:
# objects:
# person: 7
# record:
# enabled: false
driveway:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Driveway
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubDriveway
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
zones:
drivewayview:
coordinates: 0,0.373,0,1,1,1,1,0.381,0.47,0.343,0.134,0.352
loitering_time: 0
friendly_name: DrivewayView
porch:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Porch
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubPorch
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
fence_gate:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/FenceGate
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubFenceGate
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
zones:
fencegate:
coordinates:
0,0.395,0.318,0.259,0.598,0.175,0.916,0.237,1,0.296,1,1,0.003,0.996
loitering_time: 0
friendly_name: FenceGate
garden_area:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/GardenArea
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubGardenArea
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
backyard_1:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Backyard1
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubBackyard1
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
zones:
backyard1:
coordinates:
0.001,0.274,0.124,0.162,0.228,0.143,0.382,0.131,0.555,0.133,0.714,0.16,0.854,0.196,0.998,0.253,0.998,0.996,0,0.992
loitering_time: 0
friendly_name: Backyard1
backyard_2:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Backyard2
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubBackyard2
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
zones:
backyard2:
coordinates:
0.002,0.147,0.086,0.106,0.172,0.091,0.271,0.078,0.386,0.077,0.499,0.077,0.601,0.086,0.698,0.099,0.787,0.117,0.859,0.087,0.995,0.155,0.998,0.992,0.001,0.995
loitering_time: 0
friendly_name: Backyard2
backyard_3:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Backyard3
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubBackyard3
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
zones:
backyard3:
coordinates:
0.001,0.337,0.166,0.332,0.327,0.342,0.419,0.344,0.51,0.355,0.603,0.363,0.692,0.374,0.771,0.381,0.848,0.389,0.951,0.405,0.997,0.423,0.998,0.996,0.003,0.994
loitering_time: 0
friendly_name: Backyard3
motion:
mask: 0.79,0.539,0.845,0.541,0.82,0.858,0.772,0.844
backyard_4:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Backyard4
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubBackyard4
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
motion:
mask:
- 0.866,0.001,0.866,0.209,1,0.202,1,0
- 0.001,0,0.249,0.001,0.249,0.045,0.002,0.044
threshold: 60
contour_area: 10
improve_contrast: true
backyard_5:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/Backyard5
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubBackyard5
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
motion:
mask:
- 0.029,0.31,0.13,0.305,0.134,0.54,0.032,0.544
- 0.004,0.002,0.004,0.044,0.245,0.044,0.244,0
threshold: 60
contour_area: 10
improve_contrast: true
zones:
backyard5:
coordinates:
0.001,0.305,0.043,0.264,0.113,0.194,0.19,0.128,0.271,0.109,0.371,0.089,0.525,0.057,0.709,0.057,0.89,0.076,0.997,0.122,0.997,0.995,0.002,0.998
loitering_time: 0
friendly_name: Backyard5
patio_area:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/PatioArea
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubPatioArea
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
pool_area:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/PoolArea
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubPoolArea
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
motion:
mask:
0.43,0.146,0.704,0.194,0.532,0.808,0.199,0.704,0.002,0.559,0.252,0.282
back_slider:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/BackSlider
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubBackSlider
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
side_house:
enabled: true
ffmpeg:
inputs:
# - path: rtsp://tpadmin:Optimus0329@192.168.128.225/stream1
# - path: rtsp://127.0.0.1:8554/SideHouse
# input_args: preset-rtsp-restream
# roles:
# - record
- path: rtsp://127.0.0.1:8554/SubSideHouse
input_args: preset-rtsp-restream
roles:
- record
- detect
detect:
enabled: true # <---- disable detection until you have a working camera feed
width: 1280
height: 720
fps: 5
snapshots:
enabled: true
timestamp: true
bounding_box: true
retain:
objects:
person: 7
record:
enabled: true
motion:
threshold: 60
contour_area: 40
improve_contrast: true
mask:
0.001,0.859,0.085,0.717,0.203,0.637,0.383,0.736,0.65,0.421,0.642,0.997,0.003,0.999
zones:
sidehouse:
coordinates: 0.661,0.001,0.662,0.996,1,1,1,0
loitering_time: 0
friendly_name: SideHouse
objects:
filters:
person: {}
version: 0.17-0
@@ -0,0 +1,4 @@
[credentials]
username=<username>
password=<password>
IPMIEK=0000000000000000000000000000000000000000
+130 -24
View File
@@ -4,24 +4,55 @@ import re
import sys
import datetime
import argparse
import configparser
import os
# IPMI settings
IPMIUSER = "<username>"
IPMIPW = "<password>"
IPMIEK = "0000000000000000000000000000000000000000"
DEFAULT_EK = "0000000000000000000000000000000000000000"
# IPMI settings (loaded at runtime from credentials file)
IPMIUSER = None
IPMIPW = None
IPMIEK = DEFAULT_EK
# Fan / temperature settings
FANSPEED = 20 # percent
MAXTEMP = 37 # Celsius
def build_ipmi_base():
base = ["ipmitool", "-I", IPMIINTERFACE, "-H", IPMIHOST, "-U", IPMIUSER, "-P", IPMIPW, "-y", IPMIEK]
if IPMICIPHER is not None:
base += ["-C", str(IPMICIPHER)]
return base
def has_ipmi_session_error(stderr_text):
s = (stderr_text or "").lower()
markers = [
"unable to establish ipmi v2 / rmcp+ session",
"rakp",
"session timeout",
"authentication type",
"invalid user name",
]
return any(marker in s for marker in markers)
def run_ipmi(args):
base = ["ipmitool", "-I", "lanplus", "-H", IPMIHOST, "-U", IPMIUSER, "-P", IPMIPW, "-y", IPMIEK]
base = build_ipmi_base()
cmd = base + args
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if p.returncode != 0:
raise RuntimeError(f"ipmitool failed: {' '.join(cmd)}\n{p.stderr.strip()}")
if p.returncode != 0 or has_ipmi_session_error(p.stderr):
raise RuntimeError(
f"ipmitool failed: {' '.join(cmd)}\n"
f"stdout: {(p.stdout or '').strip() or '<empty>'}\n"
f"stderr: {(p.stderr or '').strip() or '<empty>'}"
)
return p.stdout
def run_ipmi_raw(args):
base = build_ipmi_base()
cmd = base + args
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return cmd, p.returncode, p.stdout, p.stderr
def log_systemd(tag, msg):
try:
subprocess.run(["systemd-cat", "-t", tag], input=msg + "\n", text=True, check=True)
@@ -34,33 +65,108 @@ def ts_print(msg):
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"{now} {msg}")
def get_ambient_temp():
out = run_ipmi(["sdr", "type", "temperature"])
# find lines like "Exhuat Temp | 29 degrees C"
lines = [l for l in out.splitlines() if "Exhaust" in l and "degrees" in l]
if not lines:
raise RuntimeError("Exhaust temperature line not found in ipmitool output")
# Extract last number found in the last matching line (like the original)
field = lines[-1].split("|")[4]
#ts_print("Debug: field =", field)
m = re.search(r"(\d{1,3})", field)
if not m:
raise RuntimeError("Could not parse temperature from line: " + lines[-1])
return int(m.group(1))
def load_credentials(path):
if not os.path.exists(path):
raise RuntimeError(f"credentials file not found: {path}")
config = configparser.ConfigParser()
config.read(path)
try:
username = config.get("credentials", "username", fallback="root")
password = config.get("credentials", "password")
encryption_key = config.get("credentials", "encryption_key", fallback=DEFAULT_EK)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
raise RuntimeError(f"error reading credentials file: {e}")
return username, password, encryption_key
def get_exhaust_temp():
commands_to_try = [
["sensor", "get", "Exhaust Temp"],
["sdr", "type", "temperature"],
["sensor", "list"],
["sdr", "elist", "full"],
]
debug_sections = []
session_error_seen = False
for args in commands_to_try:
cmd, rc, out, err = run_ipmi_raw(args)
if has_ipmi_session_error(err):
session_error_seen = True
cmd_text = " ".join(cmd)
section = [
f"Command: {cmd_text}",
f"Return code: {rc}",
"STDOUT:",
out.strip() or "<empty>",
"STDERR:",
err.strip() or "<empty>",
]
debug_sections.append("\n".join(section))
# Parse explicit sensor-get output first.
if args[:2] == ["sensor", "get"] and out:
m = re.search(r"Sensor\s+Reading\s*:\s*(-?\d+(?:\.\d+)?)\b.*degrees\s*C", out, re.IGNORECASE)
if m:
return int(float(m.group(1)))
# Parse table outputs that have pipe-delimited sensor rows.
for line in out.splitlines():
parts = [p.strip() for p in line.split("|")]
if len(parts) < 2:
continue
sensor_name = parts[0]
normalized_name = " ".join(sensor_name.lower().split())
if not (normalized_name == "exhaust temp" or normalized_name.startswith("exhaust temp ")):
continue
for part in parts[1:]:
m = re.search(r"(-?\d+(?:\.\d+)?)\s*degrees\s*C", part, re.IGNORECASE)
if m:
return int(float(m.group(1)))
if session_error_seen:
raise RuntimeError(
"Failed to establish an IPMI session. "
"Check host/credentials, then try different options like "
"--interface lan --cipher 3 or --interface lanplus --cipher 17.\n\n"
+ "Debug output from attempted commands:\n\n"
+ "\n\n---\n\n".join(debug_sections)
)
raise RuntimeError(
"Failed to read Exhaust Temp from ipmitool output.\n"
+ "Debug output from attempted commands:\n\n"
+ "\n\n---\n\n".join(debug_sections)
)
def main():
# parse CLI args (IPMI host can be overridden with -H/--host)
parser = argparse.ArgumentParser(description="Set fan speed via IPMI")
parser.add_argument("-H", "--host", default="127.0.0.1", help="IPMI host/IP (default: %(default)s)")
parser.add_argument("-H", "--host", required=True, help="IPMI host/IP")
parser.add_argument(
"-c",
"--credentials",
default="/etc/credentials",
help="Path to credentials file (default: %(default)s)",
)
parser.add_argument("--interface", default="lanplus", choices=["lanplus", "lan"], help="IPMI interface (default: %(default)s)")
parser.add_argument("--cipher", type=int, default=None, help="Optional IPMI cipher suite ID (e.g. 3 or 17)")
args = parser.parse_args()
global IPMIHOST
# set global IPMIHOST so run_ipmi() uses the provided host
global IPMIHOST, IPMIINTERFACE, IPMICIPHER, IPMIUSER, IPMIPW, IPMIEK
# set globals so run_ipmi() uses provided connection settings and credentials
IPMIHOST = args.host
IPMIINTERFACE = args.interface
IPMICIPHER = args.cipher
IPMIUSER, IPMIPW, IPMIEK = load_credentials(args.credentials)
ts_print(f"{IPMIHOST}: Fan speed check for host {IPMIHOST}")
try:
temp = get_ambient_temp()
temp = get_exhaust_temp()
ts_print(f"{IPMIHOST}: Exhaust temperature: {temp} C")
except Exception as e:
ts_print(f"{IPMIHOST}: Error reading temperature: {e}")
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/python3
import argparse
import configparser
import os
import sys
import urllib3
import redfish
# Suppress SSL warnings for self-signed iLO certificates
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
parser = argparse.ArgumentParser(description="Power off an HPE server via iLO Redfish")
parser.add_argument("hostname", help="iLO hostname or IP address")
parser.add_argument("-c", "--credentials", default="/etc/ilo_credentials",
help="Path to credentials file (default: /etc/ilo_credentials)")
args = parser.parse_args()
# Read credentials from file (chmod 600 recommended: sudo chmod 600 /etc/ilo_credentials)
# File format:
# [ilo]
# username = root
# password = yourpassword
cred_path = args.credentials
if not os.path.exists(cred_path):
print(f"Error: credentials file not found: {cred_path}", file=sys.stderr)
sys.exit(1)
config = configparser.ConfigParser()
config.read(cred_path)
try:
LOGIN_ACCOUNT = config.get("ilo", "username", fallback="root")
LOGIN_PASSWORD = config.get("ilo", "password")
except (configparser.NoSectionError, configparser.NoOptionError) as e:
print(f"Error reading credentials file: {e}", file=sys.stderr)
sys.exit(1)
BASE_URL = f"https://{args.hostname}"
REDFISHOBJ = redfish.RedfishClient(
base_url=BASE_URL,
username=LOGIN_ACCOUNT,
password=LOGIN_PASSWORD,
)
REDFISHOBJ.login(auth="session")
# Discover the correct reset action URI from the system resource
sys_response = REDFISHOBJ.get("/redfish/v1/Systems/1/")
reset_uri = sys_response.dict["Actions"]["#ComputerSystem.Reset"]["target"]
body = {"ResetType": "PushPowerButton"} # iLO 4: PushPowerButton = graceful shutdown; use "ForceOff" for hard power cut
response = REDFISHOBJ.post(reset_uri, body=body)
print(f"Status: {response.status}")
if response.status >= 400:
print(f"Error: {response.read}")
REDFISHOBJ.logout()
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
import argparse
import configparser
import os
import subprocess
import sys
DEFAULT_EK = "0000000000000000000000000000000000000000"
def load_credentials(path):
if not os.path.exists(path):
print(f"Error: credentials file not found: {path}", file=sys.stderr)
sys.exit(1)
config = configparser.ConfigParser()
config.read(path)
try:
username = config.get("credentials", "username", fallback="root")
password = config.get("credentials", "password")
encryption_key = config.get("credentials", "encryption_key", fallback=DEFAULT_EK)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
print(f"Error reading credentials file: {e}", file=sys.stderr)
sys.exit(1)
return username, password, encryption_key
def main():
p = argparse.ArgumentParser(description="Send IPMI power off")
p.add_argument("-H", "--host", required=True, help="IPMI host address")
p.add_argument(
"-c",
"--credentials",
default="credentials",
help="Path to credentials file (default: credentials)",
)
args = p.parse_args()
ipmi_user, ipmi_pw, ipmi_ek = load_credentials(args.credentials)
cmd = [
"ipmitool",
"-I", "lanplus",
"-H", args.host,
"-U", ipmi_user,
"-P", ipmi_pw,
"-y", ipmi_ek,
"power", "off",
]
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if proc.returncode != 0:
print("ipmitool failed:", proc.stderr.strip(), file=sys.stderr)
sys.exit(proc.returncode)
print(proc.stdout.strip())
if __name__ == "__main__":
main()
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import argparse
import configparser
import os
import subprocess
import sys
import datetime
def ts_print(msg):
"""ts_print message prefixed with a timestamp (YYYY-MM-DD HH:MM:SS)."""
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"{now} {msg}")
DEFAULT_EK = "0000000000000000000000000000000000000000"
def load_credentials(path):
if not os.path.exists(path):
print(f"Error: credentials file not found: {path}", file=sys.stderr)
sys.exit(1)
config = configparser.ConfigParser()
config.read(path)
try:
username = config.get("ilo", "username", fallback="root")
password = config.get("ilo", "password")
encryption_key = config.get("ilo", "encryption_key", fallback=DEFAULT_EK)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
print(f"Error reading credentials file: {e}", file=sys.stderr)
sys.exit(1)
return username, password, encryption_key
def main():
p = argparse.ArgumentParser(description="Send IPMI power on")
p.add_argument("-H", "--host", required=True, help="IPMI host address")
p.add_argument(
"-c",
"--credentials",
default="credentials",
help="Path to credentials file (default: credentials)",
)
args = p.parse_args()
ipmi_user, ipmi_pw, ipmi_ek = load_credentials(args.credentials)
cmd = [
"ipmitool",
"-I", "lanplus",
"-H", args.host,
"-U", ipmi_user,
"-P", ipmi_pw,
"-y", ipmi_ek,
"power", "on",
]
print(f"Sending power on to {args.host}")
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if proc.returncode != 0:
print("ipmitool failed:", proc.stderr.strip(), file=sys.stderr)
sys.exit(proc.returncode)
print(proc.stdout.strip())
if __name__ == "__main__":
main()
+215
View File
@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""
Script to read and parse Powerball numbers from a CSV file.
"""
import csv
from datetime import datetime
from pathlib import Path
from typing import List, Dict
from collections import Counter
def read_powerball_csv(filepath: str) -> List[Dict]:
"""
Read a CSV file containing Powerball numbers.
Expected CSV format:
Draw Date,Winning Numbers,Multiplier
09/26/2020,11 21 27 36 62 24,3
Args:
filepath: Path to the CSV file
Returns:
List of dictionaries containing parsed powerball data
"""
powerball_data = []
try:
with open(filepath, 'r', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row_num, row in enumerate(reader, start=2): # start=2 because row 1 is header
try:
# Skip empty rows
if not any(row.values()):
continue
# Parse the row
draw_date = datetime.strptime(row['Draw Date'].strip(), '%m/%d/%Y')
# Parse winning numbers - filter out empty strings
numbers_str = row['Winning Numbers'].strip()
winning_numbers = [int(n) for n in numbers_str.split() if n.strip()]
multiplier = int(row['Multiplier'].strip())
powerball_data.append({
'draw_date': draw_date,
'date_str': draw_date.strftime('%Y-%m-%d'),
'winning_numbers': winning_numbers,
'multiplier': multiplier
})
except ValueError as e:
print(f"Warning: Skipping row {row_num} due to error: {e}")
print(f" Row data: {row}")
continue
except FileNotFoundError:
print(f"Error: File '{filepath}' not found.")
return []
except Exception as e:
print(f"Error: Failed to read CSV file - {e}")
return []
return powerball_data
def display_powerball_data(data: List[Dict]) -> None:
"""
Display powerball data in a formatted table.
Args:
data: List of powerball data dictionaries
"""
if not data:
print("No data to display.")
return
print(f"{'Draw Date':<12} {'Winning Numbers':<30} {'Multiplier':<10}")
print("-" * 52)
for entry in data:
numbers_str = ' '.join(map(str, entry['winning_numbers']))
print(f"{entry['date_str']:<12} {numbers_str:<30} {entry['multiplier']:<10}")
def analyze_most_frequent_numbers(data: List[Dict]) -> None:
"""
Analyze and display the most frequent number in each slot position.
Args:
data: List of powerball data dictionaries
"""
if not data:
print("No data to analyze.")
return
# Initialize lists to hold numbers for each position
num_positions = len(data[0]['winning_numbers'])
position_numbers = [[] for _ in range(num_positions)]
# Collect all numbers for each position
for entry in data:
for position, number in enumerate(entry['winning_numbers']):
position_numbers[position].append(number)
# Find most common number in each position
print("\n" + "=" * 60)
print("Most Frequent Numbers by Slot Position")
print("=" * 60)
most_frequent = []
for position, numbers in enumerate(position_numbers, start=1):
counter = Counter(numbers)
most_common_num, frequency = counter.most_common(1)[0]
most_frequent.append(most_common_num)
percentage = (frequency / len(numbers)) * 100
print(f"Position {position}: {most_common_num:<3} (appears {frequency} times, {percentage:.1f}%)")
print("-" * 60)
print(f"Most frequent numbers combined: {' '.join(map(str, most_frequent))}")
# Analyze most frequent number across first 5 positions combined
print("\n" + "=" * 60)
print("Additional Analysis")
print("=" * 60)
# Combine all numbers from positions 1-5
first_five_combined = []
for i in range(min(5, num_positions)):
first_five_combined.extend(position_numbers[i])
if first_five_combined:
counter_first_five = Counter(first_five_combined)
top_five_numbers = counter_first_five.most_common(5)
print("Top 5 most drawn numbers in first 5 positions combined:")
for rank, (number, frequency) in enumerate(top_five_numbers, start=1):
percentage = (frequency / len(first_five_combined)) * 100
print(f" {rank}. {number:<3} (appears {frequency} times, {percentage:.1f}%)")
# Analyze the sixth position (Powerball)
most_common_sixth = None
if num_positions >= 6:
sixth_position_numbers = position_numbers[5]
counter_sixth = Counter(sixth_position_numbers)
most_common_sixth, freq_sixth = counter_sixth.most_common(1)[0]
percentage_sixth = (freq_sixth / len(sixth_position_numbers)) * 100
print(f"\nMost drawn number in position 6 (Powerball): {most_common_sixth} (appears {freq_sixth} times, {percentage_sixth:.1f}%)")
# Return statistics for prediction
return {
'top_five_combined': [num for num, freq in top_five_numbers] if first_five_combined else [],
'most_common_powerball': most_common_sixth
}
def generate_suggested_combination(stats: dict) -> None:
"""
Generate a suggested combination based on statistical analysis.
Args:
stats: Dictionary containing statistical analysis results
"""
print("\n" + "=" * 60)
print("Suggested Combination Based on Statistical Analysis")
print("=" * 60)
print("\n⚠️ DISCLAIMER: This is purely statistical analysis.")
print("Past results do NOT predict future outcomes.")
print("Each draw is random and independent.\n")
top_five = stats['top_five_combined']
powerball = stats['most_common_powerball']
if len(top_five) >= 5 and powerball:
# Use the top 5 most frequent numbers from positions 1-5
suggested_numbers = sorted(top_five[:5])
print(f"Suggested white ball numbers: {' '.join(map(str, suggested_numbers))}")
print(f"Suggested Powerball number: {powerball}")
print()
print(f"Complete combination: {' '.join(map(str, suggested_numbers))} + {powerball}")
print()
print("This combination is based on:")
print(" • The 5 most frequently drawn numbers across all white ball positions")
print(f" • The most frequently drawn Powerball number ({powerball})")
else:
print("Insufficient data to generate a suggestion.")
def main():
"""Main function."""
# Default to 'powerball_numbers.csv' in the same directory as the script
script_dir = Path(__file__).parent
csv_file = script_dir / 'powerball_numbers.csv'
# You can also specify a different file path here
# csv_file = Path('path/to/your/file.csv')
print(f"Reading Powerball numbers from: {csv_file}\n")
powerball_data = read_powerball_csv(str(csv_file))
if powerball_data:
print(f"Successfully loaded {len(powerball_data)} records.\n")
display_powerball_data(powerball_data)
stats = analyze_most_frequent_numbers(powerball_data)
generate_suggested_combination(stats)
else:
print("Failed to load powerball data.")
if __name__ == '__main__':
main()