Exemple #1
0
    def __init__(self, isServer: bool = True):
        self._client = FBServer() if isServer else FBClient()
        self._client.registerRecvCallback(self._send)

        self._serThread: ReaderThread = None

        self._connected = threading.Event()
        self._running = True

        self._conCB: List[callable] = []
        self._disconCB: List[callable] = []
Exemple #2
0
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBPos")
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)
        self._root.rowconfigure(0, weight=1)
        self._root.columnconfigure(0, weight=1)

        self._recv = FBFloatMultiRecv()
        self._client = FBServer() if isServer else FBClient()
        self._client.registerRecvCallback(self._recv.input)

        self._plotFrame = ttk.Frame(self._root)
        self._plotFrame.grid(row=0, column=0, sticky="wesn")
        self._plotFrame.rowconfigure(0, weight=1)
        self._plotFrame.columnconfigure(0, weight=1)

        self._fig = plt.Figure()
        self._fig.subplots_adjust(top=0.965,
                                  bottom=0.055,
                                  left=0.115,
                                  right=0.97,
                                  wspace=0,
                                  hspace=0)
        self._canvas = FigureCanvasTkAgg(self._fig, master=self._plotFrame)
        self._canvas.get_tk_widget().grid(row=0, column=0, sticky="wesn")
        self._ax = self._fig.add_subplot(1, 1, 1)
        self._ax.set_aspect("equal", adjustable="datalim")

        self._toolbar = NavigationToolbar2Tk(self._canvas,
                                             self._plotFrame,
                                             pack_toolbar=False)
        self._toolbar.update()
        self._hiddenPanButton = self._toolbar._buttons["Pan"]
        self._prePanState = False

        self._canvas.mpl_connect("button_press_event", self._control_on_press)
        self._canvas.mpl_connect("button_release_event",
                                 self._control_on_release)
        self._canvas.mpl_connect("motion_notify_event", self._control_on_move)
        self._control_event: SimpleNamespace = None
        self._control_event_lock = threading.Lock()
        self._control_arrow = FancyArrow(0, 0, 1, 0, width=0.1)
        self._ax.add_patch(self._control_arrow)
        self._control_arrow.set_visible(False)

        self._opFrame = ttk.Frame(self._root)
        self._opFrame.grid(row=1, column=0, sticky="we")

        self._resetLimButton = ttk.Button(self._opFrame,
                                          text="视野重置",
                                          command=self._resetLim)
        self._resetLimButton.pack(side="left", padx=5, pady=5)

        self._panButton = ttk.Checkbutton(
            self._opFrame,
            text="拖拽",
            bootstyle=("warning", "outline", "toolbutton"),
            command=self._onPanClick,
        )
        self._panButton.pack(side="left", padx=5, pady=5)

        self._controlButton = ttk.Checkbutton(
            self._opFrame,
            text="控制",
            bootstyle=("error", "outline", "toolbutton"),
            command=self._onControlClick,
        )
        self._controlButton.pack(side="left", padx=5, pady=5)

        self._pauseButton = ttk.Checkbutton(self._opFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))
        self._pauseButton.pack(side="left", padx=5, pady=5)

        self._resetPosButton = ttk.Button(self._opFrame,
                                          text="重置位置",
                                          command=self._resetPos)
        self._resetPosButton.pack(side="left", padx=5, pady=5)

        self._stopButton = ttk.Button(self._opFrame,
                                      text="停止",
                                      command=self._stop)
        self._stopButton.pack(side="left", padx=5, pady=5)

        self._robots: List[Robot] = []
        self._scatters: List[Scatter] = []
Exemple #3
0
class FBPosApp:
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBPos")
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)
        self._root.rowconfigure(0, weight=1)
        self._root.columnconfigure(0, weight=1)

        self._recv = FBFloatMultiRecv()
        self._client = FBServer() if isServer else FBClient()
        self._client.registerRecvCallback(self._recv.input)

        self._plotFrame = ttk.Frame(self._root)
        self._plotFrame.grid(row=0, column=0, sticky="wesn")
        self._plotFrame.rowconfigure(0, weight=1)
        self._plotFrame.columnconfigure(0, weight=1)

        self._fig = plt.Figure()
        self._fig.subplots_adjust(top=0.965,
                                  bottom=0.055,
                                  left=0.115,
                                  right=0.97,
                                  wspace=0,
                                  hspace=0)
        self._canvas = FigureCanvasTkAgg(self._fig, master=self._plotFrame)
        self._canvas.get_tk_widget().grid(row=0, column=0, sticky="wesn")
        self._ax = self._fig.add_subplot(1, 1, 1)
        self._ax.set_aspect("equal", adjustable="datalim")

        self._toolbar = NavigationToolbar2Tk(self._canvas,
                                             self._plotFrame,
                                             pack_toolbar=False)
        self._toolbar.update()
        self._hiddenPanButton = self._toolbar._buttons["Pan"]
        self._prePanState = False

        self._canvas.mpl_connect("button_press_event", self._control_on_press)
        self._canvas.mpl_connect("button_release_event",
                                 self._control_on_release)
        self._canvas.mpl_connect("motion_notify_event", self._control_on_move)
        self._control_event: SimpleNamespace = None
        self._control_event_lock = threading.Lock()
        self._control_arrow = FancyArrow(0, 0, 1, 0, width=0.1)
        self._ax.add_patch(self._control_arrow)
        self._control_arrow.set_visible(False)

        self._opFrame = ttk.Frame(self._root)
        self._opFrame.grid(row=1, column=0, sticky="we")

        self._resetLimButton = ttk.Button(self._opFrame,
                                          text="视野重置",
                                          command=self._resetLim)
        self._resetLimButton.pack(side="left", padx=5, pady=5)

        self._panButton = ttk.Checkbutton(
            self._opFrame,
            text="拖拽",
            bootstyle=("warning", "outline", "toolbutton"),
            command=self._onPanClick,
        )
        self._panButton.pack(side="left", padx=5, pady=5)

        self._controlButton = ttk.Checkbutton(
            self._opFrame,
            text="控制",
            bootstyle=("error", "outline", "toolbutton"),
            command=self._onControlClick,
        )
        self._controlButton.pack(side="left", padx=5, pady=5)

        self._pauseButton = ttk.Checkbutton(self._opFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))
        self._pauseButton.pack(side="left", padx=5, pady=5)

        self._resetPosButton = ttk.Button(self._opFrame,
                                          text="重置位置",
                                          command=self._resetPos)
        self._resetPosButton.pack(side="left", padx=5, pady=5)

        self._stopButton = ttk.Button(self._opFrame,
                                      text="停止",
                                      command=self._stop)
        self._stopButton.pack(side="left", padx=5, pady=5)

        self._robots: List[Robot] = []
        self._scatters: List[Scatter] = []

    def _resetPos(self):
        data = HEADER + STATE_ID + b"".join(map(as_float, (0, 0, 0, 0, 0, 0)))
        self._client.send(data)

    def _stop(self):
        data = HEADER + STOP_ID
        self._client.send(data)

    def _get_control_data(self):
        return (
            self._control_event.x,
            self._control_event.y,
            self._control_event.dx,
            self._control_event.dy,
        )

    def _control_on_press(self, event):
        if event.xdata is None:
            return
        if self._controlButton.instate(["selected"]):
            with self._control_event_lock:
                self._control_event = SimpleNamespace(x=event.xdata,
                                                      y=event.ydata,
                                                      dx=0,
                                                      dy=0)

    def _control_on_move(self, event):
        if event.xdata is None:
            return
        with self._control_event_lock:
            if self._control_event is not None:
                self._control_event.dx, self._control_event.dy = (
                    event.xdata - self._control_event.x,
                    event.ydata - self._control_event.y,
                )

    def _control_on_release(self, event):
        with self._control_event_lock:
            if self._control_event is None or self._control_event.dx == self._control_event.dy == 0:
                self._control_event = None
                return
            x, y, dx, dy = self._get_control_data()
            self._control_event = None
        yaw = atan2(dy, dx)
        print("x:%.2f y:%.2f yaw:%.2f" % (x, y, yaw))
        if event.button == 1:
            data = HEADER + CONTROL_ID + b"".join(map(as_float, (x, y, yaw)))
        else:
            data = HEADER + STATE_ID + b"".join(
                map(as_float, (x, y, yaw, 0, 0, 0)))
        self._client.send(data)

    def _getLimScale(self):
        xlim, ylim = self._ax.get_xlim(), self._ax.get_ylim()
        return min(xlim[1] - xlim[0], ylim[1] - ylim[0])

    def _updateControlArrow(self):
        with self._control_event_lock:
            if self._control_event is None or self._control_event.dx == self._control_event.dy == 0:
                self._control_arrow.set_visible(False)
                return [self._control_arrow]
            else:
                self._control_arrow.set_visible(True)
                x, y, dx, dy = self._get_control_data()
        k = self._getLimScale() * 0.2 / sqrt(dx * dx + dy * dy)
        self._control_arrow.set_data(x=x, y=y, dx=dx * k, dy=dy * k)
        return [self._control_arrow]

    def registerScatter(self, id: int, *args, **kwargs) -> Scatter:
        scatter = Scatter(self._ax, *args, **kwargs)
        self._recv.setConfig(id, 0, 4, True)
        self._recv.registerRecvCallback(
            id, lambda data: scatter.set_data(np.reshape(data, (-1, 2))))
        self._scatters.append(scatter)
        return scatter

    def _updateScatters(self):
        if not self._pauseButton.instate(["selected"]):
            for scatter in self._scatters:
                scatter.update_data()
        return [scatter.get_artist() for scatter in self._scatters]

    def registerRobot(self,
                      id: Optional[int] = None,
                      *args,
                      **kwargs) -> Robot:
        robot = Robot(*args, **kwargs)
        if id is not None:
            self._recv.setConfig(id, 3, 4, True)
            self._recv.registerRecvCallback(id,
                                            lambda data: robot.set_pose(*data))
        self._robots.append(robot)
        return robot

    def _updateRobots(self):
        if not self._pauseButton.instate(["selected"]):
            for robot in self._robots:
                robot.update_pose()
        return self._robots

    def _updatePlot(self, *_):
        changed = []
        changed.extend(self._updateControlArrow())
        changed.extend(self._updateRobots())
        changed.extend(self._updateScatters())
        return changed

    def _onPanClick(self, *_):
        cur = self._panButton.instate(["selected"])
        if cur != self._prePanState:
            self._toolbar.pan()
            self._prePanState = cur
        if cur:
            self._controlButton.state(["!selected"])

    def _onControlClick(self, *_):
        if self._controlButton.instate(["selected"]):
            self._panButton.state(["!selected"])
            self._onPanClick()

    def _initPlot(self):
        self._ax.cla()
        self._ax.grid(True)
        for robot in self._robots:
            self._ax.add_patch(robot)
        self._resetLim()

    def _resetLim(self, *_):
        self._ax.set_ylim((-1, 8))
        self._ax.set_xlim((-1, 8))

    def mainloop(self):
        self._client.start()
        self._recv.start()

        self._initPlot()
        self._animation = animation.FuncAnimation(self._fig,
                                                  self._updatePlot,
                                                  interval=20,
                                                  blit=True)
        self._root.mainloop()

    def shutdown(self):
        self._recv.shutdown()
        self._client.shutdown()

        self._root.destroy()
Exemple #4
0
            if data is None:
                continue
            for cb in self._recvCBs:
                if not self._running:
                    break
                cb(data)


__all__ = ["FBFloatRecv"]

if __name__ == "__main__":
    import sys, os.path

    sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
    from FBSocket import FBServer

    server = FBServer()
    recv = FBFloatRecv()
    server.registerRecvCallback(recv.input)
    recv.registerRecvCallback(print)

    recv.start()
    server.start()

    input()
    recv.setConfig(cnt=2)
    input()

    recv.shutdown()
    server.shutdown()
Exemple #5
0
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBImg")
        self._root.rowconfigure(0, weight=1)
        self._root.columnconfigure(0, weight=1)
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)

        self._imgCanvas = ttk.Canvas(self._root)
        self._imgCanvas.config(bg="light gray")
        self._imgCanvas.grid(row=0, column=0, sticky="nsew")
        self._imgCanvas_shape = (0, 0)

        opFrame = ttk.Frame(self._root)
        opFrame.grid(row=1, column=0, sticky="we")
        opFrame.columnconfigure(0, weight=1)

        val = lambda s: ValEntry.type_validator(int)(s) and int(s) > 0
        imgFrame = ttk.Frame(opFrame)
        imgFrame.grid(row=0, column=0, sticky="we")
        ttk.Label(imgFrame, text="宽度").pack(side="left", pady=5)
        self.wEntry = ValEntry(val, imgFrame, width=5)
        self.wEntry.pack(side="left", pady=5)
        ttk.Label(imgFrame, text="高度").pack(side="left", pady=5)
        self.hEntry = ValEntry(val, imgFrame, width=5)
        self.hEntry.pack(side="left", pady=5)
        ttk.Button(imgFrame, text="应用",
                   command=self._applySize).pack(side="left", padx=2, pady=5)

        self._pauseButton = ttk.Checkbutton(imgFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))
        self._pauseButton.pack(side="left", padx=5, pady=5)

        ttk.Label(imgFrame, text="保存路径").pack(side="left", pady=5)
        self._dirEntry = ttk.Entry(imgFrame, width=30)
        self._dirEntry.pack(side="left", pady=5)

        ttk.Button(imgFrame, text="选择",
                   command=self._selectDir).pack(side="left", pady=5)

        self._saveButton = ttk.Button(imgFrame,
                                      text="保存",
                                      bootstyle=("success"),
                                      command=self.saveImg)
        self._saveButton.pack(side="left", padx=5, pady=5)
        self._recordButton = ttk.Checkbutton(imgFrame,
                                             text="录制",
                                             command=self._toggleRecord,
                                             bootstyle=("warning", "outline",
                                                        "toolbutton"))
        self._recordButton.pack(side="left", pady=5)
        self._cntLabel = ttk.Label(imgFrame)
        self._cntLabel.pack(side="left", pady=5)

        self._client = FBServer() if isServer else FBClient()
        self._recv = FBRawRecv()

        self._client.registerRecvCallback(self._recv.input)
        self._recv.registerRecvCallback(self.updateData)

        self._imgLock = threading.Lock()
        self._dirLock = threading.Lock()
        self.loadConfig()
Exemple #6
0
class FBImgApp:
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBImg")
        self._root.rowconfigure(0, weight=1)
        self._root.columnconfigure(0, weight=1)
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)

        self._imgCanvas = ttk.Canvas(self._root)
        self._imgCanvas.config(bg="light gray")
        self._imgCanvas.grid(row=0, column=0, sticky="nsew")
        self._imgCanvas_shape = (0, 0)

        opFrame = ttk.Frame(self._root)
        opFrame.grid(row=1, column=0, sticky="we")
        opFrame.columnconfigure(0, weight=1)

        val = lambda s: ValEntry.type_validator(int)(s) and int(s) > 0
        imgFrame = ttk.Frame(opFrame)
        imgFrame.grid(row=0, column=0, sticky="we")
        ttk.Label(imgFrame, text="宽度").pack(side="left", pady=5)
        self.wEntry = ValEntry(val, imgFrame, width=5)
        self.wEntry.pack(side="left", pady=5)
        ttk.Label(imgFrame, text="高度").pack(side="left", pady=5)
        self.hEntry = ValEntry(val, imgFrame, width=5)
        self.hEntry.pack(side="left", pady=5)
        ttk.Button(imgFrame, text="应用",
                   command=self._applySize).pack(side="left", padx=2, pady=5)

        self._pauseButton = ttk.Checkbutton(imgFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))
        self._pauseButton.pack(side="left", padx=5, pady=5)

        ttk.Label(imgFrame, text="保存路径").pack(side="left", pady=5)
        self._dirEntry = ttk.Entry(imgFrame, width=30)
        self._dirEntry.pack(side="left", pady=5)

        ttk.Button(imgFrame, text="选择",
                   command=self._selectDir).pack(side="left", pady=5)

        self._saveButton = ttk.Button(imgFrame,
                                      text="保存",
                                      bootstyle=("success"),
                                      command=self.saveImg)
        self._saveButton.pack(side="left", padx=5, pady=5)
        self._recordButton = ttk.Checkbutton(imgFrame,
                                             text="录制",
                                             command=self._toggleRecord,
                                             bootstyle=("warning", "outline",
                                                        "toolbutton"))
        self._recordButton.pack(side="left", pady=5)
        self._cntLabel = ttk.Label(imgFrame)
        self._cntLabel.pack(side="left", pady=5)

        self._client = FBServer() if isServer else FBClient()
        self._recv = FBRawRecv()

        self._client.registerRecvCallback(self._recv.input)
        self._recv.registerRecvCallback(self.updateData)

        self._imgLock = threading.Lock()
        self._dirLock = threading.Lock()
        self.loadConfig()

    def _toggleRecord(self, *_):
        if self._recordButton.instate(["selected"]):
            self._saveButton.config(state="disabled")
            self._recordButton.config(text="停止")
        else:
            self._saveButton.config(state="!disabled")
            self._recordButton.config(text="录制")

    def _updateDir(self):
        os.makedirs(self.save_dir, exist_ok=True)
        self._dirEntry.config(state="normal")
        self._dirEntry.delete(0, tk.END)
        self._dirEntry.insert(0, self.save_dir)
        self._dirEntry.config(state="readonly")

    def _selectDir(self, *_):
        with self._dirLock:
            cur = self.save_dir
        s = filedialog.askdirectory(master=self._root,
                                    initialdir=cur,
                                    title="选择保存路径",
                                    mustexist=False)
        if not s:
            return
        with self._dirLock:
            self.save_dir = s
        self._updateDir()

    def saveImg(self, *_):
        def save():
            with self._imgLock:
                img = self.img
            with self._dirLock:
                Dir = self.save_dir
            name = datetime.datetime.now().strftime(
                "%Y-%m-%d-%H-%M-%S-%f") + SAVE_EXT
            cv.imwrite(os.path.join(Dir, name), img)

        threading.Thread(target=save).start()

    def updateData(self, data: bytes) -> None:
        if self._pauseButton.instate(["selected"]):
            return
        with self._imgLock:
            w, h = self.w, self.h
        img = np.frombuffer(data, dtype=np.uint8).reshape((h, w))
        with self._imgLock:
            if w != self.w or h != self.h:
                return
            self.img = img
            self._updateFlag = True
        if self._recordButton.instate(["selected"]):
            self.saveImg()

    def _applySize(self):
        with self._imgLock:
            self.w, self.h = int(self.wEntry.get()), int(self.hEntry.get())
            self.img = np.zeros((self.h, self.w), dtype=np.uint8)
            self._updateFlag = True
        self._recv.setConfig(cnt=self.w * self.h)

    def loadConfig(self):
        os.makedirs(CFG_DIR, exist_ok=True)
        cfg = {}
        if os.path.exists(CFG_PATH):
            with open(CFG_PATH, "r") as f:
                cfg = json.load(f)
        self._root.geometry(cfg.get("geometry", "876x600+30+30"))
        self.w = cfg.get("w", 752)
        self.h = cfg.get("h", 480)
        self.wEntry.set(str(self.w))
        self.hEntry.set(str(self.h))
        self.save_dir = cfg.get("save_dir", SAVE_DIR)
        self._updateDir()
        self._applySize()

    def saveConfig(self):
        cfg = {
            "geometry": self._root.geometry(),
            "w": self.w,
            "h": self.h,
            "save_dir": self.save_dir
        }
        with open(CFG_PATH, "w") as f:
            json.dump(cfg, f)

    def create_image(self):
        canvas_w, canvas_h = self._imgCanvas.winfo_width(
        ), self._imgCanvas.winfo_height()
        with self._imgLock:
            if (canvas_w, canvas_h
                ) == self._imgCanvas_shape and not self._updateFlag:
                return
            img = self.img
            self._updateFlag = False
        self._imgCanvas_shape = (canvas_w, canvas_h)

        img_w, img_h = img.shape[1], img.shape[0]
        canvas_k, img_k = canvas_w / canvas_h, img_w / img_h
        w, h = (canvas_w,
                round(canvas_w /
                      img_k)) if canvas_k < img_k else (round(canvas_h *
                                                              img_k), canvas_h)
        resized_img = cv.resize(img, (max(1, w), max(1, h)),
                                interpolation=cv.INTER_NEAREST)
        cvt_img = resized_img
        self._imgTK = ImageTk.PhotoImage(master=self._imgCanvas,
                                         image=Image.fromarray(cvt_img))
        self._imgCanvas.create_image(canvas_w >> 1,
                                     canvas_h >> 1,
                                     image=self._imgTK)

    def _create_image_task(self):
        if self._running:
            self.create_image()
        if self._running:
            self._imgCanvas.after(50, self._create_image_task)

    def mainloop(self):
        self._running = True
        self._updateFlag = True
        self._create_image_task()
        self._client.start()
        self._recv.start()
        self._root.mainloop()

    def shutdown(self):
        self._running = False
        self._recv.shutdown()
        self._client.shutdown()
        self.saveConfig()
        self._root.destroy()
Exemple #7
0
class FBSerial:
    def __init__(self, isServer: bool = True):
        self._client = FBServer() if isServer else FBClient()
        self._client.registerRecvCallback(self._send)

        self._serThread: ReaderThread = None

        self._connected = threading.Event()
        self._running = True

        self._conCB: List[callable] = []
        self._disconCB: List[callable] = []

    @staticmethod
    def _protocalFactory(master: "FBSerial"):
        class PrintLines(Protocol):
            def connection_made(self, transport):
                for cb in master._conCB:
                    if not master._running:
                        break
                    cb()
                self.port = transport.serial.port
                print(f"[FBSerial]连接建立: {self.port}")
                master._connected.set()

            def data_received(self, data):
                master._client.send(data)

            def connection_lost(self, exc):
                master._connected.clear()
                master._serThread = None
                for cb in master._disconCB:
                    if not master._running:
                        break
                    cb()
                print(f"[FBSerial]连接断开: {self.port}")

        return PrintLines

    def _send(self, data: bytes) -> None:
        if self._running:
            self._connected.wait()
        if not self._running:
            return
        try:
            self._serThread.write(data)
        except:
            pass

    def start(self):
        self._running = True
        self._client.start()

    def shutdown(self) -> None:
        self._running = False
        self.close()
        self._client.shutdown()

    def connect(self, port, baudrate=115200):
        self.close()
        ser = serial.Serial(port=port, baudrate=baudrate)
        self._serThread = ReaderThread(ser, self._protocalFactory(self))
        self._serThread.start()

    def close(self) -> None:
        if self._serThread is not None:
            if self._running:
                threading.Thread(target=self._serThread.close).start()
            else:
                self._serThread.close()

    def registerConnectCallback(self, func: callable) -> None:
        self._conCB.append(func)

    def registerDisconnectCallback(self, func: callable) -> None:
        self._disconCB.append(func)
Exemple #8
0
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBRecorder")
        self._root.resizable(False, False)
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)

        self._recv = FBFloatMultiRecv()
        self._recv.registerRecvAllCallback(self.updateData)
        self._client = FBServer() if isServer else FBClient()
        self._client.registerRecvCallback(self._recv.input)

        self._csvWriter: FBCSVWriter = None
        self._cvsWriterLock = threading.Lock()

        self.cnt = 0
        self._cntLock = threading.Lock()
        self._cntVar = tk.StringVar(value="0")

        cfgFrame = ttk.Frame(self._root)
        cfgFrame.pack(padx=5, pady=5)
        self.cfg_path = tk.StringVar()
        self._cfgPathEntry = ttk.Entry(cfgFrame,
                                       textvariable=self.cfg_path,
                                       state="readonly",
                                       width=30)
        self._cfgPathButton = ttk.Button(cfgFrame,
                                         text="选择",
                                         command=self._selectCfgPath)

        ttk.Label(cfgFrame, text="配置文件").pack(side="left")
        self._cfgPathEntry.pack(side="left")
        self._cfgPathButton.pack(side="left")

        saveFrame = ttk.Frame(self._root)
        saveFrame.pack(padx=5, pady=5)
        self.save_path = tk.StringVar()
        self._savePathEntry = ttk.Entry(saveFrame,
                                        textvariable=self.save_path,
                                        state="readonly",
                                        width=30)
        self._savePathButton = ttk.Button(saveFrame,
                                          text="选择",
                                          command=self._selectSavePath)

        ttk.Label(saveFrame, text="保存文件").pack(side="left")
        self._savePathEntry.pack(side="left")
        self._savePathButton.pack(side="left")

        opFrame = ttk.Frame(self._root)
        opFrame.pack(padx=5, pady=5, fill="x", expand=True)
        self._appendsButton = ttk.Checkbutton(
            opFrame,
            text="追加",
            bootstyle=("info", "outline", "toolbutton"),
        )
        self._recordButton = ttk.Checkbutton(opFrame,
                                             text="录制",
                                             command=self._toggleRecord,
                                             bootstyle=("warning", "outline",
                                                        "toolbutton"))
        self._pauseButton = ttk.Checkbutton(opFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))

        self._appendsButton.pack(side="left")
        self._recordButton.pack(side="left", padx=5)
        self._pauseButton.pack(side="left")
        ttk.Label(opFrame, text="已接收:").pack(side="left")
        ttk.Label(opFrame, textvariable=self._cntVar).pack(side="left")
Exemple #9
0
class FBRecorderApp:
    def __init__(self, isServer: bool = False):
        style = ttk.Style("cosmo")
        self._root = style.master
        self._root.title("FBRecorder")
        self._root.resizable(False, False)
        self._root.protocol("WM_DELETE_WINDOW", self.shutdown)

        self._recv = FBFloatMultiRecv()
        self._recv.registerRecvAllCallback(self.updateData)
        self._client = FBServer() if isServer else FBClient()
        self._client.registerRecvCallback(self._recv.input)

        self._csvWriter: FBCSVWriter = None
        self._cvsWriterLock = threading.Lock()

        self.cnt = 0
        self._cntLock = threading.Lock()
        self._cntVar = tk.StringVar(value="0")

        cfgFrame = ttk.Frame(self._root)
        cfgFrame.pack(padx=5, pady=5)
        self.cfg_path = tk.StringVar()
        self._cfgPathEntry = ttk.Entry(cfgFrame,
                                       textvariable=self.cfg_path,
                                       state="readonly",
                                       width=30)
        self._cfgPathButton = ttk.Button(cfgFrame,
                                         text="选择",
                                         command=self._selectCfgPath)

        ttk.Label(cfgFrame, text="配置文件").pack(side="left")
        self._cfgPathEntry.pack(side="left")
        self._cfgPathButton.pack(side="left")

        saveFrame = ttk.Frame(self._root)
        saveFrame.pack(padx=5, pady=5)
        self.save_path = tk.StringVar()
        self._savePathEntry = ttk.Entry(saveFrame,
                                        textvariable=self.save_path,
                                        state="readonly",
                                        width=30)
        self._savePathButton = ttk.Button(saveFrame,
                                          text="选择",
                                          command=self._selectSavePath)

        ttk.Label(saveFrame, text="保存文件").pack(side="left")
        self._savePathEntry.pack(side="left")
        self._savePathButton.pack(side="left")

        opFrame = ttk.Frame(self._root)
        opFrame.pack(padx=5, pady=5, fill="x", expand=True)
        self._appendsButton = ttk.Checkbutton(
            opFrame,
            text="追加",
            bootstyle=("info", "outline", "toolbutton"),
        )
        self._recordButton = ttk.Checkbutton(opFrame,
                                             text="录制",
                                             command=self._toggleRecord,
                                             bootstyle=("warning", "outline",
                                                        "toolbutton"))
        self._pauseButton = ttk.Checkbutton(opFrame,
                                            text="暂停",
                                            bootstyle=("success", "outline",
                                                       "toolbutton"))

        self._appendsButton.pack(side="left")
        self._recordButton.pack(side="left", padx=5)
        self._pauseButton.pack(side="left")
        ttk.Label(opFrame, text="已接收:").pack(side="left")
        ttk.Label(opFrame, textvariable=self._cntVar).pack(side="left")

    def loadConfig(self):
        os.makedirs(CFG_DIR, exist_ok=True)
        cfg = {}
        if os.path.exists(CFG_PATH):
            with open(CFG_PATH, "r") as f:
                cfg = json.load(f)
        self.cfg_path.set(cfg.get("cfg_path", ""))
        self.save_path.set(cfg.get("save_path", SAVE_PATH))
        if cfg.get("appends", False):
            self._appendsButton.invoke()

    def saveConfig(self):
        cfg = {
            "cfg_path": self.cfg_path.get(),
            "save_path": self.save_path.get(),
            "appends": self._appendsButton.instate(["selected"]),
        }
        with open(CFG_PATH, "w") as f:
            json.dump(cfg, f)

    def _increaseCnt(self):
        with self._cntLock:
            self.cnt += 1

    def _updateCnt(self):
        with self._cntLock:
            cnt = self.cnt
        self._cntVar.set(str(cnt))
        self._root.after(100, self._updateCnt)

    def mainloop(self):
        self.loadConfig()
        self._client.start()
        self._recv.start()
        self._updateCnt()
        self._root.mainloop()

    def shutdown(self):
        self._recv.shutdown()
        self._client.shutdown()
        self._stopWriter()
        self.saveConfig()
        self._root.destroy()

    def updateData(self, id: int, data: List[float]):
        if self._pauseButton.instate(["selected"]):
            return
        with self._cvsWriterLock:
            writer = self._csvWriter
        if writer is None:
            return
        self._increaseCnt()
        writer.write(map(str, (datetime.timestamp(datetime.now()), id, *data)))

    def _toggleRecord(self, *_):
        if self._recordButton.instate(["selected"]):
            self._recordButton.config(text="停止")
            self._cfgPathButton.config(state="disabled")
            self._savePathButton.config(state="disabled")
            self._appendsButton.config(state="disabled")
            self._startWriter()
        else:
            self._recordButton.config(text="录制")
            self._cfgPathButton.config(state="!disabled")
            self._savePathButton.config(state="!disabled")
            self._appendsButton.config(state="!disabled")
            self._stopWriter()

    def _startWriter(self):
        self._stopWriter()
        self._recv.resetConfig()
        cfg_path = self.cfg_path.get()
        if not isfile(cfg_path):
            messagebox.showerror("错误", "配置文件不存在", parent=self._root)
            self._recordButton.invoke()
            return
        self._recv.loadCfgFromCSV(cfg_path)
        save_path = self.save_path.get()
        with self._cvsWriterLock:
            self._csvWriter = FBCSVWriter(save_path,
                                          appends=self._appendsButton.instate(
                                              ["selected"]))

    def _stopWriter(self):
        with self._cvsWriterLock:
            writer = self._csvWriter
            self._csvWriter = None
        if writer is not None:
            writer.close()

    def _selectCfgPath(self, *_):
        s = filedialog.askopenfilename(
            master=self._root,
            initialfile=self.cfg_path.get(),
            filetypes=[("csv file", "*.csv"), ("all files", "*.*")],
            defaultextension="csv file",
            title="选择配置文件",
        )
        if s and isfile(s):
            self.cfg_path.set(s)

    def _selectSavePath(self, *_):
        s = filedialog.asksaveasfilename(
            master=self._root,
            initialfile=self.save_path.get(),
            filetypes=[("csv file", "*.csv"), ("all files", "*.*")],
            defaultextension="csv file",
            title="选择保存路径",
            confirmoverwrite=False,
        )
        if s:
            os.makedirs(dirname(s), exist_ok=True)
            self.save_path.set(s)