Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b82c9af8c4 | |||
| f4e87910ab | |||
| b8b585d242 | |||
| b38067dd8c | |||
| ee6a150e3e | |||
| b30c85dead | |||
| 9f112e0903 | |||
| 95f132bdf8 | |||
| 6fe5db1adc |
@@ -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,32 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[credentials]
|
||||||
|
username=<username>
|
||||||
|
password=<password>
|
||||||
|
IPMIEK=0000000000000000000000000000000000000000
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
#!/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()
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/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()
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/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()
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/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()
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/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()
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/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.")
|
||||||
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
ipmitool -I lanplus -H 192.168.128.223 -U root -P Optimus0329 -y 0000000000000000000000000000000000000000 power off
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
ipmitool -I lanplus -H 192.168.128.223 -U root -P Optimus0329 -y 0000000000000000000000000000000000000000 power on
|
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#!/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()
|
||||||
Reference in New Issue
Block a user