Compare commits

..

2 Commits

26 changed files with 63 additions and 1540 deletions
-637
View File
@@ -1,637 +0,0 @@
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
-32
View File
@@ -1,32 +0,0 @@
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command
30 0 * * * /cron_jobs/power/ipmi_poweroff.py -H 192.168.129.240 >> /var/log/cron_scripts.log 2>&1
30 0 * * * /cron_jobs/power/ipmi_poweroff.py -H 192.168.129.241 >> /var/log/cron_scripts.log 2>&1
0 1 * * * /cron_jobs/power/ilo_poweroff.py -H 192.168.129.242 -a ForceOff --no-serverinfo >> /var/log/cron_scripts.log 2>&1
0 5 * * * /cron_jobs/system/ilo_poweron.py -H 192.168.129.242 -a On --no-serverinfo >> /var/log/cron_scripts.log 2>&1
10 5 * * * /cron_jobs/power/ipmi_poweron.py -H 192.168.129.240 >> /var/log/cron_scripts.log 2>&1
10 5 * * * /cron_jobs/power/ipmi_poweron.py -H 192.168.129.241 >> /var/log/cron_scripts.log 2>&1
35 0 * * * /cron_jobs/system/update_system.py >> /var/log/cron_scripts.log 2>&1
*/3 * * * * /cron_jobs/fancontrol/set_fanspeed.py -H 192.168.129.240 >> /var/log/cron_scripts.log 2>&1
*/3 * * * * /cron_jobs/fancontrol/set_fanspeed.py -H 192.168.129.241 >> /var/log/cron_scripts.log 2>&1
@@ -1,4 +0,0 @@
[credentials]
username=<username>
password=<password>
IPMIEK=0000000000000000000000000000000000000000
@@ -1,191 +0,0 @@
#!/usr/bin/env python3
import subprocess
import re
import sys
import datetime
import argparse
import configparser
import os
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 = build_ipmi_base()
cmd = base + args
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
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)
except Exception:
# fallback to stdout
ts_print(msg)
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}")
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", 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, 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_exhaust_temp()
ts_print(f"{IPMIHOST}: Exhaust temperature: {temp} C")
except Exception as e:
ts_print(f"{IPMIHOST}: Error reading temperature: {e}")
sys.exit(1)
speed_hex = format(FANSPEED, "x")
if temp > MAXTEMP:
msg = f"{IPMIHOST}:Warning: Temperature is too high! Activating dynamic fan control! ({temp} C)"
log_systemd("R710-IPMI-TEMP", msg)
ts_print(msg)
run_ipmi(["raw", "0x30", "0x30", "0x01", "0x01"])
else:
okmsg = f"{IPMIHOST}: Temperature is OK ({temp} C)"
log_systemd("R710-IPMI-TEMP", okmsg)
ts_print(okmsg)
ts_print(f"{IPMIHOST}: Activating manual fan speeds! (requested %d%%)" % FANSPEED)
run_ipmi(["raw", "0x30", "0x30", "0x01", "0x00"])
run_ipmi(["raw", "0x30", "0x30", "0x02", "0xff", "0x" + speed_hex])
if __name__ == "__main__":
main()
@@ -1,38 +0,0 @@
#!/usr/bin/env python3
import argparse
import subprocess
import getpass
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}")
def run(cmd):
try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as e:
print(f"Command failed: {' '.join(cmd)}", file=sys.stderr)
sys.exit(e.returncode)
def main():
p = argparse.ArgumentParser(description="Run iLO REST commands (login, serverinfo, reboot)")
p.add_argument("-H", "--host", required=True, help="iLO host/IP")
p.add_argument("-a", "--action", default="ForceOff", choices=["ForceOff", "GracefulRestart", "Reset"],
help="reboot action to run (default: ForceOff)")
p.add_argument("--no-serverinfo", action="store_true", help="skip running serverinfo")
args = p.parse_args()
# login
ts_print(f"Sending {args.action} to {args.host}")
run(['ilorest', 'login', args.host, '-u', '<username>', '-p', '<password>'])
# optional serverinfo
if not args.no_serverinfo:
run(['ilorest', 'serverinfo'])
# power off / reboot action
run(['ilorest', 'reboot', args.action])
if __name__ == "__main__":
main()
@@ -1,35 +0,0 @@
#!/usr/bin/env python3
import argparse
import subprocess
import getpass
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}")
def run(cmd):
try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as e:
print(f"Command failed: {' '.join(cmd)}", file=sys.stderr)
sys.exit(e.returncode)
def main():
p = argparse.ArgumentParser(description="Login to iLO, show serverinfo and power on")
p.add_argument("-H", "--host", required=True, help="iLO host/IP")
p.add_argument("-a", "--action", default="On", choices=["On","Off","GracefulRestart","ForceOff","Reset"],
help="power/reboot action to run (default: On)")
p.add_argument("--no-serverinfo", action="store_true", help="skip running serverinfo")
args = p.parse_args()
ts_print(f"Sending {args.action} to {args.host}")
run(['ilorest', 'login', args.host, '-u', '<username>', '-p', '<password>'])
if not args.no_serverinfo:
run(['ilorest', 'serverinfo'])
run(['ilorest', 'reboot', args.action])
if __name__ == "__main__":
main()
@@ -1,31 +0,0 @@
#!/usr/bin/env python3
import argparse
import subprocess
import sys
IPMIUSER = "<username>"
IPMIPW = "<password>"
IPMIEK = "0000000000000000000000000000000000000000"
def main():
p = argparse.ArgumentParser(description="Send IPMI power off")
p.add_argument("-H", "--host", required=True, help="IPMI host address")
args = p.parse_args()
cmd = [
"ipmitool",
"-I", "lanplus",
"-H", args.host,
"-U", IPMIUSER,
"-P", IPMIPW,
"-y", IPMIEK,
"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()
@@ -1,38 +0,0 @@
#!/usr/bin/env python3
import argparse
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}")
IPMIUSER = "<username>"
IPMIPW = "<password>"
IPMIEK = "0000000000000000000000000000000000000000"
def main():
p = argparse.ArgumentParser(description="Send IPMI power off")
p.add_argument("-H", "--host", required=True, help="IPMI host address")
args = p.parse_args()
cmd = [
"ipmitool",
"-I", "lanplus",
"-H", args.host,
"-U", IPMIUSER,
"-P", IPMIPW,
"-y", IPMIEK,
"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()
@@ -1,45 +0,0 @@
#!/usr/bin/python3
import subprocess
import os
from datetime import datetime, timezone
from typing import Optional
def generate_timestamp(fmt: Optional[str] = None, utc: bool = False) -> str:
"""Return a timestamp string.
- If `fmt` is provided, return `datetime.strftime(fmt)`.
- If `fmt` is None, return an ISO 8601 timestamp. When `utc` is True
the timestamp ends with 'Z' (UTC). When `utc` is False it returns
the local time ISO format.
Examples:
- `generate_timestamp()` -> '2025-11-20T12:34:56Z'
- `generate_timestamp("%Y-%m-%d %H:%M:%S")` -> '2025-11-20 12:34:56'
"""
if fmt:
dt = datetime.now(timezone.utc) if utc else datetime.now()
return dt.strftime(fmt)
dt = datetime.now(timezone.utc) if utc else datetime.now()
if utc:
# force trailing Z for UTC instead of +00:00
return dt.replace(tzinfo=timezone.utc).isoformat().replace('+00:00', 'Z')
return dt.isoformat()
print(f"Starting system update at {generate_timestamp()}")
# Update package lists
print(f"{generate_timestamp()}: Updating package lists...")
subprocess.run(['sudo', 'apt', 'update'], check=True)
# Upgrade packages
print(f"{generate_timestamp()}: Upgrading packages...")
subprocess.run(['sudo', 'apt', 'upgrade', '-y'], check=True)
# Check if reboot is required
if os.path.exists('/var/run/reboot-required'):
print(f"{generate_timestamp()}: Reboot required. Rebooting now...")
subprocess.run(['sudo', 'reboot'])
else:
print(f"{generate_timestamp()}: No reboot required.")
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# IPMI IP address
IPMIHOST=<hostname>
# IPMI Username
IPMIUSER=<username
# IPMI Password
IPMIPW=<password>
# Your IPMI Encryption Key
IPMIEK=0<IPMI Encryption Key>
# Fan Speed / utilisation in percentage, for example 9 for 9% utilisation
# Please note that each fan can have a different rpm and will not all be the same speed
FANSPEED=10
# TEMPERATURE
# Change this to the temperature in Celsius you are comfortable with.
# If the temperature goes above the set degrees it will send raw IPMI command to enable dynamic fan control
MAXTEMP=30
# This variable sends a IPMI command to get the temperature, and outputs it as two digits.
# Do not edit unless you know what you do.
# Side note, if you are running ipmitool on the system you are controlling, you don't need to specify -H,-U,-P - from the OS installed on the host, ipmitool is assumed permitted. You only need host/user/pass for remote access.
TEMP=$(ipmitool -I lanplus -H $IPMIHOST -U $IPMIUSER -P $IPMIPW -y $IPMIEK sdr type temperature |grep Ambient |grep degrees |grep -Po '\d{2}' | tail -1)
# Dont edit this, this converts decimal to hex
SPEEDHEX=$( printf "%x" $FANSPEED )
if [[ $TEMP > $MAXTEMP ]];
then
printf "Warning: Temperature is too high! Activating dynamic fan control! ($TEMP C)" | systemd-cat -t R710-IPMI-TEMP
echo "Warning: Temperature is too high! Activating dynamic fan control! ($TEMP C)"
# This sets the fans to auto mode, so the motherboard will set it to a speed that it will need do cool the server down
ipmitool -I lanplus -H $IPMIHOST -U $IPMIUSER -P $IPMIPW -y $IPMIEK raw 0x30 0x30 0x01 0x01
else
printf "Temperature is OK ($TEMP C)" | systemd-cat -t R710-IPMI-TEMP
printf "Activating manual fan speeds! (1560 RPM)"
# This sets the fans to manual mode
ipmitool -I lanplus -H $IPMIHOST -U $IPMIUSER -P $IPMIPW -y $IPMIEK raw 0x30 0x30 0x01 0x00
# This is where we set the slower, quite speed
ipmitool -I lanplus -H $IPMIHOST -U $IPMIUSER -P $IPMIPW -y $IPMIEK raw 0x30 0x30 0x02 0xff 0x$SPEEDHEX
fi
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
ipmitool -I lanplus -H 192.168.128.223 -U root -P Optimus0329 -y 0000000000000000000000000000000000000000 power off
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
ipmitool -I lanplus -H 192.168.128.223 -U root -P Optimus0329 -y 0000000000000000000000000000000000000000 power on
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
echo "Client Type: $1"
echo "Client IDs: $2"
client_string=$2
client_list=()
IFS=', ' read -a array <<< "${client_string}"; echo "${array[@]}"
for index in "${!array[@]}"
do
echo "Starting container: ${array[index]}"
lxc-start ${array[index]}
done
@@ -12,11 +12,11 @@ def get_time():
def client_shutdown(client_type, clientid):
lxc_filepath = "/usr/bin/lxc-stop"
qemu_filepath = "/usr/sbin/qm"
qemu_filepath = f"/usr/sbin/qm shutdown {clientid}"
try:
print(f'Stopping {clientid}')
if client_type == 'vm':
process_id = os.spawnv(os.P_NOWAIT, qemu_filepath, ['shutdown', clientid])
process_id = os.spawnv(os.P_NOWAIT, qemu_filepath, ['--skip-lock', 'true'])
elif client_type == 'lxc':
process_id = os.spawnv(os.P_NOWAIT, lxc_filepath, [client_type, clientid])
print(process_id)
@@ -37,6 +37,7 @@ def main():
print(f'Start time: {timestamp}')
for clientid in client_list:
client_shutdown(client_type, clientid)
sleep(1)
else:
print(f'Successfully stopped client list.')
@@ -47,4 +48,4 @@ def main():
if __name__ == '__main__':
main()
# sys.exit(0)
# sys.exit(1)
# sys.exit(1)
-57
View File
@@ -1,57 +0,0 @@
#!/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
@@ -1,61 +0,0 @@
#!/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
@@ -1,65 +0,0 @@
#!/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
@@ -1,215 +0,0 @@
#!/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()
-88
View File
@@ -1,88 +0,0 @@
#!/bin/python3
"""
Parallel rsync launcher.
Requires rsync on PATH (e.g. via MSYS/Cygwin on Windows).
Runs multiple rsync invocations in parallel (one per file/entry).
"""
import argparse
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import List
#DEFAULT_RSYNC_ARGS = ["-av", "--partial", "--compress", "--info=progress2"]
DEFAULT_RSYNC_ARGS = ["-avP", "--partial"]
def build_file_list(src: str) -> List[Path]:
p = Path(src)
if p.is_dir():
# sync each top-level entry separately (files and immediate subdirs)
return [x for x in p.iterdir()]
# allow glob patterns
if any(ch in src for ch in ["*", "?"]):
return list(Path(".").glob(src))
# single file
return [p]
def run_rsync(item: Path, dest: str, rsync_args: List[str], dry_run: bool) -> (Path, int, str, str):
cmd = ["rsync", *rsync_args]
if dry_run:
cmd.append("--dry-run")
# preserve relative path by sending the item name, not its parent
cmd += [str(item), dest]
try:
proc = subprocess.run(cmd, capture_output=True, text=True)
return item, proc.returncode, proc.stdout, proc.stderr
except FileNotFoundError:
return item, 127, "", "rsync not found on PATH"
def main():
ap = argparse.ArgumentParser(description="Transfer files using rsync in parallel.")
ap.add_argument("src", help="Source file, directory or glob pattern")
ap.add_argument("dest", help="Rsync destination (local path or user@host:/path)")
ap.add_argument("-w", "--workers", type=int, default=4, help="Parallel rsync workers")
ap.add_argument("-r", "--rsync-args", default=" ".join(DEFAULT_RSYNC_ARGS),
help="Additional rsync args (quoted). Default: -a --partial --compress --progress")
ap.add_argument("--dry-run", action="store_true", help="Don't transfer, just show what would happen")
args = ap.parse_args()
items = build_file_list(args.src)
if not items:
print("No files found for:", args.src, file=sys.stderr)
sys.exit(2)
rsync_args = args.rsync_args.split()
results = []
print(f"Starting {len(items)} rsync tasks with {args.workers} workers...")
with ThreadPoolExecutor(max_workers=args.workers) as ex:
futures = {ex.submit(run_rsync, item, args.dest, rsync_args, args.dry_run): item for item in items}
for fut in as_completed(futures):
item, code, out, err = fut.result()
results.append((item, code, out, err))
status = "OK" if code == 0 else f"ERR({code})"
print(f"[{status}] {item}")
# summary
failed = [r for r in results if r[1] != 0]
print(f"\nCompleted: {len(results)} tasks, {len(failed)} failed.")
if failed:
print("Failures detail:")
for item, code, out, err in failed:
print(f"--- {item} (exit {code}) ---")
if out:
print(out.strip())
if err:
print(err.strip())
sys.exit(1 if failed else 0)
if __name__ == "__main__":
main()