second commit

This commit is contained in:
denis
2026-03-10 13:10:32 +01:00
parent b5cc0106c6
commit 3aac7ff9b9
101 changed files with 22046 additions and 1 deletions
View File
+180
View File
@@ -0,0 +1,180 @@
'''
Devices
Created on 23 août 2022
@author: denis@e-educ.fr
Flow and Frequency output of the YF-S201
Flow(L/H) Frequency(Hz)
120 16
240 32.5
360 49.3
480 65.5
600 82
720 90.2
F = 7.5 Q Q: l/mn
'''
import logging, threading, random
from modules import sensors as dev, utils, gpiolib as gpio
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
class YF_S201(dev.InputDevice):
waterflow_timeout = 2
pulse_counter = 0
start_counter = False
stop_counter = None
def __init__(self, pin, register=None, publish=None):
super().__init__(pin, register=register, callback=self.on_count_pulse, pull_up_down=gpio.IO_PULL_UP,)
self.publish = publish
threading.Thread(target=self.waterflow_process, args=()).start()
def waterflow_publish(self, flow=None):
if self.publish is not None:
self.publish(dict(
uuid=self.key,
topic="waterflow",
ts=utils.ts_now(),
flow=flow,
)
)
def on_count_pulse(self, channel):
if self.start_counter:
self.pulse_counter += 1
def wait_for_counts(self, timeout):
self.pulse_counter = 0
self.start_counter = True
threading.Event().wait(timeout)
self.start_counter = False
def waterflow_process(self):
self.stop_counter = threading.Event()
while not self.stop_counter.is_set():
self.wait_for_counts(self.waterflow_timeout)
flow = self.pulse_counter / 7.5
if flow:
logger.info(f'\nwaterflow_process flow = {flow}')
self.waterflow_publish(flow)
def stop(self):
self.stop_counter.set()
class Relay(dev.RelayFromShiftOut):
def __init__(self, shift_out_register, register=None, publish=None):
super().__init__(shift_out_register, register, relay_timeout=1.0)
self.publish = publish
self.start_time, self.end_time = 0, 0
self.started = False
self.duration_timer = threading.Event()
self.water_flow_timer = threading.Event()
self.flow, self.rate = 0.00, 0.00
self.flow_meter_start()
self.relay_publish('init', index=self.index, label=self.label, dev_type=self.dev_type)
def now(self):
return utils.ts_now_s()
def relay_publish(self, topic, **kwargs):
if self.publish is not None:
self.publish(topic, **dict(
uuid=self.key,
ts=utils.ts_now_s(),
relay_state=self.is_on(),
elapsed=self.time_elapsed(),
remaining=self.time_remaining(),
flow=self.flow,
rate=self.rate,
**kwargs
)
)
def time_elapsed(self):
elapsed = self.now() - self.start_time
if self.start_time <= 0 or elapsed <= 0:
elapsed = 0
return elapsed
def time_remaining(self):
remaining = self.end_time - self.now()
if remaining < 0:
remaining = 0
return remaining
def is_it_over(self, timeout):
elapsed = self.now() - self.start_time
if elapsed > timeout:
self.duration_timer.set()
def _open(self, timeout):
self.duration_timer = threading.Event()
self.started = True
while not self.duration_timer.is_set():
try:
if self.started:
self.is_it_over(timeout)
threading.Event().wait(1.0)
except Exception as e:
logger.error(f'run error {e}')
self.start_time = 0
self.relay_stop()
def relay_start(self, duration=0):
self.start_time = self.now()
self.end_time = self.start_time + duration
self.open()
if duration > 0:
threading.Thread(target=self._open, args=(duration, )).start()
self.rate, self.flow = 0.00, 0.00
self.begin = self.now()
self.water_flow_timer.set()
self.relay_publish('start')
def relay_stop(self):
self.duration_timer.set()
self.close()
self.started = False
self.water_flow_timer.clear()
self.relay_publish('stop')
def relay_halt(self):
if self.is_on():
self.started = False
def relay_restart(self):
if self.is_on():
self.started = True
def relay_status(self):
self.relay_publish('status')
def _flow_meter(self):
while True:
try:
self.water_flow_timer.wait()
elapsed = (self.now() - self.begin)/60
self.flow = random.randint(1050, 1065)/100 * elapsed
self.rate = self.flow / elapsed if elapsed > 0 else 0.00
threading.Event().wait(1.0)
except Exception as e:
logger.error(f'flow_meter_process error {e}')
threading.Event().wait(1.0)
def flow_meter_start(self):
self.begin = self.now()
threading.Thread(target=self._flow_meter).start()
+204
View File
@@ -0,0 +1,204 @@
'''
Created on 21 août 2022
@author: denis
'''
import logging
import os
import threading
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
if os.getenv('BOARD_DEVICE') == 'RPI':
print("found a RPI board")
import RPi.GPIO as GPIO
logger.info(GPIO.RPI_INFO) # @UndefinedVariable
GPIO.setmode(GPIO.BOARD) # @UndefinedVariable
elif os.getenv('BOARD_DEVICE') == 'RPI1B':
print("found a RPI 1B board")
import RPi.GPIO as GPIO # @Reimport
logger.info(GPIO.RPI_INFO) # @UndefinedVariable
GPIO.setmode(GPIO.BOARD) # @UndefinedVariable
elif os.getenv('BOARD_DEVICE') == 'OPIWIN+':
print("found an OPI WIN+ board")
import orangepi.winplus
from OPi import GPIO # @Reimport
GPIO.setmode(orangepi.winplus.BOARD)
elif os.getenv('BOARD_DEVICE') == 'OPIZERO':
print("found an OPI ZERO board")
import orangepi.zero
from OPi import GPIO # @Reimport
GPIO.setmode(orangepi.zero.BOARD)
GPIO.setwarnings(False) # @UndefinedVariable
IO_RISING = GPIO.RISING # @UndefinedVariable
IO_FALLING = GPIO.FALLING # @UndefinedVariable
IO_BOTH = GPIO.BOTH # @UndefinedVariable
IO_LOW = GPIO.LOW # @UndefinedVariable
IO_HIGH = GPIO.HIGH # @UndefinedVariable
IO_INPUT = GPIO.IN # @UndefinedVariable
IO_OUTPUT = GPIO.OUT # @UndefinedVariable
IO_PULL_UP = GPIO.PUD_UP
IO_PULL_DOWN = GPIO.PUD_DOWN
IO_PULL_OFF = GPIO.PUD_OFF
IO_CLEANUP = GPIO.cleanup # @UndefinedVariable
lock = threading.Lock()
def bitread(b, bitpos):
return (b>>bitpos) & 0x1
class Pin:
def __init__(self, channel, sens, initial=None, callback=None, edge=IO_FALLING, bouncetime=500, pull_up_down=GPIO.PUD_OFF):
self.channel = channel
self.sens = sens
lock.acquire()
if initial in [GPIO.LOW, GPIO.HIGH] and sens==GPIO.OUT:
GPIO.setup(channel, sens, initial=initial, pull_up_down=pull_up_down)
else:
GPIO.setup(channel, sens, pull_up_down=pull_up_down)
if callback is not None:
self.set_interrupt(edge, callback, bouncetime)
lock.release()
logger.info(f'Channel {self.channel} sens: {sens} initial: {initial}')
self.value = self.read() # initial value
def cleanup(self):
lock.acquire()
GPIO.cleanup(self.channel)
lock.release()
def set_interrupt(self, edge, callback, bouncetime=None):
if callable(callback) and edge in [IO_RISING, IO_FALLING, IO_BOTH]:
GPIO.add_event_detect(self.channel, edge, callback=callback, bouncetime=bouncetime)
def remove_interrupt(self):
lock.acquire()
GPIO.remove_event_detect(self.channel)
lock.release()
def state(self):
return self.value
def read(self):
lock.acquire()
self.value = GPIO.input(self.channel)
lock.release()
#logger.info(f'Read {self.channel} -> {self.value}')
return self.value
def write(self, value):
lock.acquire()
GPIO.output(self.channel, value)
lock.release()
#logger.info(f'Write {self.channel} <- {value}')
return self.read()
class ShiftInRegister74HC165():
lastState = 0;
currentState = 0;
def __init__(self, latch_pin, clock_pin, data_pin, number=1):
self.datalen = number * 8
self.latch_pin = Pin(latch_pin, IO_OUTPUT, initial=IO_LOW, pull_up_down=IO_PULL_DOWN)
self.clock_pin = Pin(clock_pin, IO_OUTPUT, initial=IO_LOW, pull_up_down=IO_PULL_DOWN)
self.data_pin = Pin(data_pin, IO_INPUT, initial=IO_LOW, pull_up_down=IO_PULL_DOWN)
threading.Event().wait(0.5)
def read(self):
self.lastState = self.currentState
result = 0
self.latch_pin.write(IO_HIGH)
for i in range(self.datalen):
value = self.data_pin.read()
result |= (value << ((self.datalen-1) - i))
self.clock_pin.write(IO_HIGH)
self.clock_pin.write(IO_LOW)
self.latch_pin.write(IO_LOW)
self.currentState = result
return result
def state(self, idx):
return bitread(self.currentState, idx)
def last(self, idx):
return bitread(self.lastState, idx)
def update(self):
return self.read()!=self.lastState
def hasChanged(self, idx=None):
return self.lastState!=self.currentState if idx is None else self.state(idx)!=self.last(idx)
def pressed(self, idx):
return not self.last(idx) and self.state(idx)
def released(self, idx):
return self.last(idx) and not self.state(idx)
def cleanup(self):
self.latch_pin.cleanup()
self.clock_pin.cleanup()
self.data_pin.cleanup()
class ShiftOutRegister74HC595():
def __init__(self, latch_pin, clock_pin, data_pin, number=1):
self.datalen = number * 8
self.latch_pin = Pin(latch_pin, IO_OUTPUT, initial=IO_LOW)
self.clock_pin = Pin(clock_pin, IO_OUTPUT, initial=IO_LOW)
self.data_pin = Pin(data_pin, IO_OUTPUT, initial=IO_LOW)
self.set_low()
threading.Event().wait(0.5)
def set_low(self):
self.registers = [0] * self.datalen
self.write()
def write(self):
self.clock_pin.write(IO_LOW)
self.latch_pin.write(IO_LOW)
self.clock_pin.write(IO_HIGH)
#
#for i in range(self.datalen):
for bit in self.registers:
self.clock_pin.write(IO_LOW)
self.data_pin.write(int(bit))
self.clock_pin.write(IO_HIGH)
#
self.clock_pin.write(IO_LOW)
self.latch_pin.write(IO_HIGH)
self.clock_pin.write(IO_HIGH)
def set(self, index, state):
self.registers[index] = int(state)
self.write()
return int(state)
def get(self, index):
return self.registers[index]
def cleanup(self):
self.set_low()
self.latch_pin.cleanup()
self.clock_pin.cleanup()
self.data_pin.cleanup()
+88
View File
@@ -0,0 +1,88 @@
'''
Created on 29 août 2022
@author: denis
'''
import logging
import threading
from RPLCD.i2c import CharLCD
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
#lcd = CharLCD(settings.LCD_EXPANDER, settings.LCD_ADDR, port=settings.LCD_PORT)
class LCD():
def __init__(self, lcd, timeout=None):
self.lcd = lcd
self.waiter = threading.Event()
self.cols = 16
self.timeout = timeout
self.screen_enabled = True
@property
def screen_enabled(self):
return self._enabled
@screen_enabled.setter
def screen_enabled(self, v):
self.lcd.backlight_enabled = v
self._enabled = v
def create_char(self, pos, cc):
self.lcd.create_char(pos, cc)
# LCD display
def _display(self, event, msg, timeout):
self.screen_enabled = True
self.lcd.clear()
self.lcd.write_string(msg)
event.wait(timeout)
self.screen_enabled = False
def write_msg(self, msg, timeout=5):
if not self.waiter.is_set():
self.waiter.set()
self.waiter = threading.Event()
threading.Thread(target=self._display, args=(self.waiter, msg, timeout, )).start()
def _screen_enable(self, event, timeout):
self.screen_enabled = True
event.wait(timeout)
self.screen_enabled = False
def screen_disable(self):
if not self.waiter.is_set():
self.waiter.set()
self.screen_enabled = False
def screen_enable(self):
if self.timeout is not None:
if not self.waiter.is_set():
self.waiter.set()
self.waiter = threading.Event()
threading.Thread(target=self._screen_enable, args=(self.waiter, self.timeout, )).start()
else:
self.screen_enabled = True
def display(self, msg, pos=(0, 0), align=None, clear=True):
self.screen_enable()
if clear:
self.lcd.clear()
self.lcd.cursor_pos = pos
if align=='left':
self.lcd.write_string(msg.ljust(self.cols))
elif align=='right':
self.lcd.write_string(msg.rjust(self.cols))
else:
self.lcd.write_string(msg)
+159
View File
@@ -0,0 +1,159 @@
#
# mqtt service
#
import json, time, logging, ssl
import paho.mqtt.client as mqtt
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class MqttBase(object):
def __init__(self, **p):
super().__init__()
self.host = p.get('host')
self.port = p.get('port')
self.username = p.get('username')
self.password = p.get('password')
self.keepalive = p.get('keepalive')
self.use_ssl = p.get('use_ssl', False)
self.ca_cert = p.get('ca_cert')
self.tls_version = p.get('tls_version', ssl.PROTOCOL_TLSv1_2)
self.subscriptions = [(topic, qos) for topic, qos in p.get('topic_subs', [])]
self.unsubs = self.client_get_unsubs()
self.topic_base = p.get('topic_base', '')
self.on_message_callback = p.get('on_messages', self._on_message_callback)
self.on_bytes_callback = p.get('on_bytes', self._on_bytes_callback)
# mqtt Client
self.client = mqtt.Client()
if self.username:
self.client.username_pw_set(username=self.username, password=self.password)
if self.use_ssl and self.ca_cert:
self.client.tls_set(ca_certs=self.ca_cert, tls_version=self.tls_version)
self.client.on_connect = self._on_connect
self.client.on_disconnect = self._on_disconnect
self.client.on_message = self._on_message
self.client.on_log = self._on_log
def client_get_unsubs(self):
return [topic for topic, _ in self.subscriptions ]
def client_set_subscriptions(self):
self.client.unsubscribe(self.client_get_unsubs())
self.client.subscribe(self.subscriptions)
logger.info(f"\n client_set_subscription {self.subscriptions}")
def client_add_subscriptions(self, subs=[]):
self.subscriptions += subs
self.subscriptions = list(set(self.subscriptions))
def _publish_message(self, topic, **payload):
try:
qos = payload.pop('qos', 0)
retain = payload.pop('retain', False)
message = json.dumps(payload)
self.client.publish(topic, payload=message.encode('utf-8'), qos=qos, retain=retain)
except Exception as e:
logger.error(e)
def _publish_bytes(self, topic, payload, **conf):
try:
qos = conf.pop('qos', 0)
retain = conf.pop('retain', False)
self.client.publish(topic, payload=payload, qos=qos, retain=retain)
except Exception as e:
logger.error(f"\n _publish_bytes error: {e}")
def _on_log(self, mqttc, obj, level, string): # @UnusedVariable
pass
def _on_connect_info(self, info):
logger.info(info)
def _on_connect(self, client, userdata, flags, rc):
msg = f"Client mqtt\n {client._client_id} connected\n status {rc}\n host={self.host}:{self.port}\n username={self.username}"
try:
if rc:
raise
self.client_set_subscriptions()
self._on_connect_info(msg)
except Exception as e:
logger.error(f"\n _on_connect error {e}")
def _on_disconnect(self, client, userdata, rc):
logger.info(f"Client mqtt\n Disconnected: {client._client_id} with status {rc}")
try:
j = 3
for i in range(j):
#client.unsubscribe(self.unsubs)
try:
client.reconnect()
break
except Exception as e:
if i < j:
logger.warn(e)
time.sleep(1)
continue
else:
raise
except Exception as e:
logger.error(f"Client mqtt\n _on_disconnect error {e}")
def _on_stop_mqtt(self):
pass
def _on_message_callback(self, topic, payload):
pass
def _on_bytes_callback(self, topic, payload):
pass
def _on_message(self, client, userdata, message):
try:
try:
payload = json.loads(message.payload.decode("utf-8"))
self.on_message_callback(message.topic, payload)
except:
self.on_bytes_callback(message.topic, message.payload)
except Exception as e:
logger.error(f"\n==== _on_message error {e}\n {message.topic} {message.payload[:80]}")
def connectMQTT(self):
self.client.connect_async(self.host, self.port, self.keepalive)
def loop_forever(self):
self.connectMQTT()
self.client.loop_forever()
def startMQTT(self):
self.connectMQTT()
self.client.loop_start()
def stopMQTT(self):
self._on_stop_mqtt()
self.client.loop_stop()
self.client.disconnect()
+193
View File
@@ -0,0 +1,193 @@
'''
Created on 22 août 2022
@author: denis
'''
import logging
import threading
from . import gpiolib as gpio
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
class DeviceRegister:
def __init__(self, register:dict):
"""
register must contain
{'dev_type': 'None', 'index': 0, 'key': 'default', 'label': 'Default label', 'status': 0, 'active': 1}
"""
self.register = register.copy()
@property
def dev_type(self):
return self.register.get('dev_type')
@property
def status(self):
return self.register.get('status')
@status.setter
def status(self, v):
self.register['status'] = int(v)
@property
def active_pin(self):
return self.register.get('active')
@property
def key(self):
return self.register.get('key')
@property
def label(self):
return self.register.get('label')
@property
def index(self):
return self.register.get('index')
class Device(DeviceRegister):
def __init__(self, pin, register=None, sens=gpio.IO_OUTPUT, initial=None, callback=None, edge=gpio.IO_FALLING, bouncetime=500, pull_up_down=None):
super().__init__(register)
self.pin = gpio.Pin(pin, sens=sens, initial=initial, callback=callback, edge=edge, bouncetime=bouncetime, pull_up_down=pull_up_down)
self.sens = sens
self.active = gpio.IO_HIGH if self.active_pin else gpio.IO_LOW
self.not_active = gpio.IO_LOW if self.active_pin else gpio.IO_HIGH
self.is_opened = self.is_on
self.is_closed = self.is_off
self.open = self.on
self.close = self.off
if self.sens == gpio.IO_OUTPUT:
if initial==gpio.IO_HIGH:
self.on()
elif initial==gpio.IO_LOW:
self.off()
def state(self):
return self.pin.read()
def on(self):
if self.sens == gpio.IO_OUTPUT:
return self.pin.write(self.active)
def off(self):
if self.sens == gpio.IO_OUTPUT:
return self.pin.write(self.not_active)
def is_on(self):
return self.state() == self.active
def is_off(self):
return self.state() == self.not_active
def toggle(self):
if self.is_on():
return self.off()
if self.is_off():
return self.on()
class Led(Device):
def __init__(self, pin, register=None, initial=gpio.IO_LOW):
super().__init__(pin, register=register, sens=gpio.IO_OUTPUT, initial=initial)
class Relay(Device):
def __init__(self, pin, register=None, initial=gpio.IO_LOW):
super().__init__(pin, register=register, sens=gpio.IO_OUTPUT, initial=initial)
class Switch(Device):
def __init__(self, pin, register=None, callback=None, edge=gpio.IO_BOTH):
super().__init__(pin, register=register, callback=callback, sens=gpio.IO_INPUT, edge=edge)
def on(self):
pass
def off(self):
pass
class Button(Device):
def __init__(self, pin, register=None, callback=None, bouncetime=500):
super().__init__(pin, register=register, callback=callback, sens=gpio.IO_INPUT, edge=gpio.IO_FALLING, bouncetime=bouncetime)
self._state = self.not_active
def state(self):
return self._state
def on(self):
self._state = self.active
def off(self):
self._state = self.not_active
class InputDevice(Device):
def __init__(self, pin, register=None, callback=None, pull_up_down=gpio.IO_PULL_UP):
super().__init__(pin, register=register, callback=callback, sens=gpio.IO_INPUT, edge=gpio.IO_FALLING, pull_up_down=pull_up_down)
def on(self):
pass
def off(self):
pass
class RelayFromShiftOut(DeviceRegister):
def __init__(self, shift_out_register, register=None, relay_timeout=0, closed=True):
super().__init__(register)
self.shift_out_register = shift_out_register
self.max_index = shift_out_register.datalen
self.active = gpio.IO_HIGH if self.active_pin else gpio.IO_LOW
self.not_active = gpio.IO_LOW if self.active_pin else gpio.IO_HIGH
self.relay_timeout = 0
self.is_opened = self.is_on
self.is_closed = self.is_off
self.open = self.on
self.close = self.off
if closed:
self.off()
else:
self.on()
self.relay_timeout = relay_timeout
logger.info(f'RelayShiftOut Init index {self.index}')
def wait_after_event(self, timeout):
if timeout:
threading.Event().wait(timeout)
def on(self):
self.status = self.shift_out_register.set(self.index, self.active)
self.wait_after_event(self.relay_timeout) # wait after opening
logger.info(f'RelayShiftOut {self.index} on')
return self.status
def off(self):
self.status = self.shift_out_register.set(self.index, self.not_active)
self.wait_after_event(self.relay_timeout) # wait after closing
logger.info(f'RelayShiftOut {self.index} off')
return self.status
def is_on(self):
self.status = self.shift_out_register.get(self.index) == self.active
return self.status
def is_off(self):
self.status = self.shift_out_register.get(self.index) == self.not_active
return self.status
def toggle(self):
if self.is_on():
return self.off()
if self.is_off():
return self.on()
+138
View File
@@ -0,0 +1,138 @@
'''
Created on 3 févr. 2026
@author: denis
'''
from django.conf import settings
# myapp/system_stats.py
import threading
import time
import os
import psutil
# intervale de mise à jour (secondes)
REFRESH_INTERVAL = 5
RAMDISK = settings.RAMDISK_DEVICE
_cache = {
"shm": [],
"cpu_info": {},
"memory_info": {},
"disk_info": {},
"ramdisk_info": {},
"updated_at": None
}
_lock = threading.Lock()
_timer = None
def _collect_once():
data = {}
# shm: liste /dev/shm si disponible
try:
path = "/dev/shm"
data["shm"] = os.listdir(path) if os.path.exists(path) and os.path.isdir(path) else []
except Exception as e:
data["shm_error"] = str(e)
# cpu_info
try:
cpu_times = psutil.cpu_times_percent(interval=None, percpu=False)._asdict()
data["cpu_info"] = {
"cpu_count": psutil.cpu_count(logical=True),
"cpu_count_physical": psutil.cpu_count(logical=False),
"cpu_percent": psutil.cpu_percent(interval=None),
"cpu_times_percent": cpu_times
}
except Exception as e:
data["cpu_info_error"] = str(e)
# memory_info
try:
vm = psutil.virtual_memory()._asdict()
sm = psutil.swap_memory()._asdict()
data["memory_info"] = {"virtual_memory": vm, "swap_memory": sm}
except Exception as e:
data["memory_info_error"] = str(e)
# disk_info (root and partitions)
# ex: if mountpoint == "/ramdisk" and fstype=="tmpfs" then usage.percent, usage.free, etc ...
try:
usage_root = psutil.disk_usage("/")._asdict()
parts = []
for p in psutil.disk_partitions(all=False):
try:
du = psutil.disk_usage(p.mountpoint)._asdict()
except Exception:
du = {}
parts.append({"device": p.device, "mountpoint": p.mountpoint, "fstype": p.fstype, "usage": du})
data["disk_info"] = {"root": usage_root, "partitions": parts}
except Exception as e:
data["disk_info_error"] = str(e)
# ramdisk
# ex: if mountpoint == "/ramdisk" and fstype=="tmpfs" then usage.percent, usage.free, etc ...
try:
for part in psutil.disk_partitions(all=True):
if part.mountpoint == RAMDISK and part.fstype.lower() == "tmpfs":
usage = psutil.disk_usage(part.mountpoint)
data["ramdisk_info"] = {
"percent": usage.percent,
"mount": part.mountpoint,
"device": part.device,
"fstype": part.fstype,
"total": usage.total,
"used": usage.used,
"free": usage.free,
}
except Exception as e:
data["ramdisk_info_error"] = str(e)
data["updated_at"] = time.time()
return data
def _update_cache():
global _timer
try:
new = _collect_once()
with _lock:
_cache.update(new)
finally:
# reprogrammer
_timer = threading.Timer(REFRESH_INTERVAL, _update_cache)
_timer.daemon = True
_timer.start()
def start_background_updater(interval_seconds: int = None):
global REFRESH_INTERVAL, _timer
if interval_seconds:
REFRESH_INTERVAL = interval_seconds
if _timer is not None:
return
# première collecte synchronisée
with _lock:
_cache.update(_collect_once())
_timer = threading.Timer(REFRESH_INTERVAL, _update_cache)
_timer.daemon = True
_timer.start()
def stop_background_updater():
global _timer
if _timer is not None:
_timer.cancel()
_timer = None
def get_cached_stats():
with _lock:
# retourner copie pour sécurité
return dict(_cache)
+227
View File
@@ -0,0 +1,227 @@
'''
Created on 20 avr. 2022
@author: denis
'''
import os
import yaml
import time
import importlib
from datetime import datetime
import string, secrets
import uuid
from threading import Event, Thread
from urllib.parse import urlsplit
import asyncio
import mmap
import fcntl
import psutil
SHM_DIR = "/dev/shm"
def open_shm(name: str, size: int, create=True):
path = os.path.join(SHM_DIR, name)
flags = os.O_RDWR | (os.O_CREAT if create else 0)
fd = os.open(path, flags)
try:
if create:
os.ftruncate(fd, size)
mm = mmap.mmap(fd, size)
finally:
os.close(fd)
return mm, path
def read_shm(name: str, size: int):
mm, path = open_shm(name, size, create=False) # @UnusedVariable
try:
mm.seek(0)
data = mm.read(size)
return data.rstrip(b"\0")
finally:
mm.close()
def write_shm(name: str, size: int, data: bytes):
mm, path = open_shm(name, size, create=True)
fd = os.open(path, os.O_RDWR)
try:
fcntl.flock(fd, fcntl.LOCK_EX)
try:
mm.seek(0)
mm.write(data.ljust(size, b"\0")[:size])
mm.flush()
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
finally:
mm.close()
os.close(fd)
def get_tmpfs_info(mount_point="/ramdisk"):
def sizeof(n):
for unit in ['B','KB','MB','GB','TB']:
if n < 1024:
return f"{n:.1f}{unit}"
n /= 1024
return f"{n:.1f}PB"
usage = None
for part in psutil.disk_partitions(all=True):
if part.mountpoint == mount_point and part.fstype.lower() == "tmpfs":
usage = psutil.disk_usage(part.mountpoint)
print(f"Mount: {part.mountpoint}")
print(f" Device: {part.device}")
print(f" Fstype: {part.fstype}")
print(f" Total: {usage.total} bytes ({sizeof(usage.total)})")
print(f" Used: {usage.used} bytes ({sizeof(usage.used)})")
print(f" Free: {usage.free} bytes ({sizeof(usage.free)})")
print(f" Percent used: {usage.percent}%")
break
return {
"percent": usage.percent,
"mount": part.mountpoint,
"device": part.device,
"fstype": part.fstype,
"total": usage.total,
"used": usage.used,
"free": usage.free,
}
def get_cpu_info():
# cpu percent par coeur et moyennes load
return {
"cpu_percent_per_cpu": psutil.cpu_percent(interval=0.5, percpu=True),
"cpu_percent_total": psutil.cpu_percent(interval=None),
"load_avg": os.getloadavg(), # (1,5,15)
"cpu_count": psutil.cpu_count(logical=True),
}
def get_memory_info():
vm = psutil.virtual_memory()
sm = psutil.swap_memory()
return {
"total": vm.total,
"available": vm.available,
"used": vm.used,
"free": vm.free,
"percent": vm.percent,
"swap_total": sm.total,
"swap_used": sm.used,
"swap_free": sm.free,
"swap_percent": sm.percent,
}
def get_disk_info(path="/"):
du = psutil.disk_usage(path)
return {
"path": path,
"total": du.total,
"used": du.used,
"free": du.free,
"percent": du.percent,
}
def extract_host_port_path(url, default_port=None):
"""
Retoure (host, port, path) où:
- host: string (IP ou hostname) ou None
- port: int ou None (utilise default_port si fourni et aucun port explicite)
- path: string (chemin + query + fragment si présents), ou '' si absent
"""
parts = urlsplit(url if '://' in url else '//' + url, scheme='')
host = parts.hostname
port = parts.port or default_port
# Reconstruire path complet: path + ('?' + query) + ('#' + fragment)
path = parts.path or ''
if parts.query:
path += '?' + parts.query
if parts.fragment:
path += '#' + parts.fragment
return host, port, path
def image_path(imagefile):
image_path = imagefile.path
pdir = os.path.dirname(image_path)
os.makedirs(pdir, exist_ok=True)
return str(image_path)
def serialize_datetime(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError("Type not serializable")
def start_async():
loop = asyncio.new_event_loop()
Thread(target=loop.run_forever, daemon=True).start()
return loop
def stop_async(loop):
loop.call_soon_threadsafe(loop.stop)
def submit_async(loop, awaitable):
return asyncio.run_coroutine_threadsafe(awaitable, loop)
def to_choice(d):
choices = []
for k, v in d.items():
choices.append((k, v))
return choices
def get_instance_class(module):
modulename, classname = module.rsplit(".", 1)
return getattr(importlib.import_module(modulename), classname)
def wait_for(timeout):
Event().wait(timeout)
def yaml_load(f):
with open(f, 'r') as stream:
return yaml.safe_load(stream)
return {}
def yaml_save(f, context):
with open(f, 'w') as stream:
yaml.dump(context, stream, default_flow_style = False)
def get_uuid():
return str(hex(uuid.getnode()))[2:]
def millis():
return round(time.time() * 1000)
def now():
return datetime.now()
def ts_now():
# float second
return now().timestamp()
def ts_now_s():
return int(ts_now())
def ts_now_ms():
return int(ts_now()*1000)
def ts_now_us():
return int(ts_now()*1000000)
def random_num(n=16):
alphabet = string.digits
return ''.join(secrets.choice(alphabet) for i in range(n)) # @UnusedVariable
def random_chars(n=6):
alphabet = string.ascii_letters + string.digits
return ''.join(secrets.choice(alphabet) for i in range(n)) # @UnusedVariable
def get_apikey(n=32):
chars = 'abcdefgh01234ijklABCD4567EFGHIJKLmnopqrstuvwxyz0123456789MNOPQRS789TUVWXYZ'
return ''.join(secrets.choice(chars) for i in range(n)) # @UnusedVariable
def gen_keywords(s):
c = s.replace(',', ' ').replace('+', ' ')
return [ w.strip() for w in c.split(' ') if w]
def gen_device_uuid(n=19):
return hex(int(random_num(n)))[2:]
def get_device_uuid(n=19):
return f'0x{gen_device_uuid(n)}'