clear config and launcher

This commit is contained in:
flandre 2025-05-11 17:35:37 +08:00
parent 82fc705c0e
commit e87b95ba3a
27 changed files with 254 additions and 227 deletions

View File

@ -1,47 +1,146 @@
import shutil
import subprocess
import dataclasses
import json
import sys
from pathlib import Path
import click
import platformdirs
MODULE_FOLDER = Path(__file__).parent
ASSETS = MODULE_FOLDER / 'assets'
PYQT = MODULE_FOLDER / 'pyqt'
DEV_PROJECT_FOLDER = MODULE_FOLDER.parent
@click.command()
@click.option('--data_folder', default=None)
@click.option('--generate_pyqt', default=True)
@click.option('--dev/--no-dev', default=True)
@click.option('-p', '--path', type=str,
default=platformdirs.user_config_path('Flandre', 'Scarlet') / 'launch.toml',
help='Path to launch.toml'
)
def entrypoint(data_folder, generate_pyqt, dev, path):
from flandre.config import C
from flandre.launcher import launch_from_file
if dev and '--dev' not in sys.argv:
sys.argv.append('--dev')
if (pyuic6 := shutil.which('pyuic6')) is None:
print('pyuic6 is not installed')
return
if generate_pyqt:
subprocess.run([pyuic6, '-o', PYQT / 'Main.py', PYQT / 'Main.ui'])
subprocess.run([pyuic6, '-o', PYQT / 'Image.py', PYQT / 'Image.ui'])
if data_folder is not None:
C.data_folder = Path(data_folder)
path = Path(path)
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('[MainUI]\n')
print('Use launch config', path)
launch_from_file(path)
class P:
ASSETS = MODULE_FOLDER / 'assets'
PYQT = MODULE_FOLDER / 'pyqt'
DEV_PROJECT_FOLDER = MODULE_FOLDER.parent
if __name__ == '__main__':
entrypoint()
@dataclasses.dataclass
class SoftwareConfig:
def s(self, host, port, proto='tcp'):
return f'{proto}://{host}:{port}'
data_folder_: Path = platformdirs.user_data_path('Flandre', 'Scarlet') / 'record'
config_folder: Path = platformdirs.user_config_path('Flandre', 'Scarlet')
@property
def software_config_file(self):
return self.config_folder / 'software.json'
@property
def data_folder(self):
self.data_folder_.mkdir(exist_ok=True, parents=True)
return self.data_folder_
@data_folder.setter
def data_folder(self, value):
self.data_folder_ = value
@property
def imaging_config_folder(self):
p = self.config_folder / 'imaging'
p.mkdir(exist_ok=True, parents=True)
return p
@property
def device_config_folder(self):
p = self.config_folder / 'device'
p.mkdir(exist_ok=True, parents=True)
return p
video_height: int = 1080
video_width: int = 960
live_ip: str = '11.6.1.71'
live_push_port: int = 5555
live_rep_port: int = 5556
device_py_rep_port: int = 5558
@property
def live_push_socket(self):
return f'tcp://{self.live_ip}:{self.live_push_port}'
@property
def live_rep_socket(self):
return f'tcp://{self.live_ip}:{self.live_rep_port}'
@property
def live_rep_socket_http(self):
return f'http://{self.live_ip}:{self.live_rep_port}'
@property
def device_py_rep_socket(self):
return f'tcp://{self.live_ip}:{self.device_py_rep_port}'
playback_port: int = 5003
@property
def playback_socket(self):
return f'tcp://127.0.0.1:{self.playback_port}'
local_ip: str = '127.0.0.1'
mi_rep_port: int = 5557
muxer_rep_port: int = 5560
driver_rep_port: int = 5561
@property
def mi_rep_socket(self):
return self.s(self.local_ip, self.device_py_rep_port)
@property
def muxer_rep_socket(self):
return self.s(self.local_ip, self.muxer_rep_port)
@property
def driver_rep_socket(self):
return self.s(self.local_ip, self.driver_rep_port)
switch1_ip: str = 'c1'
switch1_token: str = '7ad51e0016e7a9d22f753d5110f76c7d'
switch2_ip: str = 'c2'
switch2_token: str = 'bf5a7b77a1ba3761ea63fafd8427b7d6'
@staticmethod
def read_config(path: Path):
return SoftwareConfig.read_config_text(path.read_text(encoding='utf-8'))
@staticmethod
def read_config_text(text: str):
j = json.loads(text)
arg_d = dict()
for field in dataclasses.fields(SoftwareConfig):
try:
v = j[field.name]
match field.type.__name__:
case 'Path':
arg_d[field.name] = Path(v)
case _:
arg_d[field.name] = v
except KeyError:
pass
sc = SoftwareConfig(**arg_d)
return sc
@property
def json_text(self):
arg_d = dict()
for field in dataclasses.fields(SoftwareConfig):
v = self.__getattribute__(field.name)
match field.type.__name__:
case 'Path':
arg_d[field.name] = str(v)
case _:
arg_d[field.name] = v
return json.dumps(arg_d, indent=4)
def write_config(self, config_file=None):
if config_file is None:
config_file = self.software_config_file
config_file.parent.mkdir(exist_ok=True, parents=True)
config_file.write_text(self.json_text, encoding='utf-8')
def copy_form(self, arg: 'SoftwareConfig'):
for field in dataclasses.fields(SoftwareConfig):
self.__setattr__(field.name, arg.__getattribute__(field.name))
C = SoftwareConfig()

View File

@ -5,9 +5,12 @@ where f(y,x) is the pixel distance in echo pulse RF signal between point(0,0) an
import numpy as np
from scipy.optimize import fsolve
from flandre.config import DS
from flandre.utils.Config import DeviceConfig
# from flandre.config import DS
DS = None
def refraction_dist(dev_cfg=DeviceConfig()):
v1 = dev_cfg.v1

View File

@ -1,133 +0,0 @@
import dataclasses
import json
import sys
from pathlib import Path
import platformdirs
from flandre import DEV_PROJECT_FOLDER
ISDEV = False
if '--dev' in sys.argv:
ISDEV = True
CONFIG_FOLDER = platformdirs.user_config_path('Flandre', 'Scarlet')
if ISDEV:
CONFIG_FOLDER = DEV_PROJECT_FOLDER / 'config'
DS = DEV_PROJECT_FOLDER / '@DS'
DS.mkdir(exist_ok=True, parents=True)
SOFTWARE_CONFIG_PATH = CONFIG_FOLDER / 'software.json'
@dataclasses.dataclass
class SoftwareConfig:
def s(self, host, port, proto='tcp'):
return f'{proto}://{host}:{port}'
data_folder: Path = DS
@property
def imaging_config_folder(self):
p = CONFIG_FOLDER / 'imaging'
p.mkdir(exist_ok=True, parents=True)
return p
@property
def device_config_folder(self):
p = CONFIG_FOLDER / 'device'
p.mkdir(exist_ok=True, parents=True)
return p
video_height: int = 1080
video_width: int = 960
live_ip: str = '11.6.1.71'
live_push_port: int = 5555
live_rep_port: int = 5556
device_py_rep_port: int = 5558
@property
def live_push_socket(self):
return f'tcp://{self.live_ip}:{self.live_push_port}'
@property
def live_rep_socket(self):
return f'tcp://{self.live_ip}:{self.live_rep_port}'
@property
def live_rep_socket_http(self):
return f'http://{self.live_ip}:{self.live_rep_port}'
@property
def device_py_rep_socket(self):
return f'tcp://{self.live_ip}:{self.device_py_rep_port}'
playback_port: int = 5003
@property
def playback_socket(self):
return f'tcp://127.0.0.1:{self.playback_port}'
local_ip: str = '127.0.0.1'
mi_rep_port: int = 5557
muxer_rep_port: int = 5560
driver_rep_port: int = 5561
@property
def mi_rep_socket(self):
return self.s(self.local_ip, self.device_py_rep_port)
@property
def muxer_rep_socket(self):
return self.s(self.local_ip, self.muxer_rep_port)
@property
def driver_rep_socket(self):
return self.s(self.local_ip, self.driver_rep_port)
switch1_ip: str = 'c1'
switch1_token: str = '7ad51e0016e7a9d22f753d5110f76c7d'
switch2_ip: str = 'c2'
switch2_token: str = 'bf5a7b77a1ba3761ea63fafd8427b7d6'
@staticmethod
def read_config(path: Path):
j = json.loads(path.read_text(encoding='utf-8'))
arg_d = dict()
for field in dataclasses.fields(SoftwareConfig):
try:
v = j[field.name]
match field.type:
case Path():
arg_d[field.name] = Path(v)
case _:
arg_d[field.name] = v
except KeyError:
pass
sc = SoftwareConfig(**arg_d)
return sc
def write_config(self):
arg_d = dict()
for field in dataclasses.fields(SoftwareConfig):
v = self.__getattribute__(field.name)
match v:
case Path():
arg_d[field.name] = str(v)
case _:
arg_d[field.name] = v
SOFTWARE_CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True)
SOFTWARE_CONFIG_PATH.write_text(json.dumps(arg_d, indent=4), encoding='utf-8')
C = SoftwareConfig()
if SOFTWARE_CONFIG_PATH.exists() and not ISDEV:
C = SoftwareConfig.read_config(SOFTWARE_CONFIG_PATH)
if __name__ == '__main__':
print(C)

View File

@ -1,33 +1,24 @@
import logging
import os
import subprocess
import sys
from pathlib import Path
from PyQt6 import QtWidgets
import flandre
from flandre.config import C
from flandre import C
from flandre.nodes.MainUI import MainUI
def kde_pyqt6_mainui():
subprocess.run(['python', __file__, *sys.argv[1:]],
def kde_pyqt6_mainui(software_config):
flandre.C.write_config()
subprocess.run(['python', __file__],
env=dict(XDG_CURRENT_DESKTOP="KDE",
XDG_RUNTIME_DIR="/run/user/1000",
XDG_SESSION_TYPE="wayland",
PYTHONPATH=os.environ.get('PYTHONPATH', flandre.MODULE_FOLDER.parent))
PYTHONPATH=os.environ.get('PYTHONPATH', flandre.MODULE_FOLDER.parent),
FLANDRE_CONFIG=software_config.json_text,
)
)
if __name__ == '__main__':
if '--dev' in sys.argv:
print('qt styles:', QtWidgets.QStyleFactory.keys())
try:
idx = sys.argv.index('--data_folder')
C.data_folder = Path(sys.argv[idx + 1])
except ValueError:
pass
logging.basicConfig(level=logging.INFO)
MainUI()()
MainUI()(software_config=C.read_config_text(os.environ['FLANDRE_CONFIG']))

View File

@ -1,30 +1,35 @@
import logging
import multiprocessing
import os
import time
import shutil
import subprocess
import tomllib
from enum import Enum
from pathlib import Path
import click
import platformdirs
from flandre import C
from flandre import P
from flandre.BusClient import BusClient
from flandre.kde_pyqt6_mainui import kde_pyqt6_mainui
from flandre.nodes.Beamformer import Beamformer
from flandre.nodes.Broker import Broker
from flandre.nodes.Device import Device
from flandre.nodes.ImageCV import ImageCV
from flandre.nodes.Loader import Loader
from flandre.nodes.MainUI import MainUI
from flandre.nodes.Muxer import Muxer
from flandre.nodes.Robot import Robot
from flandre.nodes.ImageFFMPEG import ImageFFMPEG
from flandre.nodes.ImageQt import ImageQt
from flandre.nodes.Midi import Midi
from flandre.nodes.Loader import Loader
from flandre.nodes.MainUI import MainUI
from flandre.nodes.Mi import Mi
from flandre.nodes.Midi import Midi
from flandre.nodes.Muxer import Muxer
from flandre.nodes.Recorder import Recorder
from flandre.nodes.Web import Web
from flandre.nodes.Robot import Robot
from flandre.nodes.VideoQt import VideoQt
from flandre.nodes.Web import Web
from flandre.utils.Msg import KillMsg, NodeOnlineMsg, Msg1, Msg2
from flandre.config import CONFIG_FOLDER
class LaunchComponent(Enum):
@ -47,7 +52,7 @@ class LaunchComponent(Enum):
def launch(arg: dict[LaunchComponent, dict]):
logging.basicConfig(level=logging.INFO)
multiprocessing.set_start_method('spawn')
bp = multiprocessing.Process(target=Broker(broker=True))
bp = multiprocessing.Process(target=Broker(broker=True), kwargs=dict(software_config=C))
bp.start()
ps = []
for k, v in arg.items():
@ -58,7 +63,7 @@ def launch(arg: dict[LaunchComponent, dict]):
pps = []
for p in ps:
pps.append(multiprocessing.Process(target=p))
pps.append(multiprocessing.Process(target=p, kwargs=dict(software_config=C)))
for p in pps:
p.start()
@ -100,5 +105,36 @@ def launch_from_file(file: Path):
launch(arg_d)
@click.command()
@click.option('--data_folder', default=None)
@click.option('--generate_pyqt', default=True)
@click.option('--dev/--no-dev', default=True)
@click.option('-p', '--path', type=str,
default=platformdirs.user_config_path('Flandre', 'Scarlet') / 'launch.toml',
help='Path to launch.toml'
)
def entrypoint(data_folder, generate_pyqt, dev, path):
if dev:
C.config_folder = P.DEV_PROJECT_FOLDER / 'config'
else:
C.read_config(C.software_config_file)
if (pyuic6 := shutil.which('pyuic6')) is None:
print('pyuic6 is not installed')
return
if generate_pyqt:
subprocess.run([pyuic6, '-o', P.PYQT / 'Main.py', P.PYQT / 'Main.ui'])
subprocess.run([pyuic6, '-o', P.PYQT / 'Image.py', P.PYQT / 'Image.ui'])
if data_folder is not None:
C.data_folder = Path(data_folder)
path = Path(path)
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('[MainUI]\n')
print('Use launch config', path)
launch_from_file(path)
if __name__ == '__main__':
launch_from_file(CONFIG_FOLDER / 'gui.toml')
entrypoint()

View File

@ -8,7 +8,7 @@ import zmq
from flandre.beamformer.das import gen_pwi, TFM
from flandre.beamformer.dist import direct_dist
from flandre.beamformer.kernels import dist_mat_to_yids
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.utils.Config import DeviceConfig
from flandre.utils.Msg import ImageArgMsg, Msg, BeamformerMsg, RfMatMsg, RfFrameMsg, MaxMsg

View File

@ -7,7 +7,7 @@ from threading import Thread
import zmq
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.utils.Msg import ImageArgMsg, KillMsg, SetDeviceConnectedMsg, SetDeviceEnabledMsg, DeviceEnabledMsg, \
DeviceConnectedMsg, SetDeviceConfigMsg, DeviceOnlineMsg, DeviceConfigListMsg, RequestRfFrameMsg, \

View File

@ -4,7 +4,7 @@ import cv2
import numpy as np
import zmq
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.utils.Msg import BMMsg, SetWindowVisibleMsg, RfMatMsg
from flandre.utils.RfMat import RfMat

View File

@ -6,7 +6,7 @@ import cv2
import numpy as np
from flandre.BusClient import BusClient
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.utils.Msg import Msg, RfMatMsg
from flandre.utils.RfMat import RfMat

View File

@ -7,7 +7,7 @@ from PyQt6.QtCore import QByteArray, Qt
from PyQt6.QtGui import QImage, QPixmap, QKeyEvent, QWheelEvent
from PyQt6.QtWidgets import QMainWindow, QApplication, QGraphicsPixmapItem, QGraphicsScene
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.pyqt.Image import Ui_MainWindow
from flandre.pyqt.ZMQReceiver import ZMQReceiver

View File

@ -3,7 +3,7 @@ from pathlib import Path
import zmq
from flandre.config import C, ISDEV
from flandre import C
from flandre.nodes.Node import Node
from flandre.utils.Msg import MoveAxisMsg, KillMsg, SetSeqMetaMsg, SeqIdMinMax, SetBaseMsg, PlaybackSeqListMsg, \
SeqIdList, SetSidMsg, RfFrameMsg, RobotRtsiMsg
@ -49,7 +49,6 @@ class Loader(Node):
logger.warning(f'No sequences found in {base}')
else:
C.data_folder = base
if not ISDEV:
C.write_config()
self.send(PlaybackSeqListMsg(seq_list))
elif isinstance(msg, KillMsg):

View File

@ -11,8 +11,8 @@ from PyQt6 import QtWidgets, QtGui
from PyQt6.QtCore import QByteArray, pyqtSlot
from PyQt6.QtWidgets import QMainWindow, QApplication, QFrame, QMessageBox, QFileDialog, QLineEdit
from flandre import ASSETS
from flandre.config import DS, C
from flandre import P
from flandre import C
from flandre.nodes.Node import Node
from flandre.pyqt.Main import Ui_MainWindow
from flandre.pyqt.ZMQReceiver import ZMQReceiver
@ -73,12 +73,12 @@ class Adv(QMainWindow, Ui_MainWindow):
self.device_switch_state: LinkStatus = LinkStatus.RED
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(ASSETS / 'switch_button.png')))
icon.addPixmap(QtGui.QPixmap(str(P.ASSETS / 'switch_button.png')))
self.b_probe_head_switch.setIcon(icon)
self.b_us_switch.setIcon(icon)
self.b_cobot_switch.setIcon(icon)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(ASSETS / 'refresh_button.png')))
icon.addPixmap(QtGui.QPixmap(str(P.ASSETS / 'refresh_button.png')))
self.b_us_refresh.setIcon(icon)
zmq_receiver = ZMQReceiver(self)
zmq_receiver.zmq_event.connect(self.on_zmq_event)
@ -335,11 +335,9 @@ class Adv(QMainWindow, Ui_MainWindow):
self.s_t_start.setMaximum(m)
self.s_t_end.setMaximum(m)
self.sp_crop_center.setMaximum(m)
self.sp_crop_width.setMaximum(m)
def update_max(self, m):
self.s_f_rows.setMaximum(m)
self.sp_f_rows.setMaximum(m)
@ -349,6 +347,7 @@ class Adv(QMainWindow, Ui_MainWindow):
self.sp_dct_center.setMaximum(m)
self.sp_dct_bandwidth.setMaximum(m)
def on_zmq_event(self, msg: QByteArray):
try:
msg = Msg.decode_msg(msg.data())

View File

@ -4,7 +4,7 @@ from threading import Thread
import zmq
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.utils.Msg import KillMsg, SetDeviceSwitchMsg, DeviceSwitchMsg, RefreshDeviceMsg, InterruptMsg
from flandre.utils.mi import c1_connect, c1_connected, c1_disconnect

View File

@ -3,7 +3,7 @@ import logging
import zmq
from flandre.BusClient import BusClient
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
logger = logging.getLogger(__name__)

View File

@ -6,7 +6,7 @@ from threading import Thread
import zmq
from flandre.config import C
from flandre import C
from flandre.nodes.Device import Device, DeviceCmd
from flandre.nodes.Node import Node
from flandre.utils.Msg import ImageArgMsg, KillMsg, SetSeqMetaMsg, SetPlayMode, SetDeviceConfigMsg, \

View File

@ -4,6 +4,7 @@ from abc import abstractmethod
import zmq
from flandre.BusClient import BusClient
import flandre
from flandre.utils.Msg import Msg, KillMsg, NodeOnlineMsg, Msg1, Msg2
@ -62,6 +63,8 @@ class Node:
poller=True, conflare=self.conflare, req_socket_str=self.req)
def __call__(self, *args, **kwargs):
assert 'software_config' in kwargs, self.__class__
flandre.C.copy_form(kwargs['software_config'])
self.setup()
if not self.broker:
while True:

View File

@ -6,7 +6,7 @@ from pathlib import Path
import numpy as np
import zmq
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.utils.Msg import ImageArgMsg, KillMsg, SetSeqMetaMsg, SetPlayMode, SetDeviceConfigMsg, SetRecordMsg, \
RequestRfFrameMsg, RecordFrameMsg, RobotRtsiMsg, SeqMetaMsg

View File

@ -7,7 +7,7 @@ from PyQt6.QtCore import QByteArray, Qt
from PyQt6.QtGui import QImage, QPixmap, QKeyEvent, QWheelEvent
from PyQt6.QtWidgets import QMainWindow, QApplication, QGraphicsPixmapItem, QGraphicsScene
from flandre.config import C
from flandre import C
from flandre.nodes.Node import Node
from flandre.pyqt.FFmpegReceiver import FFmpegReceiver
from flandre.pyqt.Image import Ui_MainWindow

View File

View File

@ -1,8 +1,6 @@
import dataclasses
import json
from flandre.config import CONFIG_FOLDER
@dataclasses.dataclass
class ImagingConfig:
@ -31,13 +29,13 @@ class ImagingConfig:
changed_field_orig_value: str | int = None
@staticmethod
def from_file(input_format, focus_mode, folder=CONFIG_FOLDER):
def from_file(input_format, focus_mode, folder=None):
try:
return ImagingConfig(**json.load((folder / f'icfg_{input_format}_{focus_mode}.json').open()))
except Exception as e:
return ImagingConfig()
def save(self, input_format: str, focus_mode: str, folder=CONFIG_FOLDER):
def save(self, input_format: str, focus_mode: str, folder=None):
json.dump(self.__dict__, (folder / f'icfg_{input_format}_{focus_mode}.json').open('w'))

View File

@ -1,7 +1,7 @@
import click
from miio.miioprotocol import MiIOProtocol
from flandre.config import C
from flandre import C
def c1():

View File

@ -32,4 +32,4 @@ packages = { find = { where = ["."], include = ["flandre", "flandre.*"] } }
package-data = { "flandre.assets" = ["*.svg", "*.png"], "flandre.pyqt" = ["*.ui"] }
[project.scripts]
flandre = "flandre:entrypoint"
flandre = "flandre.launcher:entrypoint"

View File

@ -1,6 +1,6 @@
import zmq
from flandre.config import C
from flandre import C
from flandre.utils.RfFrame import b2t
if __name__ == '__main__':

View File

@ -5,7 +5,7 @@ import cv2
import numpy as np
import zmq
from flandre.config import C
from flandre import C
from flandre.nodes.Device import Device, DeviceCmd
from flandre.nodes.Mi import Mi

View File

@ -5,7 +5,7 @@ import cv2
import numpy as np
import zmq
from flandre.config import C
from flandre import C
from flandre.nodes.Device import Device, DeviceCmd
from flandre.nodes.Mi import Mi

View File

@ -0,0 +1,27 @@
import dataclasses
import multiprocessing
@dataclasses.dataclass
class Config:
arg: int = 1
global_config = Config(arg=2)
def process1():
print('Process1 arg=', global_config.arg)
def main():
global_config.arg = 3
multiprocessing.set_start_method("spawn")
p1 = multiprocessing.Process(target=process1)
p1.start()
p1.join()
print('main arg=', global_config.arg)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,5 @@
import platformdirs
if __name__ == '__main__':
print(platformdirs.user_data_dir('Flandre', 'Scarlet'))
print(platformdirs.user_data_dir('Flandre'))