diff --git a/Graphic_display.py b/Graphic_display.py new file mode 100644 index 0000000..7818034 --- /dev/null +++ b/Graphic_display.py @@ -0,0 +1,139 @@ +import matplotlib.pyplot as plt +import time +import numpy as np +from PIL import Image + +class graphic_display(): + def __init__(self): + self.um_per_pixel = 0.5 + self.cm_hot = plt.get_cmap('hot') + self.cm_jet = plt.get_cmap('jet') + self.cm_vir = plt.get_cmap('viridis') + self.cm_mag = plt.get_cmap('magma') + # self.cm_grn = plt.get_cmap('Greens') + self.cm_raw = plt.get_cmap('gray') + self.fps_counter = np.array([time.time(),time.time(),time.time()]) + self.img_rs = None + self.img_norm = None + self.img_gamma = None + self.img_p = None + self.img_cm = None + self.img_sb = None + self.img_fin = None + self.win = None + self.cam = None + return + + def update_win(self, win): + self.win = win + + def update_cam(self, cam): + self.cam = cam + + def update_image(self): + print('update image function') + self.img_rs = np.array(Image.fromarray(self.cam.img_raw).resize(size=(958, 638)),dtype = 'float64')/255 + if self.win.zoom_factor > 1: + r1 = self.img_rs.shape[0] + c1 = self.img_rs.shape[1] + r2 = int(np.round(r1/self.win.zoom_factor)) + c2 = int(np.round(c1/self.win.zoom_factor)) + self.img_rs = self.img_rs[int((r1-r2)/2):int((r1-r2)/2)+r2, int((c1-c2)/2):int((c1-c2)/2)+c2] + + # update and process the image for display from the camera + self.update_image_gamma() + self.normalise_img() + self.update_colormap() + self.display_saturated_pixels_purple() ### error + self.burn_scalebar_into_image() + + # gui functions + self.win.repaint_image() ### may zoom in twice for raw image, need double check + self.win.update_hist() + # self.win.image_histogram.update_histogram() # method in histogram_canvas class + self.win.status_text_update_image() + + # fps counter + self.fps_counter = np.append(self.fps_counter,time.time()) + self.fps_counter = np.delete(self.fps_counter, 0) + self.win.status_fps_number.setText(str(np.round(1/np.mean(np.diff(self.fps_counter)),5))) + print('current saved value for fps is: ' + str(self.cam.fps) + ' current timer value is: ' + str(self.cam.timer_value)) + return + + def update_image_gamma(self): + + if self.win.gamma == 1: + self.img_gamma = self.img_rs + else: + self.img_gamma = self.img_rs**self.win.gamma + return + + def normalise_img(self): + print('normalise function') + if self.win.cbox_normalise.isChecked(): + imgnormmin = np.min(np.nonzero(self.img_gamma)) + imgnormmax = np.max(self.img_gamma) + self.img_norm = (self.img_gamma-imgnormmin)/(imgnormmax--imgnormmin) + self.img_norm = self.img_norm + else: + self.img_norm = self.img_gamma + return + + def update_colormap(self): + print('update colormap function') + # convert from gray to colormap magma selection + if self.win.combobox_colourmap.currentIndex() == 0: + self.img_cm = self.cm_mag(self.img_norm) + # convert from gray to colormap green selection + elif self.win.combobox_colourmap.currentIndex() == 1: + self.img_cm = np.zeros(np.hstack([np.shape(self.img_norm),4])) + self.img_cm[:,:,1] = self.img_norm + self.img_cm[:,:,3] = 255 + ## or use Greens colormap directly + # self.img_cm = self.cm_grn(self.img_norm) + # convert from gray to colormap viridis (3 channel) selection + elif self.win.combobox_colourmap.currentIndex() == 2: + self.img_cm = self.cm_vir(self.img_norm) + # convert from gray to colormap jet selection + elif self.win.combobox_colourmap.currentIndex() == 3: + self.img_cm = self.cm_jet(self.img_norm) + elif self.win.combobox_colourmap.currentIndex() == 4: + # self.img_cm = np.zeros(np.hstack([np.shape(self.img_norm),4])) + # self.img_cm[:,:,0] = self.img_norm + # self.img_cm[:,:,1] = self.img_norm + # self.img_cm[:,:,2] = self.img_norm + # self.img_cm[:,:,3] = 1 + # print(self.img_cm) + # print(self.cam.img_raw) + ## or use gray colormap directly + self.img_cm = self.cm_raw(self.img_norm) + return + + def display_saturated_pixels_purple(self): + print('saturated pxls purple function') + # saturated pixels show up purple if check box is selected + # if self.win.combobox_colourmap.currentIndex() != 4: + self.img_p = self.img_cm + if self.win.cbox_saturated.isChecked(): + ind = self.img_norm > 254 + self.img_p[ind,0] = 255 + self.img_p[ind,1] = 0 + self.img_p[ind,2] = 255 + return + + def burn_scalebar_into_image(self): + print('burn scalebar function') + self.img_sb = self.img_p + if self.win.cbox_show_scalebar.isChecked(): + s = self.img_sb.shape + if self.win.combobox_colourmap.currentIndex() == 1: + self.img_sb[int(s[0]*0.95):int(s[0]*0.955), int(s[1]*0.05):int(s[1]*0.05+100/self.um_per_pixel), 0] = 255 + self.img_sb[int(s[0]*0.95):int(s[0]*0.955), int(s[1]*0.05):int(s[1]*0.05+100/self.um_per_pixel), 1] = 0 + self.img_sb[int(s[0]*0.95):int(s[0]*0.955), int(s[1]*0.05):int(s[1]*0.05+100/self.um_per_pixel), 2] = 255 + else: + self.img_sb[int(s[0]*0.95):int(s[0]*0.955), int(s[1]*0.05):int(s[1]*0.05+100/self.um_per_pixel), 0] = 0 + self.img_sb[int(s[0]*0.95):int(s[0]*0.955), int(s[1]*0.05):int(s[1]*0.05+100/self.um_per_pixel), 1] = 255 + self.img_sb[int(s[0]*0.95):int(s[0]*0.955), int(s[1]*0.05):int(s[1]*0.05+100/self.um_per_pixel), 2] = 0 + self.img_fin = self.img_sb + self.img_fin = np.array(self.img_fin*255,dtype='uint8') + return diff --git a/Histogram.py b/Histogram.py new file mode 100644 index 0000000..6f2f76f --- /dev/null +++ b/Histogram.py @@ -0,0 +1,85 @@ +import numpy as np +from matplotlib.figure import Figure +import matplotlib as mpl +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas + + +class histogram_canvas(FigureCanvas): + + def __init__(self, parent=None): + histo_fig = Figure(dpi=100, constrained_layout=True) + histo_fig.set_facecolor((240/255, 240/255, 240/255)) + self.axes = histo_fig.add_subplot(111) + self.axes.set_facecolor((180/255, 180/255, 180/255)) + super(histogram_canvas, self).__init__(histo_fig) + # define properties for the image histogram + self.number_of_bins = 80 + self.bin_range = int(255/80) + self.x1 = range(0,255,self.bin_range) + self.xtickedges = np.arange(0, 257, 257/self.number_of_bins).tolist() + self.axes.set_xlabel('bit depth') + self.axes.set_ylabel('histo count (log)') + self.xtickbins = np.arange(0, 257, 256/10).tolist() + self.axes.get_xaxis().set_ticks(self.xtickbins) + self.axes.get_xaxis().set_ticklabels(np.hstack([1,['']*(len(self.xtickbins)-2),255])) + self.axes.set(xlim=(-1, 257)) + self.axes.set_yscale('log') + self.axes.get_yaxis().set_ticklabels([]) + self.axes.grid(which='major', color='black', linestyle='-', linewidth=0.1, alpha=0.5) + self.axes.grid(which='minor', color='black', linestyle='-', linewidth=0.05, alpha=0.25) + self.axes.xaxis.labelpad = -10 + self.axes.yaxis.labelpad = -3 + self._plot_ref1 = None + self._plot_ref2 = None + self.win = None + self.graphic_display = None + self.cam = None + return + + def update_win(self, win): + self.win = win + + def update_graph(self, gra): + self.graphic_display = gra + + def update_cam(self, cam): + self.cam = cam + + def update_histogram(self): + print('update histogram function') + # t = time.time() + if self._plot_ref1 is None: + h1 = (self.cam.img_raw.flatten()*(self.number_of_bins/256)) + h1 = np.unique(h1.astype(np.uint8), return_counts=True) + x1 = np.arange(self.number_of_bins) + y1 = np.zeros(self.number_of_bins) + for i in np.unique(h1[0]): + y1[i] = h1[1][np.where(h1[0] == i)] + y2 = np.zeros(self.number_of_bins) + plot_refs1 = self.axes.plot(x1*(256/self.number_of_bins),y1, 'white', linewidth=2, alpha=0.5) + plot_refs2 = self.axes.plot(x1*(256/self.number_of_bins),y2, 'red', linewidth=2, alpha=0.5) + self._plot_ref1 = plot_refs1[0] + self._plot_ref2 = plot_refs2[0] + else: + h1 = (self.cam.img_raw.flatten()*(self.number_of_bins/256)) + h1 = np.unique(h1.astype(np.uint8), return_counts=True) + x1 = np.arange(self.number_of_bins) + y1 = np.zeros(self.number_of_bins) + for i in np.unique(h1[0]): + y1[i] = h1[1][np.where(h1[0] == i)] + self._plot_ref1.set_ydata(y1) + + if self.win.cbox_normalise.isChecked() or self.win.gamma != 1: + h2 = (self.graphic_display.img_norm.flatten()*(self.number_of_bins/256)) + h2 = np.unique(np.asarray(h2, dtype=np.uint8), return_counts=True) + y2 = np.zeros(self.number_of_bins) + for i in np.unique(h2[0]): + y2[i] = h2[1][np.where(h2[0] == i)] + self._plot_ref2.set_ydata(y2) + else: + self._plot_ref2.set_ydata(np.zeros(self.number_of_bins)) + y2 = y1 + self.axes.axis(ymin=1,ymax=2.5*np.amax(np.hstack([y1,y2]),0)) + self.win.image_histogram.draw() + return diff --git a/Print_thread.py b/Print_thread.py new file mode 100644 index 0000000..843a7ae --- /dev/null +++ b/Print_thread.py @@ -0,0 +1,44 @@ +from PyQt5.QtCore import * +import time +import sig as s +import traceback +import sys + +class Worker(QRunnable): + ''' + Worker thread + + :param callback: The function callback to run on this worker thread. Supplied args and + kwargs will be passed through to the runner. + :type callback: function + :param args: Arguments to pass to the callback function + :param kwargs: Keywords to pass to the callback function + ''' + + def __init__(self, fn, *args, **kwargs): + super(Worker, self).__init__() + # Store constructor arguments (re-used for processing) + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = s.WorkerSignals() + + # Add the callback to our kwargs + self.kwargs['progress_callback'] = self.signals.progress + + @pyqtSlot() + def run(self): + ''' + Initialise the runner function with passed args, kwargs. + ''' + # Retrieve args/kwargs here; and fire processing using them + try: + result = self.fn(*self.args, **self.kwargs) + except: + traceback.print_exc() + exctype, value = sys.exc_info()[:2] + self.signals.error.emit((exctype, value, traceback.format_exc())) + else: + self.signals.result.emit(result) # Return the result of the processing + finally: + self.signals.finished.emit() # Done diff --git a/Printer_canvas.py b/Printer_canvas.py new file mode 100644 index 0000000..28aff7f --- /dev/null +++ b/Printer_canvas.py @@ -0,0 +1,154 @@ +import numpy as np +from matplotlib.figure import Figure +import matplotlib as mpl +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas + +class printer_canvas(FigureCanvas): + def __init__(self, key): + self.id = key + self.c = None + self.win = None + + if self.id == "xy": + self.xy_fig = (Figure(constrained_layout=True)) + self.axes = self.xy_fig.add_subplot(111) + super(printer_canvas, self).__init__(self.xy_fig) + self.axes.set_ylabel('y pos (mm)', fontsize=6) + self.axes.set(xlim=(-10, 11)) + self.yticks = np.arange(-10, 10, 2).tolist() + self.axes.get_yaxis().set_ticks(self.yticks) + self.axes.set(ylim=(-10, 11)) + self.gcode_loaded = 0 + + elif self.id == "xz": + self.xz_fig = (Figure(constrained_layout=True)) + self.axes = self.xz_fig.add_subplot(111) + super(printer_canvas, self).__init__(self.xz_fig) + self.axes.set_ylabel('z pos (mm)', fontsize=6) + self.axes.set(xlim=(-10, 10)) + self.zticks = np.arange(-3, 3, 3).tolist() + self.axes.get_yaxis().set_ticks(self.zticks) + self.axes.set(ylim=(-3, 3)) + + self.axes.set_facecolor((180/255, 180/255, 180/255)) + self.axes.set_xlabel('x pos (mm)', fontsize=6) + self.xticks = np.arange(-10, 10, 2).tolist() + self.axes.get_xaxis().set_ticks(self.xticks) + self.axes.tick_params(axis='both', which='major', labelsize=6) + self.axes.tick_params(axis='both', which='minor', labelsize=6) + self.axes.grid(which='major', color='black', linestyle='-', linewidth=0.1, alpha=0.5) + self.axes.grid(which='minor', color='black', linestyle='-', linewidth=0.05, alpha=0.25) + self.axes.xaxis.labelpad = -2 + self.axes.yaxis.labelpad = -5 + self._plot_ref1 = None + + def update_win(self, win): + self.win = win + + def update_printer(self, c): + self.c = c + + def update_plot(self): + if self.id == "xy": + print('update plot xy function') + if self._plot_ref1 is None: + plot_refs1 = self.axes.scatter(self.c.p_xpos_current,self.c.p_ypos_current,color='red',label='label',marker='o',s=15) + self._plot_ref1 = plot_refs1 + else: + self._plot_ref1.set_offsets([self.c.p_xpos_current,self.c.p_ypos_current]) + self.win.image_printer_position_xy.draw() + elif self.id == "xz": + print('update plot xz function') + if self._plot_ref1 is None: + plot_refs1 = self.axes.scatter(self.c.p_xpos_current,self.c.p_z_cent-self.c.p_zpos_current,color='red',label='label',marker='o',s=15) + self._plot_ref1 = plot_refs1 + else: + self._plot_ref1.set_offsets([self.c.p_xpos_current,self.c.p_z_cent+self.c.p_zpos_current]) + self.win.image_printer_position_xz.draw() + +# class printer_xy_canvas(FigureCanvas): +# +# def __init__(self): +# self.xy_fig = (Figure(constrained_layout=True)) +# # self.xy_fig.set_facecolor((80/255, 80/255, 80/255)) +# self.axes = self.xy_fig.add_subplot(111) +# self.axes.set_facecolor((180/255, 180/255, 180/255)) +# super(printer_xy_canvas, self).__init__(self.xy_fig) +# self.axes.set_xlabel('x pos (mm)', fontsize=6) +# self.axes.set_ylabel('y pos (mm)', fontsize=6) +# self.axes.tick_params(axis='both', which='major', labelsize=6) +# self.axes.tick_params(axis='both', which='minor', labelsize=6) +# self.xticks = np.arange(-10, 10, 2).tolist() +# self.axes.get_xaxis().set_ticks(self.xticks) +# self.axes.set(xlim=(-10, 11)) +# self.yticks = np.arange(-10, 10, 2).tolist() +# self.axes.get_yaxis().set_ticks(self.yticks) +# self.axes.set(ylim=(-10, 11)) +# self.axes.grid(which='major', color='black', linestyle='-', linewidth=0.1, alpha=0.5) +# self.axes.grid(which='minor', color='black', linestyle='-', linewidth=0.05, alpha=0.25) +# self.axes.xaxis.labelpad = -2 +# self.axes.yaxis.labelpad = -5 +# self._plot_ref1 = None +# self.gcode_loaded = 0 +# self.win = None +# self.c = None +# return +# +# def update_win(self, win): +# self.win = win +# +# def update_printer(self, c): +# self.c = c +# +# def update_plot(self): +# print('update plot xy function') +# if self._plot_ref1 is None: +# plot_refs1 = self.axes.scatter(self.c.p_xpos_current,self.c.p_ypos_current,color='red',label='label',marker='o',s=15) +# self._plot_ref1 = plot_refs1 +# else: +# self._plot_ref1.set_offsets([self.c.p_xpos_current,self.c.p_ypos_current]) +# self.win.image_printer_position_xy.draw() +# return +# +# class printer_xz_canvas(FigureCanvas): +# +# def __init__(self): +# self.xz_fig = (Figure(constrained_layout=True)) +# # self.xz_fig.set_facecolor((80/255, 80/255, 80/255)) +# self.axes = self.xz_fig.add_subplot(111) +# self.axes.set_facecolor((180/255, 180/255, 180/255)) +# super(printer_xz_canvas, self).__init__(self.xz_fig) +# self.axes.set_xlabel('x pos (mm)', fontsize=6) +# self.axes.set_ylabel('z pos (mm)', fontsize=6) +# self.axes.tick_params(axis='both', which='major', labelsize=6) +# self.axes.tick_params(axis='both', which='minor', labelsize=6) +# self.xticks = np.arange(-10, 10, 2).tolist() +# self.axes.get_xaxis().set_ticks(self.xticks) +# self.axes.set(xlim=(-10, 10)) +# self.zticks = np.arange(-3, 3, 3).tolist() +# self.axes.get_yaxis().set_ticks(self.zticks) +# self.axes.set(ylim=(-3, 3)) +# self.axes.grid(which='major', color='black', linestyle='-', linewidth=0.1, alpha=0.5) +# self.axes.grid(which='minor', color='black', linestyle='-', linewidth=0.05, alpha=0.25) +# self.axes.xaxis.labelpad = -2 +# self.axes.yaxis.labelpad = -5 +# self._plot_ref1 = None +# self.c = None +# return +# +# def update_win(self, win): +# self.win = win +# +# def update_printer(self, c): +# self.c = c +# +# def update_plot(self): +# print('update plot xz function') +# if self._plot_ref1 is None: +# plot_refs1 = self.axes.scatter(self.c.p_xpos_current,self.c.p_z_cent-self.c.p_zpos_current,color='red',label='label',marker='o',s=15) +# self._plot_ref1 = plot_refs1 +# else: +# self._plot_ref1.set_offsets([self.c.p_xpos_current,self.c.p_z_cent+self.c.p_zpos_current]) +# self.win.image_printer_position_xz.draw() +# return diff --git a/Start_up.py b/Start_up.py new file mode 100644 index 0000000..b6c18c0 --- /dev/null +++ b/Start_up.py @@ -0,0 +1,22 @@ +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtCore import QTimer, Qt, QThreadPool, QRunnable +from PyQt5.QtWidgets import QFileDialog, QLabel, QVBoxLayout, QWidget, QDesktopWidget + +class startup_window(QWidget): + def __init__(self): + super().__init__() + pixmap = QtGui.QPixmap('startup.png') + pixmap = pixmap.scaled(600,600) + self.label = QLabel() + self.label.setPixmap(pixmap) + layout = QVBoxLayout() + layout.addWidget(self.label) + self.setLayout(layout) + self.setGeometry(0,0,600,600) + qtrect = self.frameGeometry() + qtrect.moveCenter(QDesktopWidget().availableGeometry().center()) + self.move(qtrect.topLeft()) + self.setWindowTitle('flow software loading...') + self.setWindowFlag(Qt.FramelessWindowHint) + self.setWindowIcon(QtGui.QIcon('cells.png')) + return diff --git a/cells.png b/cells.png new file mode 100644 index 0000000..747897b Binary files /dev/null and b/cells.png differ diff --git a/flow_printer_app.py b/flow_printer_app.py new file mode 100644 index 0000000..99a78be --- /dev/null +++ b/flow_printer_app.py @@ -0,0 +1,2679 @@ +import os +import sys +import datetime +import numpy as np +# import random +import time +# import threading +import re +import warnings +import serial +from PIL import Image +# import pyvips +import pandas as pd +from pandas import DataFrame +from matplotlib.figure import Figure +# from matplotlib import cm +import matplotlib as mpl +import matplotlib.pyplot as plt +import pyqtgraph as pg +mpl.use('Qt5Agg') +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +# from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtCore import QTimer, Qt, QThreadPool, QRunnable +from PyQt5.QtWidgets import QFileDialog, QLabel, QVBoxLayout, QWidget, QDesktopWidget +from simple_pyspin import Camera +from functools import partial +from playsound import playsound +# from collections import OrderedDict # this is used, not sure about the error +from pipython import GCSDevice, pitools +from pipython.gcscommands import GCSCommands as gcs + + +## import classes +import Start_up as su +import Graphic_display as gd +import Printer_canvas as pc +import Histogram as hc +import Print_thread as pt + +class main_window(QtWidgets.QMainWindow): + + def __init__(self): + super(main_window, self).__init__() + self.setWindowIcon(QtGui.QIcon('cells.png')) + app.setWindowIcon(QtGui.QIcon('cells.png')) + self.gamma = 1 + self.zoom_factor = 1 + self.setup_main_window() + self.setup_ui_text() + self.actions() + self.threadpool = QThreadPool() + self.threadpool.setMaxThreadCount(10) + try: + self.pathname = 'C:/Users/sunyi/Desktop/gcode_generator/gcode' + self.filenames = os.listdir(self.pathname) + except: + self.pathname = 'C:/Users/sunyi/Desktop' + self.filenames = os.listdir(self.pathname) + _filenames = [] + for i in range(len(self.filenames)): + item = self.filenames[i] + if '.gcode' in item: + self.list_current_directory.addItem(item) + _filenames.append(item) + self.list_current_directory.setCurrentRow(0) + os.chdir(self.pathname) + self.filenames = _filenames + if c_camera.cam: + # refresh ui camera variables + self.lineedit_analog_gain.setText(str(np.round(c_camera.cam.get_info('Gain')['value'],2))+' dB') + self.lineedit_exposure.setText(str(np.round(c_camera.cam.get_info('ExposureTime')['value'],2))+' ms') + self.take_image_start_time = time.time() + return + + def closeEvent(self, event): + try: + c_camera.cam.stop() # Stop recording if camera caught on + except: + 0 + if c_main_window.cbox_live.isChecked(): + c_camera.live_timer.stop() + + try: + c_camera.cam.close() + time.sleep(0.1) + except: + print('couldn''t close/clean up camera') + + try: + c.laser_off() + c.l.close() + c.l.__del__() + c.l = 0 + print('laser closed') + except: + 0 + print('couldn''t close/clean up laser') + + try: + c.z.SVO(1, False) + c.z.CloseConnection() + c.z = 0 + print('z stage close/clean') + except: + print('couldn''t close/clean up z stage') + + try: + c.xy.SVO(1, False) + c.xy.SVO(2, False) + c.xy.CloseConnection() + c.xy = 0 + print('xy stage close/clean') + except: + print('couldn''t close/clean up xy stage') + time.sleep(0.1) # hang here - otherwise we get some pthread-mutex-assertion-error / glitch where threads dont shutdown properly + app.quit() + print('close event ran') + print('close all controllers properly here: main window closeEvent') + return + + def setup_main_window(self): + self.setObjectName('main_window') + self.resize(1349, 915) + # self.setStyleSheet('color:#FFFFFF;\n' +# 'background-color: rgb(80,80,80);') + self.centralwidget = QtWidgets.QWidget(self) + self.centralwidget.setObjectName('centralwidget') + self.image_camera = QtWidgets.QLabel(self.centralwidget) + self.image_camera.setGeometry(QtCore.QRect(345, 25, 958, 638)) + self.image_camera.setText('') + self.image_camera.setScaledContents(True) + self.image_camera.setObjectName('image_camera') + ####pyqtgraph + pg.setConfigOption("background", (236, 236, 236)) + pg.setConfigOption("foreground", "k") + self.hist_flag1 = None + self.hist_flag2 = None + self.hist = pg.PlotWidget() + self.setCentralWidget(self.hist) + self.hist.setGeometry(QtCore.QRect(319, 670, 996, 200)) + self.hist.setObjectName('image_histogram') + self.histogram_layout = QtWidgets.QVBoxLayout() + self.histogram_layout.addWidget(self.hist) + self.hist.setXRange(-1, 257) + self.hist.setMouseEnabled(x=False, y=False) + self.hist.showGrid(x=True, y=True, alpha = 0.5) + # self.hist.hideAxis('left') + # self.hist.hideLabel('bottom') + self.hist.setLabel('left', text='') + self.hist.setLabel('bottom', text='') + self.hist.setLogMode(x=False, y=True) + self.hist.plot([1,2,3,250], [0.5, 1, 3,0], pen = pg.mkPen('k', width = 3),alpha = 0.5, clear=True) + + + ## previous + self.image_histogram = hc.histogram_canvas() + # self.setCentralWidget(self.image_histogram) + # self.image_histogram.setGeometry(QtCore.QRect(319, 670, 996, 200)) + # self.image_histogram.setObjectName('image_histogram') + # self.histogram_layout = QtWidgets.QVBoxLayout() + # self.histogram_layout.addWidget(self.image_histogram) + + self.status_image = QtWidgets.QLabel(self.centralwidget) + self.status_image.setGeometry(QtCore.QRect(709, 870, 571, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(True) + self.status_image.setFont(font) + self.status_image.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.status_image.setStyleSheet('color:#FFFFFF') + self.status_image.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.status_image.setObjectName('status_image') + self.status_current_directory = QtWidgets.QLabel(self.centralwidget) + self.status_current_directory.setGeometry(QtCore.QRect(200, 665, 181, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(True) + self.status_current_directory.setFont(font) + self.status_current_directory.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.status_current_directory.setStyleSheet('color:#FFFFFF\n' +# '') + self.status_current_directory.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.status_current_directory.setObjectName('status_current_directory') + self.line_2 = QtWidgets.QFrame(self.centralwidget) + self.line_2.setGeometry(QtCore.QRect(20, 650, 291, 16)) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName('line_2') + self.list_current_directory = QtWidgets.QListWidget(self.centralwidget) + self.list_current_directory.setGeometry(QtCore.QRect(20, 701, 291, 161)) + # self.list_current_directory.setStyleSheet('background-color: rgb(128,128,128);') + self.list_current_directory.setObjectName('list_current_directory') + font = QtGui.QFont() + font.setPointSize(8) + self.list_current_directory.setFont(font) + self.btn_change_directory = QtWidgets.QPushButton(self.centralwidget) + self.btn_change_directory.setGeometry(QtCore.QRect(20, 670, 80, 25)) + # self.btn_change_directory.setStyleSheet('color:#FFFFFF') + self.btn_change_directory.setObjectName('btn_change_directory') + self.tabs = QtWidgets.QTabWidget(self.centralwidget) + self.tabs.setGeometry(QtCore.QRect(10, 10, 321, 641)) + self.tabs.setFocusPolicy(QtCore.Qt.TabFocus) + # self.tabs.setStyleSheet('border-color: rgb(80, 80, 80);\n' +# 'selection-color: rgb(80, 80, 80);\n' +# 'alternate-background-color: rgb(80, 80, 80);\n' +# 'selection-color: rgb(128, 128, 128);\n' +# 'background-color: rgb(80,80, 80);\n' +# 'color:rgb(0, 0, 0);\n' +# '') + self.tabs.setObjectName('tabs') + self.camera_tab = QtWidgets.QWidget() + self.camera_tab.setObjectName('camera_tab') + self.btn_ROI_in = QtWidgets.QPushButton(self.camera_tab) + self.btn_ROI_in.setGeometry(QtCore.QRect(5, 195, 80, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.btn_ROI_in.sizePolicy().hasHeightForWidth()) + self.btn_ROI_in.setSizePolicy(sizePolicy) + # self.btn_ROI_in.setStyleSheet('color:#FFFFFF;') + self.btn_ROI_in.setAutoDefault(False) + self.btn_ROI_in.setDefault(False) + self.btn_ROI_in.setFlat(False) + self.btn_ROI_in.setObjectName('btn_ROI_in') + self.btn_ROI_out = QtWidgets.QPushButton(self.camera_tab) + self.btn_ROI_out.setGeometry(QtCore.QRect(105, 195, 80, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.btn_ROI_out.sizePolicy().hasHeightForWidth()) + self.btn_ROI_out.setSizePolicy(sizePolicy) + # self.btn_ROI_out.setStyleSheet('color:#FFFFFF;') + self.btn_ROI_out.setAutoDefault(False) + self.btn_ROI_out.setDefault(False) + self.btn_ROI_out.setFlat(False) + self.btn_ROI_out.setObjectName('btn_ROI_out') + self.btn_ROI_home = QtWidgets.QPushButton(self.camera_tab) + self.btn_ROI_home.setGeometry(QtCore.QRect(195, 195, 80, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.btn_ROI_home.sizePolicy().hasHeightForWidth()) + self.btn_ROI_home.setSizePolicy(sizePolicy) + # self.btn_ROI_home.setStyleSheet('color:#FFFFFF;') + self.btn_ROI_home.setAutoDefault(False) + self.btn_ROI_home.setDefault(False) + self.btn_ROI_home.setFlat(False) + self.btn_ROI_home.setObjectName('btn_ROI_home') + self.status_capture = QtWidgets.QLabel(self.camera_tab) + self.status_capture = QtWidgets.QLabel(self.camera_tab) + self.status_capture.setGeometry(QtCore.QRect(105, 35, 141, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(True) + self.status_capture.setFont(font) + # self.status_capture.setStyleSheet('color:#FFFFFF;') + self.status_capture.setAlignment(QtCore.Qt.AlignCenter) + self.status_capture.setObjectName('status_capture') + self.lineedit_analog_gain = QtWidgets.QLineEdit(self.camera_tab) + self.lineedit_analog_gain.setGeometry(QtCore.QRect(105, 105, 80, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_analog_gain.sizePolicy().hasHeightForWidth()) + self.lineedit_analog_gain.setSizePolicy(sizePolicy) + # self.lineedit_analog_gain.setStyleSheet('background-color: rgb(128, 128, 128);') + self.lineedit_analog_gain.setObjectName('lineedit_analog_gain') + # self.cbox_target_FPS = QtWidgets.QCheckBox(self.camera_tab) + # self.cbox_target_FPS.setGeometry(QtCore.QRect(205, 160, 80, 30)) + # sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + # sizePolicy.setHorizontalStretch(0) + # sizePolicy.setVerticalStretch(0) + # sizePolicy.setHeightForWidth(self.cbox_target_FPS.sizePolicy().hasHeightForWidth()) + # self.cbox_target_FPS.setSizePolicy(sizePolicy) + # self.cbox_target_FPS.setAutoFillBackground(False) + # self.cbox_target_FPS.setStyleSheet('color:#FFFFFF;') + # self.cbox_target_FPS.setCheckable(True) + # self.cbox_target_FPS.setTristate(False) + # self.cbox_target_FPS.setObjectName('cbox_target_FPS') + self.lineedit_exposure = QtWidgets.QLineEdit(self.camera_tab) + self.lineedit_exposure.setGeometry(QtCore.QRect(105, 75, 80, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_exposure.sizePolicy().hasHeightForWidth()) + self.lineedit_exposure.setSizePolicy(sizePolicy) + # self.lineedit_exposure.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_exposure.setObjectName('lineedit_exposure') + self.label_target_FPS = QtWidgets.QLabel(self.camera_tab) + self.label_target_FPS.setGeometry(QtCore.QRect(10, 160, 80, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(False) + self.label_target_FPS.setFont(font) + self.label_target_FPS.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.label_target_FPS.setStyleSheet('color:#FFFFFF;') + self.label_target_FPS.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_target_FPS.setObjectName('label_target_FPS') + self.combobox_binning = QtWidgets.QComboBox(self.camera_tab) + self.combobox_binning.setGeometry(QtCore.QRect(103, 135, 84, 20)) + self.combobox_binning.setStyleSheet('text-align:center;') + # self.combobox_binning.setStyleSheet('color:#FFFFFF;\n' +# 'selection-background-color: rgb(0,168,0);\n' +# 'background-color: rgb(128, 128, 128);\n' +# '') + self.combobox_binning.setObjectName('combobox_binning') + self.combobox_binning.addItem('') + self.combobox_binning.addItem('') + self.combobox_binning.addItem('') + self.btn_snap = QtWidgets.QPushButton(self.camera_tab) + self.btn_snap.setGeometry(QtCore.QRect(5, 10, 80, 25)) + # self.btn_snap.setStyleSheet('color:#FFFFFF;') + self.btn_snap.setObjectName('btn_snap') + self.cbox_exposure = QtWidgets.QCheckBox(self.camera_tab) + self.cbox_exposure.setGeometry(QtCore.QRect(205, 70, 80, 30)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cbox_exposure.sizePolicy().hasHeightForWidth()) + self.cbox_exposure.setSizePolicy(sizePolicy) + self.cbox_exposure.setAutoFillBackground(False) + # self.cbox_exposure.setStyleSheet('color:#FFFFFF;') + self.cbox_exposure.setCheckable(True) + self.cbox_exposure.setTristate(False) + self.cbox_exposure.setObjectName('cbox_exposure') + self.cbox_analog_gain = QtWidgets.QCheckBox(self.camera_tab) + self.cbox_analog_gain.setGeometry(QtCore.QRect(205, 100, 80, 30)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cbox_analog_gain.sizePolicy().hasHeightForWidth()) + self.cbox_analog_gain.setSizePolicy(sizePolicy) + self.cbox_analog_gain.setAutoFillBackground(False) + # self.cbox_analog_gain.setStyleSheet('color:#FFFFFF;') + self.cbox_analog_gain.setCheckable(True) + self.cbox_analog_gain.setTristate(False) + self.cbox_analog_gain.setObjectName('cbox_analog_gain') + self.lineedit_target_FPS = QtWidgets.QLineEdit(self.camera_tab) + self.lineedit_target_FPS.setGeometry(QtCore.QRect(105, 165, 80, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_target_FPS.sizePolicy().hasHeightForWidth()) + self.lineedit_target_FPS.setSizePolicy(sizePolicy) + # self.lineedit_target_FPS.setStyleSheet('background-color: rgb(128, 128, 128);') + self.lineedit_target_FPS.setObjectName('lineedit_target_FPS') + self.btn_save = QtWidgets.QPushButton(self.camera_tab) + self.btn_save.setGeometry(QtCore.QRect(5, 40, 80, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.btn_save.sizePolicy().hasHeightForWidth()) + self.btn_save.setSizePolicy(sizePolicy) + # self.btn_save.setStyleSheet('color:#FFFFFF;') + self.btn_save.setAutoDefault(False) + self.btn_save.setDefault(False) + self.btn_save.setFlat(False) + self.btn_save.setObjectName('btn_save') + self.label_camera_binning = QtWidgets.QLabel(self.camera_tab) + self.label_camera_binning.setGeometry(QtCore.QRect(10, 130, 100, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(False) + self.label_camera_binning.setFont(font) + self.label_camera_binning.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.label_camera_binning.setStyleSheet('color:#FFFFFF;\n' +# '') + self.label_camera_binning.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_camera_binning.setObjectName('label_camera_binning') + self.btn_video = QtWidgets.QPushButton(self.camera_tab) + self.btn_video.setGeometry(QtCore.QRect(105, 10, 80, 25)) + # self.btn_video.setStyleSheet('color:#FFFFFF;') + self.btn_video.setObjectName('btn_video') + self.cbox_live = QtWidgets.QCheckBox(self.camera_tab) + self.cbox_live.setGeometry(QtCore.QRect(205, 5, 80, 30)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cbox_live.sizePolicy().hasHeightForWidth()) + self.cbox_live.setSizePolicy(sizePolicy) + self.cbox_live.setAutoFillBackground(False) + # self.cbox_live.setStyleSheet('color:#FFFFFF;') + self.cbox_live.setCheckable(True) + self.cbox_live.setTristate(False) + self.cbox_live.setObjectName('cbox_live') + self.label_analog_gain = QtWidgets.QLabel(self.camera_tab) + self.label_analog_gain.setGeometry(QtCore.QRect(10, 100, 80, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(False) + self.label_analog_gain.setFont(font) + self.label_analog_gain.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.label_analog_gain.setStyleSheet('color:#FFFFFF;') + self.label_analog_gain.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_analog_gain.setObjectName('label_analog_gain') + self.label_exposure = QtWidgets.QLabel(self.camera_tab) + self.label_exposure.setGeometry(QtCore.QRect(10, 70, 80, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(False) + self.label_exposure.setFont(font) + self.label_exposure.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.label_exposure.setStyleSheet('color:#FFFFFF;') + self.label_exposure.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_exposure.setObjectName('label_exposure') + self.line_1 = QtWidgets.QFrame(self.camera_tab) + self.line_1.setGeometry(QtCore.QRect(10, 220, 261, 16)) + self.line_1.setFrameShape(QtWidgets.QFrame.HLine) + self.line_1.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_1.setObjectName('line_1') + self.lineedit_gamma = QtWidgets.QLineEdit(self.camera_tab) + self.lineedit_gamma.setGeometry(QtCore.QRect(90, 235, 50, 25)) + # self.lineedit_gamma.setStyleSheet('background-color: rgb(128, 128, 128);') + self.lineedit_gamma.setObjectName('lineedit_gamma') + self.cbox_normalise = QtWidgets.QCheckBox(self.camera_tab) + self.cbox_normalise.setGeometry(QtCore.QRect(10, 290, 131, 30)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cbox_normalise.sizePolicy().hasHeightForWidth()) + self.cbox_normalise.setSizePolicy(sizePolicy) + self.cbox_normalise.setAutoFillBackground(False) + # self.cbox_normalise.setStyleSheet('color:#FFFFFF;') + self.cbox_normalise.setCheckable(True) + self.cbox_normalise.setTristate(False) + self.cbox_normalise.setObjectName('cbox_normalise') + self.cbox_show_scalebar = QtWidgets.QCheckBox(self.camera_tab) + self.cbox_show_scalebar.setGeometry(QtCore.QRect(10, 320, 131, 30)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cbox_show_scalebar.sizePolicy().hasHeightForWidth()) + self.cbox_show_scalebar.setSizePolicy(sizePolicy) + self.cbox_show_scalebar.setAutoFillBackground(False) + # self.cbox_show_scalebar.setStyleSheet('color:#FFFFFF;') + self.cbox_show_scalebar.setCheckable(True) + self.cbox_show_scalebar.setChecked(True) + self.cbox_show_scalebar.setTristate(False) + self.cbox_show_scalebar.setObjectName('cbox_show_scalebar') + self.combobox_objective = QtWidgets.QComboBox(self.camera_tab) + self.combobox_objective.setGeometry(QtCore.QRect(80, 410, 84, 25)) +# self.combobox_objective.setStyleSheet('color:#FFFFFF;\n' +# 'selection-background-color: rgb(0,168,0);\n' +# 'background-color: rgb(128, 128, 128);\n' +# '') + self.combobox_objective.setObjectName('combobox_objective') + self.combobox_objective.addItem('') + self.combobox_objective.addItem('') + self.combobox_objective.addItem('') + self.combobox_objective.addItem('') + self.combobox_objective.addItem('') + self.combobox_colourmap = QtWidgets.QComboBox(self.camera_tab) + self.combobox_colourmap.setGeometry(QtCore.QRect(80, 350, 84, 25)) +# self.combobox_colourmap.setStyleSheet('color:#FFFFFF;\n' +# 'selection-background-color: rgb(0,168,0);\n' +# 'background-color: rgb(128, 128, 128);\n' +# '') + + self.combobox_colourmap.setObjectName('combobox_colourmap') + self.combobox_colourmap.addItem('') + self.combobox_colourmap.addItem('') + self.combobox_colourmap.addItem('') + self.combobox_colourmap.addItem('') + self.combobox_colourmap.addItem('') + self.cbox_saturated = QtWidgets.QCheckBox(self.camera_tab) + self.cbox_saturated.setGeometry(QtCore.QRect(10, 380, 131, 30)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cbox_saturated.sizePolicy().hasHeightForWidth()) + self.cbox_saturated.setSizePolicy(sizePolicy) + self.cbox_saturated.setAutoFillBackground(False) + # self.cbox_saturated.setStyleSheet('color:#FFFFFF;') + self.cbox_saturated.setCheckable(True) + self.cbox_saturated.setChecked(True) + self.cbox_saturated.setTristate(False) + self.cbox_saturated.setObjectName('cbox_saturated') + self.label_gamma = QtWidgets.QLabel(self.camera_tab) + self.label_gamma.setGeometry(QtCore.QRect(15, 230, 51, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(False) + self.label_gamma.setFont(font) + self.label_gamma.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.label_gamma.setStyleSheet('color:#FFFFFF;') + self.label_gamma.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_gamma.setObjectName('label_gamma') + self.label_objective = QtWidgets.QLabel(self.camera_tab) + self.label_objective.setGeometry(QtCore.QRect(10, 410, 61, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(False) + self.label_objective.setFont(font) + self.label_objective.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.label_objective.setStyleSheet('color:#FFFFFF;') + self.label_objective.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_objective.setObjectName('label_objective') + self.slider_gamma = QtWidgets.QSlider(self.camera_tab) + self.slider_gamma.setGeometry(QtCore.QRect(10, 260, 131, 30)) + self.slider_gamma.setMaximum(200) + self.slider_gamma.setSliderPosition(100) + self.slider_gamma.setOrientation(QtCore.Qt.Horizontal) + self.slider_gamma.setTickPosition(QtWidgets.QSlider.TicksBelow) + self.slider_gamma.setTickInterval(5) + self.slider_gamma.setObjectName('slider_gamma') + self.label_colourmap = QtWidgets.QLabel(self.camera_tab) + self.label_colourmap.setGeometry(QtCore.QRect(10, 350, 61, 30)) + font = QtGui.QFont() + font.setPointSize(9) + font.setItalic(False) + self.label_colourmap.setFont(font) + self.label_colourmap.setLayoutDirection(QtCore.Qt.LeftToRight) + # self.label_colourmap.setStyleSheet('color:#FFFFFF;') + self.label_colourmap.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_colourmap.setObjectName('label_colourmap') + self.tabs.addTab(self.camera_tab, '') + self.printer_tab = QtWidgets.QWidget() + self.printer_tab.setObjectName('printer_tab') + self.btn_print = QtWidgets.QPushButton(self.printer_tab) + self.btn_print.setGeometry(QtCore.QRect(110, 10, 91, 25)) + # self.btn_print.setStyleSheet('color:#808080') + self.btn_print.setObjectName('btn_print') + font = QtGui.QFont() + font.setPointSize(11) + font.setBold(True) + font.setWeight(75) + font.setKerning(True) + self.btn_print.setFont(font) + self.image_printer_position_xy = pc.printer_canvas('xy') # c_main_window.image_printer_position_xy.update_plot + self.verticalLayoutWidget = QtWidgets.QWidget(self.printer_tab) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(30, 260, 241, 241)) + self.verticalLayoutWidget.setObjectName('verticalLayoutWidget') + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName('verticalLayout') + #### pyqtgraph + self.graph_xy = pg.PlotWidget() + self.verticalLayout.addWidget(self.graph_xy) + self.graph_xy.setXRange(-10, 11) + self.graph_xy.setYRange(-10, 11) + self.graph_xy.showGrid(x=True, y=True, alpha=1) + self.graph_xy.setMouseEnabled(x=False, y=False) + self.xy_graph_plot = self.graph_xy.plot([c.p_xpos_current], [c.p_ypos_current], color=(255,0,0), symbol='o', clear=True, symbolBrush =(255, 0, 0)) + # self.verticalLayout.addWidget(self.image_printer_position_xy) + self.image_printer_position_xz = pc.printer_canvas('xz') + self.verticalLayoutWidget_2 = QtWidgets.QWidget(self.printer_tab) + self.verticalLayoutWidget_2.setGeometry(QtCore.QRect(30, 520, 241, 80)) + self.verticalLayoutWidget_2.setObjectName('verticalLayoutWidget_2') + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setObjectName('verticalLayout_2') + ### PlotWidget + self.graph_xz = pg.PlotWidget() + self.verticalLayout_2.addWidget(self.graph_xz) + self.graph_xz.setXRange(-10, 10) + self.graph_xz.setYRange(-3, 3) + self.graph_xz.showGrid(x=True, y=True, alpha=1) + self.graph_xz.setMouseEnabled(x=False, y=False) + self.xz_graph_plot = self.graph_xz.plot([c.p_xpos_current], [c.p_z_cent-c.p_zpos_current], color=(255,0,0), symbol='o', clear=True, symbolBrush =(255, 0, 0)) + print("x = " + str(c.p_xpos_current)) + print("y = " + str(c.p_ypos_current)) + print("z = " + str(c.p_z_cent-c.p_zpos_current)) + # self.verticalLayout_2.addWidget(self.image_printer_position_xz) + self.btn_stop_print = QtWidgets.QPushButton(self.printer_tab) + self.btn_stop_print.setGeometry(QtCore.QRect(20, 151, 181, 25)) + font = QtGui.QFont() + font.setPointSize(11) + font.setBold(True) + font.setWeight(75) + font.setKerning(True) + self.btn_stop_print.setFont(font) + # self.btn_stop_print.setStyleSheet('color: rgb(225, 0, 0);\n' +# 'background-color: rgb(120, 120, 120);') + self.btn_stop_print.setObjectName('btn_stop_print') + self.btn_pos_y = QtWidgets.QPushButton(self.printer_tab) + self.btn_pos_y.setGeometry(QtCore.QRect(140, 240, 30, 20)) + # self.btn_pos_y.setStyleSheet('color:#FFFFFF;') + self.btn_pos_y.setObjectName('btn_pos_y') + self.label_status_y_num = QtWidgets.QLabel(self.printer_tab) + self.label_status_y_num.setGeometry(QtCore.QRect(130, 600, 60, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_status_y_num.setFont(font) + # self.label_status_y_num.setStyleSheet('color:#FFFFFF') + self.label_status_y_num.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_status_y_num.setObjectName('label_status_y_num') + self.btn_neg_y = QtWidgets.QPushButton(self.printer_tab) + self.btn_neg_y.setGeometry(QtCore.QRect(140, 500, 30, 20)) + # self.btn_neg_y.setStyleSheet('color:#FFFFFF') + self.btn_neg_y.setObjectName('btn_neg_y') + self.label_status_x_num = QtWidgets.QLabel(self.printer_tab) + self.label_status_x_num.setGeometry(QtCore.QRect(30, 600, 60, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_status_x_num.setFont(font) +# self.label_status_x_num.setStyleSheet('color:#FFFFFF\n' +# '') + self.label_status_x_num.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_status_x_num.setObjectName('label_status_x_num') + self.btn_pos_z = QtWidgets.QPushButton(self.printer_tab) + self.btn_pos_z.setGeometry(QtCore.QRect(270, 240, 30, 20)) + # self.btn_pos_z.setStyleSheet('color:#FFFFFF') + self.btn_pos_z.setObjectName('btn_pos_z') + self.btn_neg_z = QtWidgets.QPushButton(self.printer_tab) + self.btn_neg_z.setGeometry(QtCore.QRect(0, 500, 30, 20)) + # self.btn_neg_z.setStyleSheet('color:#FFFFFF') + self.btn_neg_z.setObjectName('btn_neg_z') + self.btn_pos_x = QtWidgets.QPushButton(self.printer_tab) + self.btn_pos_x.setGeometry(QtCore.QRect(270, 370, 30, 20)) + # self.btn_pos_x.setStyleSheet('color:#FFFFFF') + self.btn_pos_x.setObjectName('btn_pos_x') + self.btn_neg_x = QtWidgets.QPushButton(self.printer_tab) + self.btn_neg_x.setGeometry(QtCore.QRect(0, 370, 30, 20)) + # self.btn_neg_x.setStyleSheet('color:#FFFFFF') + self.btn_neg_x.setObjectName('btn_neg_x') + self.label_status_z_num = QtWidgets.QLabel(self.printer_tab) + self.label_status_z_num.setGeometry(QtCore.QRect(240, 600, 60, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_status_z_num.setFont(font) +# self.label_status_z_num.setStyleSheet('color:#FFFFFF\n' +# '') + self.label_status_z_num.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_status_z_num.setObjectName('label_status_z_num') + self.label_status_x = QtWidgets.QLabel(self.printer_tab) + self.label_status_x.setGeometry(QtCore.QRect(10, 600, 20, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_status_x.setFont(font) + # self.label_status_x.setStyleSheet('color:#FFFFFF') + self.label_status_x.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_status_x.setObjectName('label_status_x') + self.label_status_y = QtWidgets.QLabel(self.printer_tab) + self.label_status_y.setGeometry(QtCore.QRect(110, 600, 20, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_status_y.setFont(font) + # self.label_status_y.setStyleSheet('color:#FFFFFF') + self.label_status_y.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_status_y.setObjectName('label_status_y') + self.label_status_z = QtWidgets.QLabel(self.printer_tab) + self.label_status_z.setGeometry(QtCore.QRect(220, 600, 20, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_status_z.setFont(font) + # self.label_status_z.setStyleSheet('color:#FFFFFF') + self.label_status_z.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_status_z.setObjectName('label_status_z') + self.lineedit_xincrement = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_xincrement.setGeometry(QtCore.QRect(30, 200, 31, 20)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_xincrement.sizePolicy().hasHeightForWidth()) + self.lineedit_xincrement.setSizePolicy(sizePolicy) + # self.lineedit_xincrement.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_xincrement.setObjectName('lineedit_xincrement') + self.lineedit_yincrement = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_yincrement.setGeometry(QtCore.QRect(130, 200, 31, 20)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_yincrement.sizePolicy().hasHeightForWidth()) + self.lineedit_yincrement.setSizePolicy(sizePolicy) + # self.lineedit_yincrement.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_yincrement.setObjectName('lineedit_yincrement') + self.lineedit_zincrement = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_zincrement.setGeometry(QtCore.QRect(230, 200, 31, 20)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_zincrement.sizePolicy().hasHeightForWidth()) + self.lineedit_zincrement.setSizePolicy(sizePolicy) + # self.lineedit_zincrement.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_zincrement.setObjectName('lineedit_zincrement') + self.label_xincrement = QtWidgets.QLabel(self.printer_tab) + self.label_xincrement.setGeometry(QtCore.QRect(10, 200, 21, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_xincrement.setFont(font) + # self.label_xincrement.setStyleSheet('color:#FFFFFF') + self.label_xincrement.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_xincrement.setObjectName('label_xincrement') + self.label_yincrement = QtWidgets.QLabel(self.printer_tab) + self.label_yincrement.setGeometry(QtCore.QRect(110, 200, 16, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_yincrement.setFont(font) + # self.label_yincrement.setStyleSheet('color:#FFFFFF') + self.label_yincrement.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_yincrement.setObjectName('label_yincrement') + self.label_zincrement = QtWidgets.QLabel(self.printer_tab) + self.label_zincrement.setGeometry(QtCore.QRect(210, 200, 16, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_zincrement.setFont(font) + # self.label_zincrement.setStyleSheet('color:#FFFFFF') + self.label_zincrement.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_zincrement.setObjectName('label_zincrement') + self.btn_pause_print = QtWidgets.QPushButton(self.printer_tab) + self.btn_pause_print.setGeometry(QtCore.QRect(110, 40, 91, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_pause_print.setFont(font) +# self.btn_pause_print.setStyleSheet('color: rgb(255, 255, 255);\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(150, 0, 0);\n' +# 'border-left-color: rgb(150, 0, 0);\n' +# 'background-color: rgb(140, 100, 100);') + self.btn_pause_print.setObjectName('btn_pause_print') + self.btn_load_gcode = QtWidgets.QPushButton(self.printer_tab) + self.btn_load_gcode.setGeometry(QtCore.QRect(10, 40, 91, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_load_gcode.setFont(font) +# self.btn_load_gcode.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_load_gcode.setObjectName('btn_load_gcode') + self.btn_laser_on = QtWidgets.QPushButton(self.printer_tab) + self.btn_laser_on.setGeometry(QtCore.QRect(240, 40, 61, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_laser_on.setFont(font) +# self.btn_laser_on.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_laser_on.setObjectName('btn_laser_on') + self.btn_laser_off = QtWidgets.QPushButton(self.printer_tab) + self.btn_laser_off.setGeometry(QtCore.QRect(240, 70, 61, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_laser_off.setFont(font) +# self.btn_laser_off.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_laser_off.setObjectName('btn_laser_off') + self.btn_set_laser_current = QtWidgets.QPushButton(self.printer_tab) + self.btn_set_laser_current.setGeometry(QtCore.QRect(240, 120, 61, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_set_laser_current.setFont(font) +# self.btn_set_laser_current.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_set_laser_current.setObjectName('btn_set_laser_current') + self.lineedit_set_laser_current = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_set_laser_current.setGeometry(QtCore.QRect(240, 150, 61, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_set_laser_current.sizePolicy().hasHeightForWidth()) + self.lineedit_set_laser_current.setSizePolicy(sizePolicy) + self.lineedit_set_laser_current.setStyleSheet('font: 8px') +# self.lineedit_set_laser_current.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# 'font: 8px' +# '') + self.lineedit_set_laser_current.setObjectName('lineedit_set_laser_current') + self.line_3 = QtWidgets.QFrame(self.printer_tab) + self.line_3.setGeometry(QtCore.QRect(10, 180, 281, 16)) + self.line_3.setFrameShape(QtWidgets.QFrame.HLine) + self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_3.setObjectName('line_3') + self.lineedit_set_zcent = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_set_zcent.setGeometry(QtCore.QRect(90, 70, 51, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_set_zcent.sizePolicy().hasHeightForWidth()) + self.lineedit_set_zcent.setSizePolicy(sizePolicy) +# self.lineedit_set_zcent.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_set_zcent.setObjectName('lineedit_set_zcent') + self.label_zcent_text = QtWidgets.QLabel(self.printer_tab) + self.label_zcent_text.setGeometry(QtCore.QRect(20, 70, 60, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_zcent_text.setFont(font) + # self.label_zcent_text.setStyleSheet('color:#FFFFFF') + self.label_zcent_text.setAlignment(QtCore.Qt.AlignCenter) + self.label_zcent_text.setObjectName('label_zcent_text') + self.btn_set_zcent = QtWidgets.QPushButton(self.printer_tab) + self.btn_set_zcent.setGeometry(QtCore.QRect(160, 70, 41, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_set_zcent.setFont(font) +# self.btn_set_zcent.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_set_zcent.setObjectName('btn_set_zcent') + self.line = QtWidgets.QFrame(self.printer_tab) + self.line.setGeometry(QtCore.QRect(210, 10, 20, 171)) + self.line.setFrameShape(QtWidgets.QFrame.VLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName('line') + self.label_printer_section_text = QtWidgets.QLabel(self.printer_tab) + self.label_printer_section_text.setGeometry(QtCore.QRect(10, 10, 81, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setBold(False) + font.setItalic(True) + font.setWeight(50) + self.label_printer_section_text.setFont(font) + self.label_printer_section_text.setLayoutDirection(QtCore.Qt.LeftToRight) +# self.label_printer_section_text.setStyleSheet('color:rgb(180, 180, 180);\n' +# '') + self.label_printer_section_text.setAlignment(QtCore.Qt.AlignCenter) + self.label_printer_section_text.setObjectName('label_printer_section_text') + self.label_laser_section_text = QtWidgets.QLabel(self.printer_tab) + self.label_laser_section_text.setGeometry(QtCore.QRect(240, 10, 61, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setBold(False) + font.setItalic(True) + font.setWeight(50) + self.label_laser_section_text.setFont(font) + self.label_laser_section_text.setLayoutDirection(QtCore.Qt.LeftToRight) +# self.label_laser_section_text.setStyleSheet('color:rgb(180, 180, 180);\n' +# '') + self.label_laser_section_text.setAlignment(QtCore.Qt.AlignCenter) + self.label_laser_section_text.setObjectName('label_laser_section_text') + self.lineedit_xvelocity = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_xvelocity.setGeometry(QtCore.QRect(70, 200, 31, 20)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_xvelocity.sizePolicy().hasHeightForWidth()) + self.lineedit_xvelocity.setSizePolicy(sizePolicy) +# self.lineedit_xvelocity.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_xvelocity.setObjectName('lineedit_xvelocity') + self.lineedit_yvelocity = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_yvelocity.setGeometry(QtCore.QRect(170, 200, 31, 20)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_yvelocity.sizePolicy().hasHeightForWidth()) + self.lineedit_yvelocity.setSizePolicy(sizePolicy) +# self.lineedit_yvelocity.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_yvelocity.setObjectName('lineedit_yvelocity') + self.lineedit_zvelocity = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_zvelocity.setGeometry(QtCore.QRect(270, 200, 31, 20)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_zvelocity.sizePolicy().hasHeightForWidth()) + self.lineedit_zvelocity.setSizePolicy(sizePolicy) +# self.lineedit_zvelocity.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_zvelocity.setObjectName('lineedit_zvelocity') + self.label_incr_1 = QtWidgets.QLabel(self.printer_tab) + self.label_incr_1.setGeometry(QtCore.QRect(30, 220, 31, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_incr_1.setFont(font) + # self.label_incr_1.setStyleSheet('color:#FFFFFF') + self.label_incr_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_incr_1.setObjectName('label_incr_1') + self.label_vel_1 = QtWidgets.QLabel(self.printer_tab) + self.label_vel_1.setGeometry(QtCore.QRect(70, 220, 31, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_vel_1.setFont(font) + # self.label_vel_1.setStyleSheet('color:#FFFFFF') + self.label_vel_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_vel_1.setObjectName('label_vel_1') + self.label_incr_2 = QtWidgets.QLabel(self.printer_tab) + self.label_incr_2.setGeometry(QtCore.QRect(130, 220, 31, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_incr_2.setFont(font) + # self.label_incr_2.setStyleSheet('color:#FFFFFF') + self.label_incr_2.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_incr_2.setObjectName('label_incr_2') + self.label_vel_2 = QtWidgets.QLabel(self.printer_tab) + self.label_vel_2.setGeometry(QtCore.QRect(170, 220, 31, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_vel_2.setFont(font) + # self.label_vel_2.setStyleSheet('color:#FFFFFF') + self.label_vel_2.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_vel_2.setObjectName('label_vel_2') + self.label_incr_3 = QtWidgets.QLabel(self.printer_tab) + self.label_incr_3.setGeometry(QtCore.QRect(230, 220, 31, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_incr_3.setFont(font) + # self.label_incr_3.setStyleSheet('color:#FFFFFF') + self.label_incr_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_incr_3.setObjectName('label_incr_3') + self.label_vel_3 = QtWidgets.QLabel(self.printer_tab) + self.label_vel_3.setGeometry(QtCore.QRect(270, 220, 31, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_vel_3.setFont(font) + # self.label_vel_3.setStyleSheet('color:#FFFFFF') + self.label_vel_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_vel_3.setObjectName('label_vel_3') + self.lineedit_custom_serial_send = QtWidgets.QLineEdit(self.printer_tab) + self.lineedit_custom_serial_send.setGeometry(QtCore.QRect(20, 120, 121, 25)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineedit_custom_serial_send.sizePolicy().hasHeightForWidth()) + self.lineedit_custom_serial_send.setSizePolicy(sizePolicy) +# self.lineedit_custom_serial_send.setStyleSheet('background-color: rgb(128, 128, 128);\n' +# '') + self.lineedit_custom_serial_send.setObjectName('lineedit_custom_serial_send') + self.btn_custom_serial_send = QtWidgets.QPushButton(self.printer_tab) + self.btn_custom_serial_send.setGeometry(QtCore.QRect(160, 120, 41, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_custom_serial_send.setFont(font) +# self.btn_custom_serial_send.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_custom_serial_send.setObjectName('btn_custom_serial_send') + self.label_custom_serial_send = QtWidgets.QLabel(self.printer_tab) + self.label_custom_serial_send.setGeometry(QtCore.QRect(20, 95, 121, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.label_custom_serial_send.setFont(font) + # self.label_custom_serial_send.setStyleSheet('color:#FFFFFF') + self.label_custom_serial_send.setAlignment(QtCore.Qt.AlignCenter) + self.label_custom_serial_send.setObjectName('label_custom_serial_send') + self.tabs.addTab(self.printer_tab, '') + self.tab = QtWidgets.QWidget() + self.tab.setObjectName('tab') + self.btn_connect_all = QtWidgets.QPushButton(self.tab) + self.btn_connect_all.setGeometry(QtCore.QRect(20, 10, 131, 101)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_connect_all.setFont(font) +# self.btn_connect_all.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_connect_all.setObjectName('btn_connect_all') + self.btn_connect_x = QtWidgets.QPushButton(self.tab) + self.btn_connect_x.setGeometry(QtCore.QRect(160, 10, 121, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_connect_x.setFont(font) +# self.btn_connect_x.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_connect_x.setObjectName('btn_connect_x') + self.btn_connect_y = QtWidgets.QPushButton(self.tab) + self.btn_connect_y.setGeometry(QtCore.QRect(160, 30, 121, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_connect_y.setFont(font) +# self.btn_connect_y.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_connect_y.setObjectName('btn_connect_y') + self.btn_connect_z = QtWidgets.QPushButton(self.tab) + self.btn_connect_z.setGeometry(QtCore.QRect(160, 50, 121, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_connect_z.setFont(font) +# self.btn_connect_z.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_connect_z.setObjectName('btn_connect_z') + self.btn_connect_laser = QtWidgets.QPushButton(self.tab) + self.btn_connect_laser.setGeometry(QtCore.QRect(160, 70, 121, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_connect_laser.setFont(font) +# self.btn_connect_laser.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_connect_laser.setObjectName('btn_connect_laser') + self.btn_connect_camera = QtWidgets.QPushButton(self.tab) + self.btn_connect_camera.setGeometry(QtCore.QRect(160, 90, 121, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_connect_camera.setFont(font) +# self.btn_connect_camera.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_connect_camera.setObjectName('btn_connect_camera') + self.status_controller = QtWidgets.QLabel(self.tab) + self.status_controller.setGeometry(QtCore.QRect(20, 120, 261, 20)) + font = QtGui.QFont() + font.setPointSize(9) + font.setBold(False) + font.setItalic(True) + font.setWeight(50) + self.status_controller.setFont(font) + self.status_controller.setLayoutDirection(QtCore.Qt.LeftToRight) +# self.status_controller.setStyleSheet('color:rgb(180, 180, 180);\n' +# '') + self.status_controller.setAlignment(QtCore.Qt.AlignCenter) + self.status_controller.setObjectName('status_controller') + self.btn_disconnect_all = QtWidgets.QPushButton(self.tab) + self.btn_disconnect_all.setGeometry(QtCore.QRect(20, 150, 261, 21)) + font = QtGui.QFont() + font.setPointSize(9) + font.setKerning(True) + self.btn_disconnect_all.setFont(font) +# self.btn_disconnect_all.setStyleSheet('color: #FFFFFF;\n' +# 'border-bottom-color: rgb(111, 0, 0);\n' +# 'border-right-color: rgb(111, 0, 0);\n' +# 'border-top-color: rgb(50,50,50);\n' +# 'border-left-color: rgb(50,50,50);\n' +# '') + self.btn_disconnect_all.setObjectName('btn_disconnect_all') + self.tabs.addTab(self.tab, '') + self.status_printer = QtWidgets.QLabel(self.centralwidget) + self.status_printer.setGeometry(QtCore.QRect(20, 870, 681, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.status_printer.setFont(font) + self.status_printer.setObjectName('status_printer') + self.status_fps_text = QtWidgets.QLabel(self.centralwidget) + self.status_fps_text.setGeometry(QtCore.QRect(320, 5, 31, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.status_fps_text.setFont(font) + # self.status_fps_text.setStyleSheet('color:#FFFFFF;') + self.status_fps_text.setObjectName('status_fps_text') + self.status_fps_number = QtWidgets.QLabel(self.centralwidget) + self.status_fps_number.setGeometry(QtCore.QRect(360, 5, 60, 20)) + font = QtGui.QFont() + font.setItalic(True) + self.status_fps_number.setFont(font) + # self.status_fps_number.setStyleSheet('color:#FFFFFF;') + self.status_fps_number.setObjectName('status_fps_number') + self.setCentralWidget(self.centralwidget) + self.statusBar = QtWidgets.QStatusBar(self) + self.statusBar.setObjectName('statusBar') + self.setStatusBar(self.statusBar) + + self.tabs.setCurrentIndex(0) + self.combobox_objective.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(self) + + def setup_ui_text(self): + _translate = QtCore.QCoreApplication.translate + self.status_image.setText(_translate('main_window', '... image status ...')) + self.status_current_directory.setText(_translate('main_window', '... current directory ...')) + self.btn_change_directory.setText(_translate('main_window', 'change dir')) + self.btn_print.setText(_translate('main_window', 'print')) + self.btn_ROI_in.setText(_translate('main_window', 'zoom in')) + self.btn_ROI_out.setText(_translate('main_window', 'zoom out')) + self.btn_ROI_home.setText(_translate('main_window', 'zoom home')) + self.status_capture.setText(_translate('main_window', '...camera status...')) + self.label_target_FPS.setText(_translate('main_window', 'target FPS')) + self.combobox_binning.setItemText(0, _translate('main_window', '4x4')) + self.combobox_binning.setItemText(1, _translate('main_window', '2x2')) + self.combobox_binning.setItemText(2, _translate('main_window', '1x1')) + self.btn_snap.setText(_translate('main_window', 'snap')) + self.cbox_exposure.setText(_translate('main_window', 'auto')) + self.cbox_analog_gain.setText(_translate('main_window', 'auto')) + self.btn_save.setText(_translate('main_window', 'save')) + self.label_camera_binning.setText(_translate('main_window', 'binning')) + self.btn_video.setText(_translate('main_window', 'video')) + self.cbox_live.setText(_translate('main_window', ' live')) + self.label_analog_gain.setText(_translate('main_window', 'analog gain')) + self.label_exposure.setText(_translate('main_window', 'exposure')) + self.cbox_normalise.setText(_translate('main_window', ' digital norm.')) + self.cbox_show_scalebar.setText(_translate('main_window', ' show scalebar')) + self.combobox_objective.setCurrentText(_translate('main_window', 'x40')) + self.combobox_objective.setItemText(0, _translate('main_window', 'x40')) + self.combobox_objective.setItemText(1, _translate('main_window', 'x20')) + self.combobox_objective.setItemText(2, _translate('main_window', 'x10')) + self.combobox_objective.setItemText(3, _translate('main_window', 'x5')) + self.combobox_objective.setItemText(4, _translate('main_window', 'x4')) + self.combobox_colourmap.setItemText(0, _translate('main_window', 'magma')) + self.combobox_colourmap.setItemText(1, _translate('main_window', 'green')) + self.combobox_colourmap.setItemText(2, _translate('main_window', 'viridis')) + self.combobox_colourmap.setItemText(3, _translate('main_window', 'jet')) + self.combobox_colourmap.setItemText(4, _translate('main_window', 'raw')) + self.cbox_saturated.setText(_translate('main_window', ' saturated pixels')) + self.label_gamma.setText(_translate('main_window', 'gamma')) + self.label_objective.setText(_translate('main_window', 'objective')) + self.label_colourmap.setText(_translate('main_window', 'colormap')) + self.tabs.setTabText(self.tabs.indexOf(self.camera_tab), _translate('main_window', 'camera')) + self.btn_stop_print.setText(_translate('main_window', 'stop all')) + self.label_status_x_num.setText(_translate('main_window', '... x pos ...')) + self.label_status_y_num.setText(_translate('main_window', '... y pos ...')) + self.label_status_z_num.setText(_translate('main_window', '... z pos ...')) + self.btn_pos_y.setText(_translate('main_window', '+y')) + self.btn_neg_y.setText(_translate('main_window', '-y')) + self.btn_pos_z.setText(_translate('main_window', '+z')) + self.btn_neg_z.setText(_translate('main_window', '-z')) + self.btn_pos_x.setText(_translate('main_window', '+x')) + self.btn_neg_x.setText(_translate('main_window', '-x')) + self.label_status_x.setText(_translate('main_window', 'x:')) + self.label_status_y.setText(_translate('main_window', 'y:')) + self.label_status_z.setText(_translate('main_window', 'z:')) + self.label_xincrement.setText(_translate('main_window', 'x:')) + self.label_yincrement.setText(_translate('main_window', 'y:')) + self.label_zincrement.setText(_translate('main_window', 'z:')) + self.btn_pause_print.setText(_translate('main_window', 'pause')) + self.btn_load_gcode.setText(_translate('main_window', 'load g.code')) + self.btn_laser_on.setText(_translate('main_window', 'laser on')) + self.btn_laser_off.setText(_translate('main_window', 'laser off')) + self.btn_set_laser_current.setText(_translate('main_window', 'current')) + self.label_zcent_text.setText(_translate('main_window', 'z-cent')) + self.btn_set_zcent.setText(_translate('main_window', 'set')) + self.label_printer_section_text.setText(_translate('main_window', 'printer')) + self.label_laser_section_text.setText(_translate('main_window', 'laser')) + self.label_incr_1.setText(_translate('main_window', 'incr')) + self.label_vel_1.setText(_translate('main_window', 'vel')) + self.label_incr_2.setText(_translate('main_window', 'incr')) + self.label_vel_2.setText(_translate('main_window', 'vel')) + self.label_incr_3.setText(_translate('main_window', 'incr')) + self.label_vel_3.setText(_translate('main_window', 'vel')) + self.btn_custom_serial_send.setText(_translate('main_window', 'send')) + self.label_custom_serial_send.setText(_translate('main_window', 'custom serial send')) + self.tabs.setTabText(self.tabs.indexOf(self.printer_tab), _translate('main_window', 'printer')) + self.btn_connect_all.setText(_translate('main_window', 'connect ALL')) + self.btn_connect_x.setText(_translate('main_window', 'connect x')) + self.btn_connect_y.setText(_translate('main_window', 'connect y')) + self.btn_connect_z.setText(_translate('main_window', 'connect z')) + self.btn_connect_laser.setText(_translate('main_window', 'connect laser')) + self.btn_connect_camera.setText(_translate('main_window', 'connect camera')) + self.status_controller.setText(_translate('main_window', '... status ...')) + self.btn_disconnect_all.setText(_translate('main_window', 'disconnect all')) + self.tabs.setTabText(self.tabs.indexOf(self.tab), _translate('main_window', 'controllers')) + self.status_printer.setText(_translate('main_window', '... printer status ...')) + self.status_fps_text.setText(_translate('main_window', 'FPS:')) + self.status_fps_number.setText(_translate('main_window', '#####')) + ############################ + self.lineedit_gamma.setText(str(self.gamma)) + self.lineedit_xincrement.setText(str(c.p_xincrement)) + self.lineedit_yincrement.setText(str(c.p_yincrement)) + self.lineedit_zincrement.setText(str(c.p_zincrement)) + self.lineedit_xvelocity.setText(str(c.p_vel_global)) + self.lineedit_yvelocity.setText(str(c.p_vel_global)) + self.lineedit_zvelocity.setText(str(c.p_zvel_global)) + self.lineedit_set_laser_current.setText('---') + self.lineedit_custom_serial_send.setText('---') + self.lineedit_set_zcent.setText(str(c.p_z_cent)) + self.lineedit_target_FPS.setText('1.0') + + return + + def actions(self): + self.btn_change_directory.clicked.connect(self.change_directory) + self.btn_snap.clicked.connect(c_camera.take_image) + self.combobox_colourmap.currentIndexChanged.connect(c_graphic_display.update_image) + self.cbox_normalise.stateChanged.connect(c_graphic_display.update_image) + self.cbox_live.stateChanged.connect(c_camera.camera_live) + self.lineedit_target_FPS.returnPressed.connect(c_camera.camera_live) + self.lineedit_exposure.returnPressed.connect(c_camera.update_exposure_time) + self.lineedit_analog_gain.returnPressed.connect(c_camera.update_gain) + self.cbox_show_scalebar.stateChanged.connect(c_graphic_display.update_image) + self.cbox_saturated.stateChanged.connect(c_graphic_display.update_image) + self.slider_gamma.valueChanged.connect(lambda: self.update_gamma(1)) + self.lineedit_gamma.returnPressed.connect(lambda: self.update_gamma(0)) + self.btn_pos_x.clicked.connect(partial(c.uimove, float(1), float(0), float(0))) + self.btn_pos_y.clicked.connect(partial(c.uimove, float(0), float(1), float(0))) + self.btn_pos_z.clicked.connect(partial(c.uimove, float(0), float(0), float(-1))) + self.btn_neg_x.clicked.connect(partial(c.uimove, float(-1), float(0), float(0))) + self.btn_neg_y.clicked.connect(partial(c.uimove, float(0), float(-1), float(0))) + self.btn_neg_z.clicked.connect(partial(c.uimove, float(0), float(0), float(1))) + self.btn_load_gcode.clicked.connect(c.read_gcode) + self.btn_set_zcent.clicked.connect(self.update_zcent) + self.lineedit_set_zcent.returnPressed.connect(self.update_zcent) + self.combobox_binning.currentIndexChanged.connect(c_camera.update_binning) + self.btn_connect_laser.clicked.connect(c.connect_laser) + self.btn_laser_on.clicked.connect(partial(c.laser_on, 100)) + self.btn_laser_off.clicked.connect(partial(c.laser_on, 0)) + self.lineedit_set_laser_current.returnPressed.connect(c.laser_set) + self.btn_set_laser_current.clicked.connect(c.laser_set) + self.btn_save.clicked.connect(self.save_image) + self.cbox_exposure.stateChanged.connect(c_camera.toggle_auto_exposure) + self.cbox_analog_gain.stateChanged.connect(c_camera.toggle_auto_gain) + self.btn_connect_camera.clicked.connect(c_camera.connect_camera) + self.btn_disconnect_all.clicked.connect(self.disconnect_all) + self.btn_connect_all.clicked.connect(self.connect_all) + self.btn_connect_x.clicked.connect(c.connect_xy) + self.btn_connect_y.clicked.connect(c.connect_xy) + self.btn_connect_z.clicked.connect(c.connect_z) + self.lineedit_xincrement.returnPressed.connect(c.position_velocity_toggled) + self.lineedit_yincrement.returnPressed.connect(c.position_velocity_toggled) + self.lineedit_zincrement.returnPressed.connect(c.position_velocity_toggled) + self.lineedit_xvelocity.returnPressed.connect(c.position_velocity_toggled) + self.lineedit_yvelocity.returnPressed.connect(c.position_velocity_toggled) + self.lineedit_zvelocity.returnPressed.connect(c.position_velocity_toggled) + self.btn_print.clicked.connect(self.start_printing) + self.lineedit_custom_serial_send.returnPressed.connect(c.custom_serial_send) + self.btn_custom_serial_send.clicked.connect(c.custom_serial_send) + self.btn_ROI_in.clicked.connect(partial(self.zoom, 1)) + self.btn_ROI_out.clicked.connect(partial(self.zoom, 0)) + self.btn_ROI_home.clicked.connect(partial(self.zoom, 2)) + return + + def connect_all(self): + print('connecting all') + try: + if c_main_window.btn_connect_z.text() == 'connect z': + c.connect_z() + except: + print('no z stage connection') + try: + if c_main_window.btn_connect_x.text() == 'connect x': + c.connect_xy() + except: + print('no xy stage connection') + try: + if c_main_window.btn_connect_laser.text() == 'connect laser': + c.connect_laser() + except: + print('no laser connection') + try: + if c_main_window.btn_connect_camera.text() == 'connect camera': + c_camera.connect_camera() + except: + print('no camera connection') + + return + + def disconnect_all(self): + print('disconnecting all') + if c_main_window.btn_connect_z.text() == 'disconnect z': + c.connect_z() + if c_main_window.btn_connect_x.text() == 'disconnect x': + c.connect_xy() + if c_main_window.btn_connect_laser.text() == 'disconnect laser': + c.connect_laser() + if c_main_window.btn_connect_camera.text() == 'disconnect camera': + c_camera.connect_camera() + return + + def update_zcent(self): + print('zcent changed function') + c.p_z_cent = np.array(self.lineedit_set_zcent.text(),dtype='float64') + if c_main_window.btn_load_gcode.text() == 'unload g.code': + c.read_gcode() + self.lineedit_set_zcent.setText(str(c.p_z_cent)) + self.status_printer.setText('z-cent updated') + return + + def change_directory(self): + print('change directory function') + if c_main_window.btn_load_gcode.text() == 'unload g.code': + c.read_gcode() + + _filenames = [] + self.pathname = str(QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Directory')) + print(self.pathname) + try: + c_main_window.list_current_directory.clear() + self.filenames = os.listdir(self.pathname) + contains_gcode = 0 + for i in range(len(self.filenames)): + item = self.filenames[i] + if '.gcode' in item: + self.list_current_directory.addItem(item) + contains_gcode = 1 + _filenames.append(item) + + if contains_gcode: + self.status_current_directory.setText(self.pathname) + else: + self.status_current_directory.setText('no *.gcode files here!') + now = datetime.datetime.now().time() + if np.mod(now.second,2) == 0: + self.status_current_directory.setStyleSheet('color:#FF0000;') + else: + self.status_current_directory.setStyleSheet('color:#FFFF00;') + + c_main_window.list_current_directory.setCurrentRow(0) + os.chdir(self.pathname) + self.status_printer.setText('directory changed') + except: + c_main_window.list_current_directory.clear() + self.status_current_directory.setText('no directory selected!') + now = datetime.datetime.now().time() + if np.mod(now.second,2) == 0: + self.status_current_directory.setStyleSheet('color:#FF0000;') + else: + self.status_current_directory.setStyleSheet('color:#FFFF00;') + self.status_printer.setText('directory not changed') + + self.filenames = _filenames + return + + def status_text_update_image(self): + print('update image text status') + + image_status_text = ''.join([ + 'image size: ',str(c_camera.img_raw.shape), + ', colourmap: ',self.combobox_colourmap.currentText() + ]) + self.status_image.setText(image_status_text) + app.processEvents() + return + + def update_gamma(self,_input): + print('update gamma func') + if _input: + self.gamma = c_main_window.slider_gamma.value()/100 # read from the slider + c_main_window.lineedit_gamma.setText(str(self.gamma)) # update text + c_graphic_display.update_image() + + if not _input: + self.gamma = float(c_main_window.lineedit_gamma.text()) # read from the text input + c_main_window.slider_gamma.setSliderPosition(self.gamma*100) # update slider + c_graphic_display.update_image() + return + + def repaint_image(self): + print('repainting image function') + + # if c_main_window.combobox_colourmap.currentIndex() == 4: ## raw image + # if self.zoom_factor > 1: + # print(self.zoom_factor) + # r1 = c_camera.img_raw.shape[0] + # c1 = c_camera.img_raw.shape[1] + # r2 = int(np.round(r1/self.zoom_factor)) + # c2 = int(np.round(c1/self.zoom_factor)) + # # c_camera.img_raw = c_camera.img_raw[int((r1-r2)/2):int((r1-r2)/2)+r2, int((c1-c2)/2):int((c1-c2)/2)+c2] + # c_graphic_display.img = c_camera.img_raw[int((r1-r2)/2):int((r1-r2)/2)+r2, int((c1-c2)/2):int((c1-c2)/2)+c2] + # else: + # c_graphic_display.img = c_camera.img_raw + # print(c_graphic_display.img.shape[1], c_graphic_display.img.shape[0]) + # qimage = QtGui.QImage(c_graphic_display.img.tobytes(), c_graphic_display.img.shape[1], c_graphic_display.img.shape[0], QtGui.QImage.Format_Indexed8) + # print(qimage.size()) + # else: + c_graphic_display.img = c_graphic_display.img_fin + qimage = QtGui.QImage(c_graphic_display.img.tobytes(), c_graphic_display.img.shape[1], c_graphic_display.img.shape[0], QtGui.QImage.Format_RGBA8888) + + pixmap_image = QtGui.QPixmap(qimage) + c_main_window.image_camera.setPixmap(pixmap_image) + print("repainting with maximum %d threads" % self.threadpool.maxThreadCount()) + return + + def save_image(self): + print('saving image function') + path = (QFileDialog.getSaveFileName(self, 'Save file', '', 'PNG (*.png))|*.png')) + if path: + mpl.image.imsave(path[0], c_graphic_display.img) + c_main_window.status_printer.setText('Saved to: '+path[0]) + return + + def zoom(self,zin): + if zin == 1: # zoom in + self.zoom_factor = self.zoom_factor*1.2 + elif zin == 0: # zoom out + self.zoom_factor = self.zoom_factor/1.2 + elif zin == 2: # zoom home + self.zoom_factor = 1 + if self.zoom_factor < 1: + self.zoom_factor = 1 + self.status_printer.setText('can''t zoom < x1!') + if self.zoom_factor > 10: + self.zoom_factor = 10 + self.status_printer.setText('can''t zoom > x10!') + c_graphic_display.update_image() + return + + def update_hist(self): + print('update histogram function') + # t = time.time() + h1 = (c_camera.img_raw.flatten()*(80/256)) + h1 = np.unique(h1.astype(np.uint8), return_counts=True) + x1 = np.arange(80) + y1 = np.zeros(80) + for i in np.unique(h1[0]): + y1[i] = h1[1][np.where(h1[0] == i)] + + if self.hist_flag1 == None or self.hist_flag2 == None: + self.hist_flag1 = 1 + self.hist_flag2 = 1 + y2 = np.zeros(80) + else: + if self.cbox_normalise.isChecked() or self.gamma != 1: + h2 = (c_graphic_display.img_norm.flatten()*(80/256)) + h2 = np.unique(np.asarray(h2, dtype=np.uint8), return_counts=True) + y2 = np.zeros(80) + for i in np.unique(h2[0]): + y2[i] = h2[1][np.where(h2[0] == i)] + else: + y2 = np.zeros(80) + + x11 = x1[y1 != 0] + y1 = y1[y1 != 0] + x12 = x1[y2 != 0] + y2 = y2[y2 != 0] + self.hist.plot(x11*(256/80),y1, pen = ('b'), linewidth=2, alpha=0.5, clear = True) + # self.histogram.setData(x1*(256/80),y1, pen = ('k'), linewidth=2) + self.hist.plot(x12*(256/80),y2, pen = ('r'), linewidth=2, alpha=0.5) + # self.histogram.setData(x1*(256/80),y2) + # self.axes.axis(ymin=1,ymax=2.5*np.amax(np.hstack([y1,y2]),0)) + + def print_output(self, s): + print(s) + + def print_complete(self): + print("Printing Complete") + + def progress_fn(self, n): + print("%d%% done" % n) + + def start_printing(self): + QtGui.QApplication.processEvents() + # Pass the function to execute + worker = pt.Worker(self.printing) # Any other args, kwargs are passed to the run function + worker.signals.result.connect(self.print_output) + worker.signals.finished.connect(self.print_complete) + worker.signals.progress.connect(self.progress_fn) + # Execute + self.threadpool.start(worker) + print("Active thread count: " + str(self.threadpool.activeThreadCount())) + + def printing(self, progress_callback): + #progress + for n in range (0, 5): + # time.sleep(1) + progress_callback.emit(int(n * 100 / 4)) + + if c_main_window.btn_load_gcode.text() == 'unload g.code': + if self.btn_connect_z.text() == 'disconnect z' and self.btn_connect_x.text() == 'disconnect x' and self.btn_connect_laser.text() == 'disconnect laser': + + c.xy.sendPN('MOV 1 0 2 0') + laser_off_str = 'slc 0' + chr(13) + laser_off_str = str.encode(laser_off_str) + laser_off_str = bytes(laser_off_str) + c.l.writePN(laser_off_str) + + # add dialog here are you ready to print?! + + linear_tolerance = 0.05 # 50 um (in mm), + # ~ for 1.67 mm/sec total velocity * 0.03, + # (float(xvel)**2+float(yvel))**2)**0.5) * 0.03 + laser_current_old = np.array(0,dtype='float64') + xpos_old = 0 + ypos_old = 0 + zpos_old = 0 + xvel_old = 0 + yvel_old = 0 + zvel_old = 0 + + c.time_laser = [] + c.time_xymov = [] + c.time_xyisready = [] + c.time_xyismoving = [] + c.time_line = [] + c.time_vector = [] + c.c_xpos_vector = [] + c.c_ypos_vector = [] + + # printing from gcode starts here + c.print_start_time = time.time() + while len(c.g) > 0: + current_line = c.g.loc[0] ## get the first row + self.list_current_directory.takeItem(0) # remove and get the first row + c.g = c.g[1:] ## update the dataframe + c.g = c.g.reset_index(drop=True) + # for i in range(len(c.g)): + # + # # time_line_start = time.time() + # current_line = c.g.loc[i] + print('DB@ '+ str(current_line['gcode'])) + print(current_line) + wait_time = current_line['wait'] + laser_current = current_line['laser'] + + if wait_time != 0: # if its not a wait time + time.sleep(wait_time) + elif laser_current != laser_current_old: # and its not a laser power change + # s = time.time() + laser = 'slc ' + str(laser_current) + chr(13) + laser = str.encode(laser) + laser = bytes(laser) + c.l.writePN(laser) + # c.time_laser.append(time.time()-s) + laser_current_old = laser_current + else: # its a velocity or position change + xpos = current_line['xpos'] + ypos = current_line['ypos'] + zpos = current_line['zpos'] + xvel = current_line['xvel'] + yvel = current_line['yvel'] + zvel = current_line['zvel'] + + if zvel_old != zvel: + # gcs.send(c.z,'VEL 1 ' + zvel) + c.z.sendPN('VEL 1 ' + zvel) + ready = False + while ready != True: + # ready = gcs.read(c.z, chr(7)) + ready = c.z.readPN(chr(7)) + if 177 == ord(ready.strip()): + ready = True + elif 176 == ord(ready.strip()): + ready = False + zvel_old = zvel + + if zpos_old != zpos: + # gcs.send(c.z, 'MOV 1 ' + zpos) + c.z.sendPN('MOV 1 ' + zpos) + moving = 1 + while moving > 0: + # moving = int(gcs.read(c.z, chr(5))) + moving = int(c.z.readPN(chr(5))) + self.update_position_graph() + zpos_old = zpos + + if xvel_old != xvel or yvel_old != yvel: + # gcs.send(c.xy, 'VEL 1 ' + xvel + ' 2 ' + yvel) + c.xy.sendPN('VEL 1 ' + xvel + ' 2 ' + yvel) + ready = False + while ready != True: + # ready = gcs.read(c.xy, chr(7)) + ready = c.xy.readPN(chr(7)) + if 177 == ord(ready.strip()): + ready = True + elif 176 == ord(ready.strip()): + ready = False + xvel_old = xvel + yvel_old = yvel + if (((float(xvel)**2+float(yvel))**2)**0.5) >= c.p_max_vel_global: + if float(xvel) == c.p_max_vel_global: + linear_tolerance = float(yvel)*0.03 + elif float(yvel) == c.p_max_vel_global: + linear_tolerance = float(xvel)*0.03 + else: + linear_tolerance = (((float(xvel)**2+float(yvel))**2)**0.5)*0.025 + + if xpos_old != xpos or ypos_old != ypos: + # s = time.time() + # gcs.send(c.xy, 'MOV 1 ' + xpos + ' 2 ' + ypos) + c.xy.sendPN('MOV 1 ' + xpos + ' 2 ' + ypos) + # c.time_xymov.append(time.time()-s) + ready = False + while ready != True: + # s = time.time() + # ready = gcs.read(c.xy, chr(7)) + ready = c.xy.readPN(chr(7)) + if 177 == ord(ready.strip()): + ready = True + elif 176 == ord(ready.strip()): + ready = False + else: + print('probably an error has occured') + # c.time_xyisready.append(time.time()-s) + moving = 1 + xpos = np.array(xpos,dtype='float64') + ypos = np.array(ypos,dtype='float64') + while moving > 0: + # s = time.time() + # pos = gcs.read(c.xy,'POS? 1 2') + pos = c.xy.readPN('POS? 1 2') + pos = pos.split('\n') + pos0 = pos[0] + pos0 = np.array(pos0[2:],dtype='float64') + pos1 = pos[1] + pos1 = np.array(pos1[2:],dtype='float64') + + c.time_vector.append(time.time()) + c.c_xpos_vector.append(pos0) + c.c_ypos_vector.append(pos1) + + pos_diff = np.abs(xpos-pos0) + np.abs(ypos-pos1) + if pos_diff < linear_tolerance: + moving = 0 + + self.update_position_graph() + # c.time_xyismoving.append(time.time()-s) + + xpos_old = str(xpos) + ypos_old = str(ypos) + + # c.time_line.append(time.time()-time_line_start) + + c.print_end_time = time.time() + print('printing took: ' + str(np.round((c.print_end_time-c.print_start_time)/60,8)) + ' mins') + c.flush_laser + c.l.write(laser_off_str) + c.xy.sendPN('MOV 1 0 2 0') # move to center + # dialog print finished + + else: + c_main_window.status_printer.setText('connect to the stages and laser') + else: + c_main_window.status_printer.setText('load in a *.gcode file!') + + # np.savetxt("c.time_laser.csv", c.time_laser, delimiter=",") + # np.savetxt("c.time_xymov.csv", c.time_xymov, delimiter=",") + # np.savetxt("c.time_xyisready.csv", c.time_xyisready, delimiter=",") + # np.savetxt("c.time_xyismoving.csv", c.time_xyismoving, delimiter=",") + # np.savetxt("c.time_line.csv", c.time_line, delimiter=",") + + # plt.hist(c.time_xyismoving) + + # plt.figure(0) + # plt.plot(c.time_vector, np.array(c.c_xpos_vector)-np.array(c.c_ypos_vector)) + # plt.plot(c.time_vector, np.array(c.c_xpos_vector)) + # plt.plot(c.time_vector, np.array(c.c_ypos_vector)) + # plt.show() + + # plt.figure(0) + # sp1 = plt.subplot(221) + # plt.hist(c.time_laser) + # sp1.set_ylabel('laser') + + # sp2 = plt.subplot(222) + # plt.hist(c.time_xymov) + # sp2.set_ylabel('move') + + # sp3 = plt.subplot(223) + # plt.hist(c.time_xyisready) + # sp3.set_ylabel('isready') + + # sp4 = plt.subplot(224) + # plt.hist(c.time_xyismoving) + # sp4.set_ylabel('ismoving') + + # something to update the GUI pos display here + playsound('C:/Users/sunyi/Desktop/app/flow_printer_app/water-droplet-2.mp3') + return "Done" + + def update_position_graph(self): + ## update position pyqtgraphs + c.p_zpos_current = c.z.qPOS(1) + c.p_zpos_current = c.p_zpos_current[1] + pos = c.xy.qPOS([1,2]) + c.p_xpos_current = pos[1] + c.p_ypos_current = pos[2] + self.label_status_x_num.setText(str(np.round(c.p_xpos_current,6))) + self.label_status_y_num.setText(str(np.round(c.p_ypos_current,6))) + self.label_status_z_num.setText(str(np.round(c.p_zpos_current,6))) + + # c_main_window.image_printer_position_xy.update_plot() + self.xy_graph_plot.setData([c.p_xpos_current], [c.p_ypos_current]) + self.xz_graph_plot.setData([c.p_xpos_current], [c.p_z_cent-c.p_zpos_current]) + print("x = " + str(c.p_xpos_current)) + print("y = " + str(c.p_ypos_current)) + print("z = " + str(c.p_z_cent-c.p_zpos_current)) + + +class camera(): + def __init__(self): + self.fps = 1 + self.timer_value = 1 + try: + # initialize settings + self.cam = Camera() # Acquire Camera + self.cam.init() # Initialize camera + self.cam.PixelFormat = 'Mono8' + self.cam.GammaEnable = False + self.cam.GainAuto = 'Off' + self.cam.AcquisitionMode = 'SingleFrame' + self.cam.AcquisitionFrameRateEnable = False + self.cam.AcquisitionFrameCount = 2 + self.cam.BinningHorizontal = 4 + self.cam.BinningVertical = 4 + self.cam.ExposureAuto = 'Off' + self.max_gain = self.cam.get_info('Gain')['max'] + self.min_gain = self.cam.get_info('Gain')['min'] + self.max_exposure = self.cam.get_info('ExposureTime')['max'] + self.min_exposure = self.cam.get_info('ExposureTime')['min'] + self.cam.Gain = max(min(10, self.max_gain), self.min_gain) + self.cam.ExposureTime = max(min(100, self.max_exposure), self.min_exposure) # microseconds + self.cam.start() # Start recording + self.img_raw = self.cam.get_array() # Get frames + self.cam.stop() # Stop recording + except: + if not hasattr(self, 'img_raw'): + print('camera init failed...') + self.img_raw = np.random.rand(100,100)*255 + self.img_raw = self.img_raw.astype(np.uint8) + self.cam = [] + self.live_timer = QTimer() + self.live_timer.timeout.connect(self.take_image) + return + + def connect_camera(self): + print('connect camera toggled') + if c_main_window.btn_connect_camera.text() == 'disconnect camera': + print('disconnecting camera') + try: + c_camera.cam.stop() # Stop recording if camera caught on + except: + 0 + + if c_main_window.cbox_live.isChecked(): + c_camera.live_timer.stop() + try: + c_camera.cam.close() + time.sleep(0.2) + except: + print('couldn''t close/clean up camera') + c_camera.cam = 0 + c_main_window.btn_connect_camera.setText('connect camera') + else: + print('connecting camera') + self.cam = Camera() # Acquire Camera + self.cam.init() # Initialize camera + self.cam.PixelFormat = 'Mono8' + self.cam.GammaEnable = False + self.cam.GainAuto = 'Off' + self.cam.AcquisitionMode = 'SingleFrame' + self.cam.AcquisitionFrameRateEnable = False + self.cam.AcquisitionFrameCount = 2 + self.cam.BinningHorizontal = 4 + self.cam.BinningVertical = 4 + self.cam.ExposureAuto = 'Off' + self.max_gain = self.cam.get_info('Gain')['max'] + self.min_gain = self.cam.get_info('Gain')['min'] + self.max_exposure = self.cam.get_info('ExposureTime')['max'] + self.min_exposure = self.cam.get_info('ExposureTime')['min'] + self.cam.Gain = max(min(2, self.max_gain), self.min_gain) + self.cam.ExposureTime = max(min(50, self.max_exposure), self.min_exposure) # microseconds + self.cam.start() # Start recording + self.img_raw = self.cam.get_array() # Get frames + self.cam.stop() # Stop recording + self.update_binning() + self.update_exposure_time() + self.update_gain() + c_main_window.btn_connect_camera.setText('disconnect camera') + return + + def toggle_auto_exposure(self): + if c_main_window.cbox_exposure.isChecked(): + self.cam.ExposureAuto = 'Continuous' + c_main_window.lineedit_exposure.setText('--') + else: + self.cam.ExposureAuto = 'Off' + c_main_window.lineedit_exposure.setText(str(np.round(self.cam.get_info('ExposureTime')['value'],2))+' ms') + self.update_exposure_time() + c_main_window.status_printer.setText('exposure time toggled') + return + + def toggle_auto_gain(self): + if c_main_window.cbox_analog_gain.isChecked(): + self.cam.GainAuto = 'Continuous' + c_main_window.lineedit_analog_gain.setText('--') + else: + c_camera.cam.GainAuto = 'Off' + c_main_window.lineedit_analog_gain.setText(str(np.round(self.cam.get_info('Gain')['value'],2))+' dB') + self.update_gain() + c_main_window.status_printer.setText('auto gain toggled') + return + + def update_binning(self): + print('update camera binning function') + try: + if c_main_window.combobox_binning.currentIndex() == 0: + self.cam.BinningHorizontal = 4 + self.cam.BinningVertical = 4 + if c_main_window.combobox_binning.currentIndex() == 1: + self.cam.BinningHorizontal = 2 + self.cam.BinningVertical = 2 + if c_main_window.combobox_binning.currentIndex() == 2: + self.cam.BinningHorizontal = 1 + self.cam.BinningVertical = 1 + c_main_window.status_printer.setText('binning updated') + except: + print('binning not set....') + return + + def update_exposure_time(self): + print('updating exposure time') + try: + user_exp = np.round(np.array(re.sub('[^\d\.]', '', c_main_window.lineedit_exposure.text()),dtype='float64'),0) + self.cam.ExposureTime = max(min(user_exp, self.max_exposure), self.min_exposure) + c_main_window.lineedit_exposure.setText(str(np.round(c_camera.cam.get_info('ExposureTime')['value'],2))+' ms') + c_main_window.status_printer.setText('exposure time updated') + except: + print('exposure time not set....') + return + + def update_gain(self): + print('updating gain') + try: + user_gain = np.round(np.array(re.sub('[^\d\.]', '', c_main_window.lineedit_analog_gain.text()),dtype='float64'),1) + self.cam.Gain = max(min(user_gain, self.max_gain), self.min_gain) + c_main_window.lineedit_analog_gain.setText(str(np.round(self.cam.get_info('Gain')['value'],2))+' dB') + c_main_window.status_printer.setText('gain updated') + except: + print('gain not set....') + return + + def camera_live(self): + print('camera live toggled') + self.fps = np.array(re.sub('[^\d\.]', '', c_main_window.lineedit_target_FPS.text()),dtype='float64') + c_main_window.lineedit_target_FPS.setText(str(self.fps)+' Hz') + if c_main_window.cbox_live.isChecked(): + self.live_timer.stop() + self.timer_value = (1/self.fps)*1000 + self.live_timer.start(self.timer_value) + c_main_window.status_printer.setText('camera live') + else: + self.live_timer.stop() + c_main_window.status_printer.setText('camera stopped') + return + + def take_image(self): + print('take image function') + c_main_window.take_image_start_time = time.time() + c_main_window.status_capture.setText('capturing image...') + + try: + print('taking image') + c_camera.cam.start() # Start recording + c_camera.img_raw = c_camera.cam.get_array() # Get frames + c_camera.cam.stop() # Stop recording + except: + print('generate random noise') + self.img_raw = np.random.rand(100,100)*255 + self.img_raw = self.img_raw.astype(np.uint8) + + print('img time taken: '+str(time.time()-c_main_window.take_image_start_time)) + c_main_window.status_capture.setText('displaying image...') + + + c_graphic_display.update_image() + #c_graphic_display.fps_counter = np.append(c_graphic_display.fps_counter,time.time()) + #c_graphic_display.fps_counter = np.delete(c_graphic_display.fps_counter, 0) + #c_main_window.status_fps_number.setText(str(np.round(1/np.mean(np.diff(c_graphic_display.fps_counter)),3))) + print('current saved value for fps is: ' + str(self.fps) + ' current timer value is: ' + str(self.timer_value)) + + +class printer(): + + def __init__(self): + # controller variables + self.xy = 0 + self.z = 0 + self.l = 0 + # printer velocity variables + self.p_vel_global = (10) # maybe change after extensive PID calibrations + self.p_zvel_global = float(1) + self.p_max_vel_global = float(10) + self.p_max_zvel_global = float(1) + # printer position variables + self.p_xpos_current = float(0) + self.p_ypos_current = float(0) + self.p_zpos_current = np.array(25, dtype ='float64') + self.p_xpos_target = self.p_xpos_current + self.p_ypos_target = self.p_ypos_current + self.p_zpos_target = self.p_zpos_current + # printer velocity variables + self.p_xvel_current = self.p_vel_global + self.p_yvel_current = self.p_vel_global + self.p_zvel_current =self.p_zvel_global + self.p_xvel_target = self.p_xvel_current + self.p_yvel_target = self.p_yvel_current + self.p_zvel_target = self.p_zvel_current + + # DB PN - 20201112 + self.laser_current_max_power = 137.8 + + # printer increment vars + self.p_xincrement = float(1) + self.p_yincrement = float(1) + self.p_zincrement = float(0.25) + # printer other variables + self.p_z_cent = float(25) + # gcode variables + self.G = 0 + self.g = 0 + return + + def read_gcode(self): + print('reading gcode') + self.g = [0] + self.g_size = [0] + if c_main_window.btn_load_gcode.text() == 'unload g.code': # toggle gcode obs and directory + print('unload gcode') + c_main_window.list_current_directory.clear() + _filenames = [] + for i in range(len(c_main_window.filenames)): + item = c_main_window.filenames[i] + if '.gcode' in item: + c_main_window.list_current_directory.addItem(item) + print('item' + item) + _filenames.append(item) + c_main_window.list_current_directory.setCurrentRow(0) + os.chdir(c_main_window.pathname) + c_main_window.filenames = _filenames + c_main_window.btn_load_gcode.setText('load g.code') + c_main_window.btn_load_gcode.setStyleSheet('color:#000000;') + c_main_window.btn_print.setStyleSheet('color:#808080') + else: + print('loading gcode') # toggle gcode obs and directory + if c_main_window.list_current_directory.currentRow() == -1: + c_main_window.btn_print.setStyleSheet('color:#808080') + c_main_window.status_current_directory.setText('select *.gcode file!') + now = datetime.datetime.now().time() + if np.mod(now.second,2) == 0: + c_main_window.status_current_directory.setStyleSheet('color:#FF0000;') + else: + c_main_window.status_current_directory.setStyleSheet('color:#FFFF00;') + else: + # read in the gcode file + filename = c_main_window.filenames[c_main_window.list_current_directory.currentRow()] + gcode = open(filename, 'r') + gcode = gcode.read() + gcode = gcode.split('\n') + gcode = [i.strip(' ') for i in gcode] + c_main_window.list_current_directory.clear() + for i in range(len(gcode)): + item = gcode[i] + print(type(item)) + if len(item) > 0: + c_main_window.list_current_directory.addItem(item) + # c_main_window.list_current_directory.addItem(item) + gcode = DataFrame(gcode, columns=['gcode']) + gcode = gcode.replace('', np.nan) + # gcode_blank_line_ind = np.where(pd.isnull(gcode)) + gcode_line_ind = np.where(pd.notnull(gcode)) + + # initialize variables for controller quick access + laser_state = np.array(0) + xvel = np.array(0) + yvel = np.array(0) + zvel = np.array(0) + xpos = np.array(0) + ypos = np.array(0) + zpos = np.array(0) + xpos_old = np.array(0) + ypos_old = np.array(0) + zpos_old = np.array(0) + + Gcode = [] + Laser_state = [] + Xpos = [] + Ypos = [] + Zpos = [] + Xvel = [] + Yvel = [] + Zvel = [] + Gcode_estimated_time_to_completion = [] + Wait_time = [] + + for gcode_line in gcode_line_ind[0]: + g_line = gcode.loc[gcode_line,'gcode'] + try: # remove comments in the gcode + semicolon_ind = g_line.index(';') + except: + semicolon_ind = len(g_line) + + g_line = g_line[0:semicolon_ind] + g_cell = g_line.split(' ') + gm_ind = [i for i, s in enumerate(g_cell) if 'G' in s] + if not gm_ind: + gm_ind = [i for i, s in enumerate(g_cell) if 'M' in s] + x_ind = [i for i, s in enumerate(g_cell) if 'X' in s] + y_ind = [i for i, s in enumerate(g_cell) if 'Y' in s] + z_ind = [i for i, s in enumerate(g_cell) if 'Z' in s] + f_ind = [i for i, s in enumerate(g_cell) if 'F' in s] + s_ind = [i for i, s in enumerate(g_cell) if 'S' in s] + + if gm_ind: + gm_ind = int(re.sub('[^\d\.]', '', g_cell[gm_ind[0]])) + if x_ind: + xpos = np.array(re.sub('[^\d\.]', '', g_cell[x_ind[0]]), dtype='float64') + else: + xpos = xpos_old + if y_ind: + ypos = np.array(re.sub('[^\d\.]', '', g_cell[y_ind[0]]), dtype='float64') + else: + ypos = ypos_old + if z_ind: + zpos = np.array(re.sub('[^\d\.\-]', '', g_cell[z_ind[0]]), dtype='float64') + else: + zpos = zpos_old + if f_ind: + f_ind = np.array(re.sub('[^\d\.]', '', g_cell[f_ind[0]]), dtype='float64') + else: + f_ind = np.array(0,dtype='float64') + if s_ind: + s_ind = np.array(re.sub('[^\d\.]', '', g_cell[s_ind[0]]), dtype='float64') + else: + s_ind = np.array(0,dtype='float64') + + gcode_estimated_time_to_completion = 0.001; + + if gm_ind == 1 or gm_ind == 0: + distance = np.absolute([xpos-xpos_old, ypos-ypos_old]) + total_distance = ((np.nansum(distance**2))**0.5) + if total_distance > 0: + if gm_ind == 0: + total_velocity = self.p_max_vel_global + else: + total_velocity = f_ind/60 # from mm/min -> in mm/sec + velocity = total_velocity*(distance/total_distance) + xvel = np.round(np.nanmin([velocity[0], self.p_max_vel_global]),4) + yvel = np.round(np.nanmin([velocity[1], self.p_max_vel_global]),4) + zvel = np.round(np.nanmin([f_ind/60, self.p_max_zvel_global]),4) + + gcode_estimated_time_to_completion = total_distance/total_velocity + + if not np.isnan(xpos): + xpos_old = xpos + if not np.isnan(ypos): + ypos_old = ypos + if not np.isnan(zpos): + zpos_old = zpos + if gm_ind == 4: + wait_time = s_ind/1000 # since in msec for gcode + gcode_estimated_time_to_completion = wait_time + else: + wait_time = 0 + if gm_ind == 20: + raise Exception('gcode is in inches, please convert to mm') + if gm_ind == 21: + print('gcode is in mm') + if gm_ind == 90: + print('XYZ absolute') + if gm_ind == 91: + raise Exception('coordinate system should be absolute, & control @PI_') + if gm_ind == 106: + laser_state = np.array(c.laser_current_max_power, dtype='float64')*(np.array(s_ind, dtype='float64')/np.array(100, dtype='float64')) + if gm_ind == 203: + raise Exception('M203 codes have not been implemented') + if xvel == 0: + xvel = self.p_max_vel_global + if yvel == 0: + yvel = self.p_max_vel_global + if zvel == 0: + zvel = self.p_max_zvel_global + + # the calculus to with on the PI stages + xpos = xpos - 10 + ypos = ypos - 10 + zpos = self.p_z_cent + zpos + + Gcode.append(g_line) + Laser_state.append(laser_state) + Xpos.append(xpos) + Ypos.append(ypos) + Zpos.append(zpos) + print(zpos) + Xvel.append(xvel) + Yvel.append(yvel) + Zvel.append(zvel) + Gcode_estimated_time_to_completion.append(gcode_estimated_time_to_completion) + Wait_time.append(wait_time) + + # load into dataframe ready for interpretation + gcode = DataFrame(Gcode, columns=['gcode']) + gcode['laser'] = Laser_state + gcode['xpos'] = Xpos + gcode['ypos'] = Ypos + gcode['zpos'] = Zpos + gcode['xvel'] = Xvel + gcode['yvel'] = Yvel + gcode['zvel'] = Zvel + gcode['time'] = Gcode_estimated_time_to_completion + gcode['wait'] = Wait_time + c_main_window.btn_load_gcode.setText('unload g.code') + c_main_window.btn_load_gcode.setStyleSheet('color:#C0C0C0;') + c_main_window.status_current_directory.setText('gcode loaded') + + if abs(gcode['xpos'].max()) > 10 or abs(gcode['ypos'].max()) > 10 or gcode['zpos'].max() > 29 or gcode['zpos'].min() < 21: + warnings.warn('you got some whack x, y or z values; unloading gcode') + gcode = [0] + c.read_gcode() # to unload + + # display code + self.g_size = len(gcode) + self.g = gcode + pd.options.display.width = 0 + pd.set_option('display.max_rows',len(self.g)) + pd.set_option('display.max_columns',9) + pd.set_option('max_colwidth',8) + print(self.g)# pandas dataframe + c_main_window.btn_print.setStyleSheet('color:#FF0000') + + gcode['xpos'] = gcode['xpos'].apply(str) + gcode['ypos'] = gcode['ypos'].apply(str) + gcode['zpos'] = gcode['zpos'].apply(str) + gcode['xvel'] = gcode['xvel'].apply(str) + gcode['yvel'] = gcode['yvel'].apply(str) + gcode['zvel'] = gcode['zvel'].apply(str) + + return + + def position_velocity_toggled(self): + + c.p_xvel_target = np.min([self.p_vel_global,float(re.sub('[^\d\.]', '', c_main_window.lineedit_xvelocity.text()))]) + c.p_yvel_target = np.min([self.p_vel_global,float(re.sub('[^\d\.]', '', c_main_window.lineedit_yvelocity.text()))]) + c.p_zvel_target = np.min([self.p_zvel_global,float(re.sub('[^\d\.]', '', c_main_window.lineedit_zvelocity.text()))]) + c_main_window.lineedit_xvelocity.setText(str(c.p_xvel_target)) + c_main_window.lineedit_yvelocity.setText(str(c.p_yvel_target)) + c_main_window.lineedit_zvelocity.setText(str(c.p_zvel_target)) + + c.p_xincrement = float(re.sub('[^\d\.]', '', c_main_window.lineedit_xincrement.text())) + c.p_yincrement = float(re.sub('[^\d\.]', '', c_main_window.lineedit_yincrement.text())) + c.p_zincrement = float(re.sub('[^\d\.]', '', c_main_window.lineedit_zincrement.text())) + c_main_window.lineedit_xincrement.setText(str(c.p_xincrement)) + c_main_window.lineedit_yincrement.setText(str(c.p_yincrement)) + c_main_window.lineedit_zincrement.setText(str(c.p_zincrement)) + + return + + def connect_laser(self): + c_main_window.status_printer.setText('laser connect pressed...') + if c_main_window.btn_connect_laser.text() == 'connect laser': + c_main_window.status_printer.setText('...connecting to laser...') + if not c.l: + try: + try: + c.l = serial.Serial('COM3', 115200, timeout=0.1) + except: + c.l = serial.Serial('COM7', 115200, timeout=0.1) + + c.laser_com('@cobas 0') + c.laser_com('@cob1') + c.laser_com('l1') + # c.laser_com('cp') + # c.laser_com('p 0.080') # turn on at 80 mW + # time.sleep(1) # wait for current to stabilize + c.laser_current_max_power = str(137.8) #c.laser_com('i?') + print('current at laser max power is: '+c.laser_current_max_power) + c.laser_com('ci') + laser_on_current = '0' + c.laser_com('slc '+ laser_on_current) + c_main_window.status_printer.setText('laser connected') + c_main_window.btn_connect_laser.setText('disconnect laser') + c_main_window.lineedit_set_laser_current.setText(laser_on_current+' ('+c.laser_current_max_power+')') + except: + c_main_window.status_printer.setText('couldn''t connect to laser!...') + c.l = 0 + else: + c_main_window.status_printer.setText('you might already be connected to the laser!...') + else: + try: + try: + try: + laser_on_current = '0' + c.laser_com('slc ' + laser_on_current) + c_main_window.status_printer.setText('laser off') + c_main_window.lineedit_set_laser_current.setText(laser_on_current+' ('+c.laser_current_max_power+')') + except: + 0 + c.l.close() + c.l.__del__() + c.l = 0 + c_main_window.status_printer.setText('laser disconnected') + c_main_window.btn_connect_laser.setText('connect laser') + except: + print('couldnt disconnect dont know why') + except: + c_main_window.btn_connect_laser.setText('connect laser') + return + + def laser_com(self,input_str): + c.l.write(str.encode(input_str+chr(13))) + answer = c.l.read_until(chr(13)) + answer = answer.decode('utf-8') + answer = answer.rstrip() + return answer + + def laser_on(self,pwr): + if not c.l: + c.connect_laser() + else: + laser_on_current = str(np.array(c.laser_current_max_power,dtype='float64')*((pwr)/100)) + c.laser_com('slc ' + laser_on_current) + c_main_window.status_printer.setText('go laser go!') + c_main_window.lineedit_set_laser_current.setText(laser_on_current +' (' + c.laser_current_max_power + ')') + return + + def laser_set(self): + c.connect_laser + laser_on_current = c_main_window.lineedit_set_laser_current.text() + laser_on_current = np.min([float(laser_on_current),float(c.laser_current_max_power)]) + laser_on_current = np.max([laser_on_current,0]) + laser_on_current = str(laser_on_current) + c.laser_com('slc ' + laser_on_current) + c_main_window.status_printer.setText('go laser go!') + c_main_window.lineedit_set_laser_current.setText(laser_on_current+' ('+c.laser_current_max_power+')') + return + + def laser_off(self): + c.laser_com('slc 0') + c_main_window.status_printer.setText('laser off') + c_main_window.lineedit_set_laser_current.setText('0 ('+str(c.laser_current_max_power)+')') + return + + def connect_xy(self): + if c_main_window.btn_connect_x.text() == 'connect x': + c.xy = GCSDevice('C-413') + c.xy_serialnum = '0120007086' + # pidevice.ConnectUSB(serialnum='0020550003') + c.xy.ConnectUSB(serialnum=c.xy_serialnum) + print('initialize connected stages...') + c.xy.SVO(1,True) + c.xy.SVO(2,True) + print('referncing xy stage...') + c.xy.FRF(1) + c.xy.FRF(2) + ready = 0 + while not ready: + ready = c.xy.IsControllerReady() + + c_main_window.btn_connect_x.setText('disconnect x') + c_main_window.btn_connect_y.setText('disconnect y') + print('xy referenced...') + + gcs.send(c.xy, 'CCL 1 advanced') + gcs.send(c.xy, 'SPA 1 0x06010400 '+str(self.p_vel_global)) # Profile Generator Maximum Velocity + # gcs.read(c.xy, 'SPA? 1 0x06010400') + gcs.send(c.xy, 'SPA 2 0x06010400 '+str(self.p_vel_global)) # Profile Generator Maximum Velocity + # gcs.read(c.xy, 'SPA? 2 0x06010400') + gcs.send(c.xy, 'SPA 1 0x06010000 2000') # startup default is set at 50 - Profile Generator Maximum Acceleration + # gcs.read(c.xy, 'SPA? 1 0x06010000') + gcs.send(c.xy, 'SPA 2 0x06010000 2000') # startup default is set at 50 - Profile Generator Maximum Acceleration + # gcs.read(c.xy, 'SPA? 2 0x06010000') + + # acceleration mean no zero line time total time + # 5 0.6241187 0.92 + # 10 0.48642 0.71 + # 20 0.4023073 0.59 + # 50 0.33021193 0.48 + # 100 0.30719449 0.45 + # 200 0.2920371 0.43 + # 500 0.28619 0.42 + # 1000 0.285229 0.42 + # 2000 0.28271 0.41 + # 5000 0.28632 0.42 + + else: + print('disconnecting z stage...') + c.xy.SVO(1, False) + c.xy.SVO(2, False) + c.xy.CloseConnection() + c.xy = 0 + c_main_window.btn_connect_x.setText('connect x') + c_main_window.btn_connect_y.setText('connect y') + + return + + def connect_z(self): + if c_main_window.btn_connect_z.text() == 'connect z': + c.z = GCSDevice('C-863.11') + # c.z.InterfaceSetupDlg(key='sample') + c.z.ConnectUSB('0020550003') + print('initialize connected stages...') + c.z.SVO(1,True) + c.z.VEL(1,1) + print('referncing z stage...') + c.z.FRF(1) + ready = 0 + while not ready: + ready = c.z.IsControllerReady() + c_main_window.btn_connect_z.setText('disconnect z') + else: + print('disconnecting z stage...') + c.z.SVO(1,False) + c.z.CloseConnection() + c.z = 0 + c_main_window.btn_connect_z.setText('connect z') + return + + def uimove(self,xdel,ydel,zdel): + + xdel = xdel*c.p_xincrement + ydel = ydel*c.p_yincrement + zdel = -1*zdel*c.p_zincrement # -1 because the stage moves down (-ve), but the stage is upside down + self.p_xpos_target = np.round(self.p_xpos_current,2) + xdel # rounded to the neartes 10 um + self.p_ypos_target = np.round(self.p_ypos_current,2) + ydel + self.p_zpos_target = np.round(self.p_zpos_current,2) + zdel + + if self.p_xpos_target > 10: + self.p_xpos_target = 10 + if self.p_xpos_target < -10: + self.p_xpos_target = -10 + if self.p_ypos_target > 10: + self.p_ypos_target = 10 + if self.p_ypos_target < -10: + self.p_ypos_target = -10 + + self.move() + + c.p_zpos_current = c.z.qPOS(1) + c.p_zpos_current = c.p_zpos_current[1] + pos = c.xy.qPOS([1,2]) + c.p_xpos_current = pos[1] + c.p_ypos_current = pos[2] + c_main_window.label_status_x_num.setText(str(np.round(c.p_xpos_current,6))) + c_main_window.label_status_y_num.setText(str(np.round(c.p_ypos_current,6))) + c_main_window.label_status_z_num.setText(str(np.round(c.p_zpos_current,6))) + + # c_main_window.image_printer_position_xy.update_plot() + c_main_window.xy_graph_plot.setData([c.p_xpos_current], [c.p_ypos_current]) + c_main_window.xz_graph_plot.setData([c.p_xpos_current], [c.p_z_cent-c.p_zpos_current]) + print("x = " + str(c.p_xpos_current)) + print("y = " + str(c.p_ypos_current)) + print("z = " + str(c.p_z_cent-c.p_zpos_current)) + # c_main_window.image_printer_position_xz.update_plot() + return + + def move(self): + + if not c.z == 0: + try: + c.p_zpos_current = c.z.qPOS(1) + c.p_zpos_current = c.p_zpos_current[1] + if not c.p_zvel_current == c.p_zvel_target: + c.z.VEL(1, c.p_zvel_target) + c.p_zvel_current = c.p_zvel_target + ready = 0 + while not ready: + ready = c.z.IsControllerReady() + if not c.p_zpos_current == c.p_zpos_target: + c.z.MOV(1,c.p_zpos_target) + pitools.waitontarget(c.z) + c.p_zpos_current = c.p_zpos_target + except: + print('z move failed') + else: + c.p_zpos_current = c.p_zpos_target + + if not c.xy == 0: + try: + pos = c.xy.qPOS([1,2]) + c.p_xpos_current = pos[1] + c.p_ypos_current = pos[2] + if not c.p_xvel_current == c.p_xvel_target or not c.p_yvel_current == c.p_yvel_target: + c.xy.VEL([1,2],[c.p_xvel_target,c.p_yvel_target]) + c.p_xvel_current = c.p_xvel_target + c.p_yvel_current = c.p_yvel_target + ready = 0 + while not ready: + ready = c.xy.IsControllerReady() + if not c.p_xpos_current == c.p_xpos_target or not c.p_ypos_current == c.p_ypos_target: + c.xy.MOV([1,2],[c.p_xpos_target,c.p_ypos_target]) + pitools.waitontarget(c.xy) + c.p_xpos_current = c.p_xpos_target + c.p_ypos_current = c.p_ypos_target + except: + print('xy move failed') + else: + c.p_xpos_current = c.p_xpos_target + c.p_ypos_current = c.p_ypos_target + + if self.p_xpos_target == -10 and self.p_ypos_target == -10 and self.p_zpos_target == self.p_z_cent: + print('Pete, put a laser calibration tag in here when the printer returns home, i.e. cross reference the power to the current') + + c_main_window.label_status_x_num.setText(str(np.round(c.p_xpos_current,6))) + c_main_window.label_status_y_num.setText(str(np.round(c.p_ypos_current,6))) + c_main_window.label_status_z_num.setText(str(np.round(c.p_zpos_current,6))) + + return + + # @profile + # def printing(self, progress_callback): + # #progress + # for n in range (0, 5): + # # time.sleep(1) + # progress_callback.emit(int(n * 100 / 4)) + # + # if c_main_window.btn_load_gcode.text() == 'unload g.code': + # if c_main_window.btn_connect_z.text() == 'disconnect z' and c_main_window.btn_connect_x.text() == 'disconnect x' and c_main_window.btn_connect_laser.text() == 'disconnect laser': + # + # c.xy.sendPN('MOV 1 0 2 0') + # laser_off_str = 'slc 0' + chr(13) + # laser_off_str = str.encode(laser_off_str) + # laser_off_str = bytes(laser_off_str) + # c.l.writePN(laser_off_str) + # + # # add dialog here are you ready to print?! + # + # linear_tolerance = 0.05 # 50 um (in mm), + # # ~ for 1.67 mm/sec total velocity * 0.03, + # # (float(xvel)**2+float(yvel))**2)**0.5) * 0.03 + # laser_current_old = np.array(0,dtype='float64') + # xpos_old = 0 + # ypos_old = 0 + # zpos_old = 0 + # xvel_old = 0 + # yvel_old = 0 + # zvel_old = 0 + # + # c.time_laser = [] + # c.time_xymov = [] + # c.time_xyisready = [] + # c.time_xyismoving = [] + # c.time_line = [] + # c.time_vector = [] + # c.c_xpos_vector = [] + # c.c_ypos_vector = [] + # + # # printing from gcode starts here + # c.print_start_time = time.time() + # for i in range(len(c.g)): + # + # # time_line_start = time.time() + # current_line = c.g.loc[i] + # print('DB@ '+ str(current_line['gcode'])) + # print(c.g.loc[i]) + # wait_time = current_line['wait'] + # laser_current = current_line['laser'] + # + # if wait_time != 0: # if its not a wait time + # time.sleep(wait_time) + # elif laser_current != laser_current_old: # and its not a laser power change + # # s = time.time() + # laser = 'slc ' + str(laser_current) + chr(13) + # laser = str.encode(laser) + # laser = bytes(laser) + # c.l.writePN(laser) + # # c.time_laser.append(time.time()-s) + # laser_current_old = laser_current + # else: # its a velocity or position change + # xpos = current_line['xpos'] + # ypos = current_line['ypos'] + # zpos = current_line['zpos'] + # xvel = current_line['xvel'] + # yvel = current_line['yvel'] + # zvel = current_line['zvel'] + # + # if zvel_old != zvel: + # # gcs.send(c.z,'VEL 1 ' + zvel) + # c.z.sendPN('VEL 1 ' + zvel) + # ready = False + # while ready != True: + # # ready = gcs.read(c.z, chr(7)) + # ready = c.z.readPN(chr(7)) + # if 177 == ord(ready.strip()): + # ready = True + # elif 176 == ord(ready.strip()): + # ready = False + # zvel_old = zvel + # + # if zpos_old != zpos: + # # gcs.send(c.z, 'MOV 1 ' + zpos) + # c.z.sendPN('MOV 1 ' + zpos) + # moving = 1 + # while moving > 0: + # # moving = int(gcs.read(c.z, chr(5))) + # moving = int(c.z.readPN(chr(5))) + # zpos_old = zpos + # + # if xvel_old != xvel or yvel_old != yvel: + # # gcs.send(c.xy, 'VEL 1 ' + xvel + ' 2 ' + yvel) + # c.xy.sendPN('VEL 1 ' + xvel + ' 2 ' + yvel) + # ready = False + # while ready != True: + # # ready = gcs.read(c.xy, chr(7)) + # ready = c.xy.readPN(chr(7)) + # if 177 == ord(ready.strip()): + # ready = True + # elif 176 == ord(ready.strip()): + # ready = False + # xvel_old = xvel + # yvel_old = yvel + # if (((float(xvel)**2+float(yvel))**2)**0.5) >= self.p_max_vel_global: + # if float(xvel) == self.p_max_vel_global: + # linear_tolerance = float(yvel)*0.03 + # elif float(yvel) == self.p_max_vel_global: + # linear_tolerance = float(xvel)*0.03 + # else: + # linear_tolerance = (((float(xvel)**2+float(yvel))**2)**0.5)*0.025 + # + # if xpos_old != xpos or ypos_old != ypos: + # # s = time.time() + # # gcs.send(c.xy, 'MOV 1 ' + xpos + ' 2 ' + ypos) + # c.xy.sendPN('MOV 1 ' + xpos + ' 2 ' + ypos) + # # c.time_xymov.append(time.time()-s) + # ready = False + # while ready != True: + # # s = time.time() + # # ready = gcs.read(c.xy, chr(7)) + # ready = c.xy.readPN(chr(7)) + # if 177 == ord(ready.strip()): + # ready = True + # elif 176 == ord(ready.strip()): + # ready = False + # else: + # print('probably an error has occured') + # # c.time_xyisready.append(time.time()-s) + # moving = 1 + # xpos = np.array(xpos,dtype='float64') + # ypos = np.array(ypos,dtype='float64') + # while moving > 0: + # # s = time.time() + # # pos = gcs.read(c.xy,'POS? 1 2') + # pos = c.xy.readPN('POS? 1 2') + # pos = pos.split('\n') + # pos0 = pos[0] + # pos0 = np.array(pos0[2:],dtype='float64') + # pos1 = pos[1] + # pos1 = np.array(pos1[2:],dtype='float64') + # + # c.time_vector.append(time.time()) + # c.c_xpos_vector.append(pos0) + # c.c_ypos_vector.append(pos1) + # + # pos_diff = np.abs(xpos-pos0) + np.abs(ypos-pos1) + # if pos_diff < linear_tolerance: + # moving = 0 + # # c.time_xyismoving.append(time.time()-s) + # + # xpos_old = str(xpos) + # ypos_old = str(ypos) + # + # # c.time_line.append(time.time()-time_line_start) + # + # c.print_end_time = time.time() + # print('printing took: ' + str(np.round((c.print_end_time-c.print_start_time)/60,8)) + ' mins') + # c.flush_laser + # c.l.write(laser_off_str) + # c.xy.sendPN('MOV 1 0 2 0') # move to center + # # dialog print finished + # + # else: + # c_main_window.status_printer.setText('connect to the stages and laser') + # else: + # c_main_window.status_printer.setText('load in a *.gcode file!') + # + # # np.savetxt("c.time_laser.csv", c.time_laser, delimiter=",") + # # np.savetxt("c.time_xymov.csv", c.time_xymov, delimiter=",") + # # np.savetxt("c.time_xyisready.csv", c.time_xyisready, delimiter=",") + # # np.savetxt("c.time_xyismoving.csv", c.time_xyismoving, delimiter=",") + # # np.savetxt("c.time_line.csv", c.time_line, delimiter=",") + # + # # plt.hist(c.time_xyismoving) + # + # # plt.figure(0) + # # plt.plot(c.time_vector, np.array(c.c_xpos_vector)-np.array(c.c_ypos_vector)) + # # plt.plot(c.time_vector, np.array(c.c_xpos_vector)) + # # plt.plot(c.time_vector, np.array(c.c_ypos_vector)) + # # plt.show() + # + # # plt.figure(0) + # # sp1 = plt.subplot(221) + # # plt.hist(c.time_laser) + # # sp1.set_ylabel('laser') + # + # # sp2 = plt.subplot(222) + # # plt.hist(c.time_xymov) + # # sp2.set_ylabel('move') + # + # # sp3 = plt.subplot(223) + # # plt.hist(c.time_xyisready) + # # sp3.set_ylabel('isready') + # + # # sp4 = plt.subplot(224) + # # plt.hist(c.time_xyismoving) + # # sp4.set_ylabel('ismoving') + # + # # something to update the GUI pos display here + # playsound('C:/Users/sunyi/Desktop/app/flow_printer_app/water-droplet-2.mp3') + # return "Done" + + def flush_laser(self): + ans = b'0' # flushing out the unread laser responses + while ans != b'': + ans = c.l.read() + + def custom_serial_send(self): + + action = c_main_window.lineedit_custom_serial_send.text() + action = action.split(',') + try: + stage = action[0].replace(' ','') + gcscom = action[1].strip() + if '?' in gcscom: + if 'xy' in stage: + ans = gcs.read(c.xy, gcscom) + elif 'z' in stage: + ans = gcs.read(c.z, gcscom) + else: + ans = 'should be in form "c.xy/z, GCSCOMMAND HERE" i.e. "c.xy, MOV 1 2 0 0"' + else: + if 'xy' in stage: + ans = gcs.send(c.xy, gcscom) + elif 'z' in stage: + ans = gcs.send(c.z, gcscom) + else: + ans = 'should be in form "c.xy/z, GCSCOMMAND HERE" i.e. "c.xy, MOV 1 2 0 0"' + except: + ans = 'should be in form "c.xy/z, GCSCOMMAND HERE" i.e. "c.xy, MOV 1 2 0 0"' + if ans: + c_main_window.status_printer.setText(ans) + else: + c_main_window.status_printer.setText('send to controler'+stage+', '+gcscom) + return + +# initi Qt +app = QtWidgets.QApplication(sys.argv) +# threadpool = QThreadPool() + +# startup window +c_startup = su.startup_window() +c_startup.show() +app.processEvents() + +# establish classes +c = printer() +c_graphic_display = gd.graphic_display() +c_camera = camera() +c_main_window = main_window() +c_main_window.connect_all() +c_main_window.setWindowTitle('flow printer software') +c_main_window.image_histogram.update_win(c_main_window) +c_main_window.image_histogram.update_cam(c_camera) +c_main_window.image_histogram.update_graph(c_graphic_display) +# c_main_window.image_printer_position_xy.update_win(c_main_window) +# c_main_window.image_printer_position_xz.update_win(c_main_window) +# c_main_window.image_printer_position_xy.update_printer(c) +# c_main_window.image_printer_position_xz.update_printer(c) +# c_main_window.image_printer_position_xy.update_plot() +# c_main_window.image_printer_position_xz.update_plot() +c_graphic_display.update_win(c_main_window) +c_graphic_display.update_cam(c_camera) +c_graphic_display.update_image() + + + + +print('up and running yah') +c_startup.close() + +# show app +app.setWindowIcon(QtGui.QIcon('cells.png')) +c_main_window.show() +playsound('C:/Users/sunyi/Desktop/app/flow_printer_app/water-droplet-2.mp3') + +# close events +sys.exit(app.exec_()) diff --git a/packages_for_clean_installation /serial/__init__.py b/packages_for_clean_installation /serial/__init__.py new file mode 100644 index 0000000..caa4de1 --- /dev/null +++ b/packages_for_clean_installation /serial/__init__.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# This is a wrapper module for different platform implementations +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import sys +import importlib + +from serial.serialutil import * +#~ SerialBase, SerialException, to_bytes, iterbytes + +__version__ = '3.5' + +VERSION = __version__ + +# pylint: disable=wrong-import-position +if sys.platform == 'cli': + from serial.serialcli import Serial +else: + import os + # chose an implementation, depending on os + if os.name == 'nt': # sys.platform == 'win32': + from serial.serialwin32 import Serial + elif os.name == 'posix': + from serial.serialposix import Serial, PosixPollSerial, VTIMESerial # noqa + elif os.name == 'java': + from serial.serialjava import Serial + else: + raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) + + +protocol_handler_packages = [ + 'serial.urlhandler', +] + + +def serial_for_url(url, *args, **kwargs): + """\ + Get an instance of the Serial class, depending on port/url. The port is not + opened when the keyword parameter 'do_not_open' is true, by default it + is. All other parameters are directly passed to the __init__ method when + the port is instantiated. + + The list of package names that is searched for protocol handlers is kept in + ``protocol_handler_packages``. + + e.g. we want to support a URL ``foobar://``. A module + ``my_handlers.protocol_foobar`` is provided by the user. Then + ``protocol_handler_packages.append("my_handlers")`` would extend the search + path so that ``serial_for_url("foobar://"))`` would work. + """ + # check and remove extra parameter to not confuse the Serial class + do_open = not kwargs.pop('do_not_open', False) + # the default is to use the native implementation + klass = Serial + try: + url_lowercase = url.lower() + except AttributeError: + # it's not a string, use default + pass + else: + # if it is an URL, try to import the handler module from the list of possible packages + if '://' in url_lowercase: + protocol = url_lowercase.split('://', 1)[0] + module_name = '.protocol_{}'.format(protocol) + for package_name in protocol_handler_packages: + try: + importlib.import_module(package_name) + handler_module = importlib.import_module(module_name, package_name) + except ImportError: + continue + else: + if hasattr(handler_module, 'serial_class_for_url'): + url, klass = handler_module.serial_class_for_url(url) + else: + klass = handler_module.Serial + break + else: + raise ValueError('invalid URL, protocol {!r} not known'.format(protocol)) + # instantiate and open when desired + instance = klass(None, *args, **kwargs) + instance.port = url + if do_open: + instance.open() + return instance diff --git a/packages_for_clean_installation /serial/__init__.pyc b/packages_for_clean_installation /serial/__init__.pyc new file mode 100644 index 0000000..7f1122a Binary files /dev/null and b/packages_for_clean_installation /serial/__init__.pyc differ diff --git a/packages_for_clean_installation /serial/__main__.py b/packages_for_clean_installation /serial/__main__.py new file mode 100644 index 0000000..bd0a2e6 --- /dev/null +++ b/packages_for_clean_installation /serial/__main__.py @@ -0,0 +1,3 @@ +from .tools import miniterm + +miniterm.main() diff --git a/packages_for_clean_installation /serial/__pycache__/__init__.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..cd5084c Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/__init__.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/__main__.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000..b6a7b8c Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/__main__.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/aio.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/aio.cpython-38.pyc new file mode 100644 index 0000000..20b1aee Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/aio.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/rfc2217.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/rfc2217.cpython-38.pyc new file mode 100644 index 0000000..2399044 Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/rfc2217.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/rs485.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/rs485.cpython-38.pyc new file mode 100644 index 0000000..9ee59ce Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/rs485.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/serialcli.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/serialcli.cpython-38.pyc new file mode 100644 index 0000000..f50c6c3 Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/serialcli.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/serialjava.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/serialjava.cpython-38.pyc new file mode 100644 index 0000000..67128d3 Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/serialjava.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/serialposix.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/serialposix.cpython-38.pyc new file mode 100644 index 0000000..762344b Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/serialposix.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/serialutil.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/serialutil.cpython-38.pyc new file mode 100644 index 0000000..c4df69c Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/serialutil.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/serialwin32.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/serialwin32.cpython-38.pyc new file mode 100644 index 0000000..f197d44 Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/serialwin32.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/__pycache__/win32.cpython-38.pyc b/packages_for_clean_installation /serial/__pycache__/win32.cpython-38.pyc new file mode 100644 index 0000000..2a17911 Binary files /dev/null and b/packages_for_clean_installation /serial/__pycache__/win32.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/aio.py b/packages_for_clean_installation /serial/aio.py new file mode 100644 index 0000000..257c47c --- /dev/null +++ b/packages_for_clean_installation /serial/aio.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# module for serial IO for POSIX compatible systems, like Linux +# see __init__.py +# +# (C) 2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +"""\ +Support asyncio with serial ports. EXPERIMENTAL + +Posix platforms only, Python 3.4+ only. + +Windows event loops can not wait for serial ports with the current +implementation. It should be possible to get that working though. +""" +import asyncio +import serial +import logger + + +class SerialTransport(asyncio.Transport): + def __init__(self, loop, protocol, serial_instance): + self._loop = loop + self._protocol = protocol + self.serial = serial_instance + self._closing = False + self._paused = False + # XXX how to support url handlers too + self.serial.timeout = 0 + self.serial.nonblocking() + loop.call_soon(protocol.connection_made, self) + # only start reading when connection_made() has been called + loop.call_soon(loop.add_reader, self.serial.fd, self._read_ready) + + def __repr__(self): + return '{self.__class__.__name__}({self._loop}, {self._protocol}, {self.serial})'.format(self=self) + + def close(self): + if self._closing: + return + self._closing = True + self._loop.remove_reader(self.serial.fd) + self.serial.close() + self._loop.call_soon(self._protocol.connection_lost, None) + + def _read_ready(self): + data = self.serial.read(1024) + if data: + self._protocol.data_received(data) + + def write(self, data): + self.serial.write(data) + + def can_write_eof(self): + return False + + def pause_reading(self): + if self._closing: + raise RuntimeError('Cannot pause_reading() when closing') + if self._paused: + raise RuntimeError('Already paused') + self._paused = True + self._loop.remove_reader(self._sock_fd) + if self._loop.get_debug(): + logger.debug("%r pauses reading", self) + + def resume_reading(self): + if not self._paused: + raise RuntimeError('Not paused') + self._paused = False + if self._closing: + return + self._loop.add_reader(self._sock_fd, self._read_ready) + if self._loop.get_debug(): + logger.debug("%r resumes reading", self) + + #~ def set_write_buffer_limits(self, high=None, low=None): + #~ def get_write_buffer_size(self): + #~ def writelines(self, list_of_data): + #~ def write_eof(self): + #~ def abort(self): + + +@asyncio.coroutine +def create_serial_connection(loop, protocol_factory, *args, **kwargs): + ser = serial.Serial(*args, **kwargs) + protocol = protocol_factory() + transport = SerialTransport(loop, protocol, ser) + return (transport, protocol) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + class Output(asyncio.Protocol): + def connection_made(self, transport): + self.transport = transport + print('port opened', transport) + transport.serial.rts = False + transport.write(b'hello world\n') + + def data_received(self, data): + print('data received', repr(data)) + self.transport.close() + + def connection_lost(self, exc): + print('port closed') + asyncio.get_event_loop().stop() + + loop = asyncio.get_event_loop() + coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200) + loop.run_until_complete(coro) + loop.run_forever() + loop.close() diff --git a/packages_for_clean_installation /serial/aio.pyc b/packages_for_clean_installation /serial/aio.pyc new file mode 100644 index 0000000..3e72025 Binary files /dev/null and b/packages_for_clean_installation /serial/aio.pyc differ diff --git a/packages_for_clean_installation /serial/rfc2217.py b/packages_for_clean_installation /serial/rfc2217.py new file mode 100644 index 0000000..2ae188e --- /dev/null +++ b/packages_for_clean_installation /serial/rfc2217.py @@ -0,0 +1,1351 @@ +#! python +# +# This module implements a RFC2217 compatible client. RF2217 descibes a +# protocol to access serial ports over TCP/IP and allows setting the baud rate, +# modem control lines etc. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +# TODO: +# - setting control line -> answer is not checked (had problems with one of the +# severs). consider implementing a compatibility mode flag to make check +# conditional +# - write timeout not implemented at all + +# ########################################################################### +# observations and issues with servers +# =========================================================================== +# sredird V2.2.1 +# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz +# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding +# [105 1] instead of the actual value. +# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger +# numbers than 2**32? +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done +# =========================================================================== +# telnetcpcd (untested) +# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz +# - To get the signature [COM_PORT_OPTION] w/o data has to be sent. +# =========================================================================== +# ser2net +# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least +# acknowledges that the client activates these options +# - The configuration may be that the server prints a banner. As this client +# implementation does a flushInput on connect, this banner is hidden from +# the user application. +# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one +# second. +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: run ser2net daemon, in /etc/ser2net.conf: +# 2000:telnet:0:/dev/ttyS0:9600 remctl banner +# ########################################################################### + +# How to identify ports? pySerial might want to support other protocols in the +# future, so lets use an URL scheme. +# for RFC2217 compliant servers we will use this: +# rfc2217://:[?option[&option...]] +# +# options: +# - "logging" set log level print diagnostic messages (e.g. "logging=debug") +# - "ign_set_control": do not look at the answers to SET_CONTROL +# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read. +# Without this option it expects that the server sends notifications +# automatically on change (which most servers do and is according to the +# RFC). +# the order of the options is not relevant + +from __future__ import absolute_import + +import logging +import socket +import struct +import threading +import time +try: + import urlparse +except ImportError: + import urllib.parse as urlparse +try: + import Queue +except ImportError: + import queue as Queue + +import serial +from serial.serialutil import SerialBase, SerialException, to_bytes, \ + iterbytes, PortNotOpenError, Timeout + +# port string is expected to be something like this: +# rfc2217://host:port +# host may be an IP or including domain, whatever. +# port is 0...65535 + +# map log level names to constants. used in from_url() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, +} + + +# telnet protocol characters +SE = b'\xf0' # Subnegotiation End +NOP = b'\xf1' # No Operation +DM = b'\xf2' # Data Mark +BRK = b'\xf3' # Break +IP = b'\xf4' # Interrupt process +AO = b'\xf5' # Abort output +AYT = b'\xf6' # Are You There +EC = b'\xf7' # Erase Character +EL = b'\xf8' # Erase Line +GA = b'\xf9' # Go Ahead +SB = b'\xfa' # Subnegotiation Begin +WILL = b'\xfb' +WONT = b'\xfc' +DO = b'\xfd' +DONT = b'\xfe' +IAC = b'\xff' # Interpret As Command +IAC_DOUBLED = b'\xff\xff' + +# selected telnet options +BINARY = b'\x00' # 8-bit data path +ECHO = b'\x01' # echo +SGA = b'\x03' # suppress go ahead + +# RFC2217 +COM_PORT_OPTION = b'\x2c' + +# Client to Access Server +SET_BAUDRATE = b'\x01' +SET_DATASIZE = b'\x02' +SET_PARITY = b'\x03' +SET_STOPSIZE = b'\x04' +SET_CONTROL = b'\x05' +NOTIFY_LINESTATE = b'\x06' +NOTIFY_MODEMSTATE = b'\x07' +FLOWCONTROL_SUSPEND = b'\x08' +FLOWCONTROL_RESUME = b'\x09' +SET_LINESTATE_MASK = b'\x0a' +SET_MODEMSTATE_MASK = b'\x0b' +PURGE_DATA = b'\x0c' + +SERVER_SET_BAUDRATE = b'\x65' +SERVER_SET_DATASIZE = b'\x66' +SERVER_SET_PARITY = b'\x67' +SERVER_SET_STOPSIZE = b'\x68' +SERVER_SET_CONTROL = b'\x69' +SERVER_NOTIFY_LINESTATE = b'\x6a' +SERVER_NOTIFY_MODEMSTATE = b'\x6b' +SERVER_FLOWCONTROL_SUSPEND = b'\x6c' +SERVER_FLOWCONTROL_RESUME = b'\x6d' +SERVER_SET_LINESTATE_MASK = b'\x6e' +SERVER_SET_MODEMSTATE_MASK = b'\x6f' +SERVER_PURGE_DATA = b'\x70' + +RFC2217_ANSWER_MAP = { + SET_BAUDRATE: SERVER_SET_BAUDRATE, + SET_DATASIZE: SERVER_SET_DATASIZE, + SET_PARITY: SERVER_SET_PARITY, + SET_STOPSIZE: SERVER_SET_STOPSIZE, + SET_CONTROL: SERVER_SET_CONTROL, + NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE, + NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE, + FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND, + FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME, + SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK, + SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK, + PURGE_DATA: SERVER_PURGE_DATA, +} + +SET_CONTROL_REQ_FLOW_SETTING = b'\x00' # Request Com Port Flow Control Setting (outbound/both) +SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01' # Use No Flow Control (outbound/both) +SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02' # Use XON/XOFF Flow Control (outbound/both) +SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03' # Use HARDWARE Flow Control (outbound/both) +SET_CONTROL_REQ_BREAK_STATE = b'\x04' # Request BREAK State +SET_CONTROL_BREAK_ON = b'\x05' # Set BREAK State ON +SET_CONTROL_BREAK_OFF = b'\x06' # Set BREAK State OFF +SET_CONTROL_REQ_DTR = b'\x07' # Request DTR Signal State +SET_CONTROL_DTR_ON = b'\x08' # Set DTR Signal State ON +SET_CONTROL_DTR_OFF = b'\x09' # Set DTR Signal State OFF +SET_CONTROL_REQ_RTS = b'\x0a' # Request RTS Signal State +SET_CONTROL_RTS_ON = b'\x0b' # Set RTS Signal State ON +SET_CONTROL_RTS_OFF = b'\x0c' # Set RTS Signal State OFF +SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d' # Request Com Port Flow Control Setting (inbound) +SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e' # Use No Flow Control (inbound) +SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f' # Use XON/XOFF Flow Control (inbound) +SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10' # Use HARDWARE Flow Control (inbound) +SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11' # Use DCD Flow Control (outbound/both) +SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12' # Use DTR Flow Control (inbound) +SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13' # Use DSR Flow Control (outbound/both) + +LINESTATE_MASK_TIMEOUT = 128 # Time-out Error +LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty +LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty +LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error +LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error +LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error +LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error +LINESTATE_MASK_DATA_READY = 1 # Data Ready + +MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect) +MODEMSTATE_MASK_RI = 64 # Ring Indicator +MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State +MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State +MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect +MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector +MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready +MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send + +PURGE_RECEIVE_BUFFER = b'\x01' # Purge access server receive data buffer +PURGE_TRANSMIT_BUFFER = b'\x02' # Purge access server transmit data buffer +PURGE_BOTH_BUFFERS = b'\x03' # Purge both the access server receive data + # buffer and the access server transmit data buffer + + +RFC2217_PARITY_MAP = { + serial.PARITY_NONE: 1, + serial.PARITY_ODD: 2, + serial.PARITY_EVEN: 3, + serial.PARITY_MARK: 4, + serial.PARITY_SPACE: 5, +} +RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items()) + +RFC2217_STOPBIT_MAP = { + serial.STOPBITS_ONE: 1, + serial.STOPBITS_ONE_POINT_FIVE: 3, + serial.STOPBITS_TWO: 2, +} +RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items()) + +# Telnet filter states +M_NORMAL = 0 +M_IAC_SEEN = 1 +M_NEGOTIATE = 2 + +# TelnetOption and TelnetSubnegotiation states +REQUESTED = 'REQUESTED' +ACTIVE = 'ACTIVE' +INACTIVE = 'INACTIVE' +REALLY_INACTIVE = 'REALLY_INACTIVE' + + +class TelnetOption(object): + """Manage a single telnet option, keeps track of DO/DONT WILL/WONT.""" + + def __init__(self, connection, name, option, send_yes, send_no, ack_yes, + ack_no, initial_state, activation_callback=None): + """\ + Initialize option. + :param connection: connection used to transmit answers + :param name: a readable name for debug outputs + :param send_yes: what to send when option is to be enabled. + :param send_no: what to send when option is to be disabled. + :param ack_yes: what to expect when remote agrees on option. + :param ack_no: what to expect when remote disagrees on option. + :param initial_state: options initialized with REQUESTED are tried to + be enabled on startup. use INACTIVE for all others. + """ + self.connection = connection + self.name = name + self.option = option + self.send_yes = send_yes + self.send_no = send_no + self.ack_yes = ack_yes + self.ack_no = ack_no + self.state = initial_state + self.active = False + self.activation_callback = activation_callback + + def __repr__(self): + """String for debug outputs""" + return "{o.name}:{o.active}({o.state})".format(o=self) + + def process_incoming(self, command): + """\ + A DO/DONT/WILL/WONT was received for this option, update state and + answer when needed. + """ + if command == self.ack_yes: + if self.state is REQUESTED: + self.state = ACTIVE + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is ACTIVE: + pass + elif self.state is INACTIVE: + self.state = ACTIVE + self.connection.telnet_send_option(self.send_yes, self.option) + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is REALLY_INACTIVE: + self.connection.telnet_send_option(self.send_no, self.option) + else: + raise ValueError('option in illegal state {!r}'.format(self)) + elif command == self.ack_no: + if self.state is REQUESTED: + self.state = INACTIVE + self.active = False + elif self.state is ACTIVE: + self.state = INACTIVE + self.connection.telnet_send_option(self.send_no, self.option) + self.active = False + elif self.state is INACTIVE: + pass + elif self.state is REALLY_INACTIVE: + pass + else: + raise ValueError('option in illegal state {!r}'.format(self)) + + +class TelnetSubnegotiation(object): + """\ + A object to handle subnegotiation of options. In this case actually + sub-sub options for RFC 2217. It is used to track com port options. + """ + + def __init__(self, connection, name, option, ack_option=None): + if ack_option is None: + ack_option = option + self.connection = connection + self.name = name + self.option = option + self.value = None + self.ack_option = ack_option + self.state = INACTIVE + + def __repr__(self): + """String for debug outputs.""" + return "{sn.name}:{sn.state}".format(sn=self) + + def set(self, value): + """\ + Request a change of the value. a request is sent to the server. if + the client needs to know if the change is performed he has to check the + state of this object. + """ + self.value = value + self.state = REQUESTED + self.connection.rfc2217_send_subnegotiation(self.option, self.value) + if self.connection.logger: + self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value)) + + def is_ready(self): + """\ + Check if answer from server has been received. when server rejects + the change, raise a ValueError. + """ + if self.state == REALLY_INACTIVE: + raise ValueError("remote rejected value for option {!r}".format(self.name)) + return self.state == ACTIVE + # add property to have a similar interface as TelnetOption + active = property(is_ready) + + def wait(self, timeout=3): + """\ + Wait until the subnegotiation has been acknowledged or timeout. It + can also throw a value error when the answer from the server does not + match the value sent. + """ + timeout_timer = Timeout(timeout) + while not timeout_timer.expired(): + time.sleep(0.05) # prevent 100% CPU load + if self.is_ready(): + break + else: + raise SerialException("timeout while waiting for option {!r}".format(self.name)) + + def check_answer(self, suboption): + """\ + Check an incoming subnegotiation block. The parameter already has + cut off the header like sub option number and com port option value. + """ + if self.value == suboption[:len(self.value)]: + self.state = ACTIVE + else: + # error propagation done in is_ready + self.state = REALLY_INACTIVE + if self.connection.logger: + self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state)) + + +class Serial(SerialBase): + """Serial port implementation for RFC 2217 remote serial ports.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def __init__(self, *args, **kwargs): + self._thread = None + self._socket = None + self._linestate = 0 + self._modemstate = None + self._modemstate_timeout = Timeout(-1) + self._remote_suspend_flow = False + self._write_lock = None + self.logger = None + self._ignore_set_control_answer = False + self._poll_modem_state = False + self._network_timeout = 3 + self._telnet_options = None + self._rfc2217_port_settings = None + self._rfc2217_options = None + self._read_buffer = None + super(Serial, self).__init__(*args, **kwargs) # must be last call in case of auto-open + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + self.logger = None + self._ignore_set_control_answer = False + self._poll_modem_state = False + self._network_timeout = 3 + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + try: + self._socket = socket.create_connection(self.from_url(self.portstr), timeout=5) # XXX good value? + self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception as msg: + self._socket = None + raise SerialException("Could not open port {}: {}".format(self.portstr, msg)) + + # use a thread save queue as buffer. it also simplifies implementing + # the read timeout + self._read_buffer = Queue.Queue() + # to ensure that user writes does not interfere with internal + # telnet/rfc2217 options establish a lock + self._write_lock = threading.Lock() + # name the following separately so that, below, a check can be easily done + mandadory_options = [ + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED), + ] + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED), + ] + mandadory_options + # RFC 2217 specific states + # COM port settings + self._rfc2217_port_settings = { + 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE), + 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE), + 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY), + 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE), + } + # There are more subnegotiation objects, combine all in one dictionary + # for easy access + self._rfc2217_options = { + 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA), + 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL), + } + self._rfc2217_options.update(self._rfc2217_port_settings) + # cache for line and modem states that the server sends to us + self._linestate = 0 + self._modemstate = None + self._modemstate_timeout = Timeout(-1) + # RFC 2217 flow control between server and client + self._remote_suspend_flow = False + + self.is_open = True + self._thread = threading.Thread(target=self._telnet_read_loop) + self._thread.setDaemon(True) + self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port)) + self._thread.start() + + try: # must clean-up if open fails + # negotiate Telnet/RFC 2217 -> send initial requests + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnet_send_option(option.send_yes, option.option) + # now wait until important options are negotiated + timeout = Timeout(self._network_timeout) + while not timeout.expired(): + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options): + break + else: + raise SerialException( + "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options)) + if self.logger: + self.logger.info("Negotiated options: {}".format(self._telnet_options)) + + # fine, go on, set RFC 2217 specific things + self._reconfigure_port() + # all things set up get, now a clean start + if not self._dsrdtr: + self._update_dtr_state() + if not self._rtscts: + self._update_rts_state() + self.reset_input_buffer() + self.reset_output_buffer() + except: + self.close() + raise + + def _reconfigure_port(self): + """Set communication parameters on opened port.""" + if self._socket is None: + raise SerialException("Can only operate on open ports") + + # if self._timeout != 0 and self._interCharTimeout is not None: + # XXX + + if self._write_timeout is not None: + raise NotImplementedError('write_timeout is currently not supported') + # XXX + + # Setup the connection + # to get good performance, all parameter changes are sent first... + if not 0 < self._baudrate < 2 ** 32: + raise ValueError("invalid baudrate: {!r}".format(self._baudrate)) + self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate)) + self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize)) + self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity])) + self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits])) + + # and now wait until parameters are active + items = self._rfc2217_port_settings.values() + if self.logger: + self.logger.debug("Negotiating settings: {}".format(items)) + timeout = Timeout(self._network_timeout) + while not timeout.expired(): + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in items) == len(items): + break + else: + raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items)) + if self.logger: + self.logger.info("Negotiated settings: {}".format(items)) + + if self._rtscts and self._xonxoff: + raise ValueError('xonxoff and rtscts together are not supported') + elif self._rtscts: + self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL) + elif self._xonxoff: + self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL) + else: + self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL) + + def close(self): + """Close port""" + self.is_open = False + if self._socket: + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except: + # ignore errors. + pass + if self._thread: + self._thread.join(7) # XXX more than socket timeout + self._thread = None + # in case of quick reconnects, give the server some time + time.sleep(0.3) + self._socket = None + + def from_url(self, url): + """\ + extract host and port from an URL string, other settings are extracted + an stored in instance + """ + parts = urlparse.urlsplit(url) + if parts.scheme != "rfc2217": + raise SerialException( + 'expected a string in the form ' + '"rfc2217://:[?option[&option...]]": ' + 'not starting with rfc2217:// ({!r})'.format(parts.scheme)) + try: + # process options now, directly altering self + for option, values in urlparse.parse_qs(parts.query, True).items(): + if option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.rfc2217') + self.logger.setLevel(LOGGER_LEVELS[values[0]]) + self.logger.debug('enabled logging') + elif option == 'ign_set_control': + self._ignore_set_control_answer = True + elif option == 'poll_modem': + self._poll_modem_state = True + elif option == 'timeout': + self._network_timeout = float(values[0]) + else: + raise ValueError('unknown option: {!r}'.format(option)) + if not 0 <= parts.port < 65536: + raise ValueError("port not in range 0...65535") + except ValueError as e: + raise SerialException( + 'expected a string in the form ' + '"rfc2217://:[?option[&option...]]": {}'.format(e)) + return (parts.hostname, parts.port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of bytes currently in the input buffer.""" + if not self.is_open: + raise PortNotOpenError() + return self._read_buffer.qsize() + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + data = bytearray() + try: + timeout = Timeout(self._timeout) + while len(data) < size: + if self._thread is None or not self._thread.is_alive(): + raise SerialException('connection failed (reader thread died)') + buf = self._read_buffer.get(True, timeout.time_left()) + if buf is None: + return bytes(data) + data += buf + if timeout.expired(): + break + except Queue.Empty: # -> timeout + pass + return bytes(data) + + def write(self, data): + """\ + Output the given byte string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed. + """ + if not self.is_open: + raise PortNotOpenError() + with self._write_lock: + try: + self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED)) + except socket.error as e: + raise SerialException("connection failed (socket error): {}".format(e)) + return len(data) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER) + # empty read buffer + while self._read_buffer.qsize(): + self._read_buffer.get(False) + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER) + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, to transmitting is + possible. + """ + if not self.is_open: + raise PortNotOpenError() + if self.logger: + self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive')) + if self._break_state: + self.rfc2217_set_control(SET_CONTROL_BREAK_ON) + else: + self.rfc2217_set_control(SET_CONTROL_BREAK_OFF) + + def _update_rts_state(self): + """Set terminal status line: Request To Send.""" + if not self.is_open: + raise PortNotOpenError() + if self.logger: + self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive')) + if self._rts_state: + self.rfc2217_set_control(SET_CONTROL_RTS_ON) + else: + self.rfc2217_set_control(SET_CONTROL_RTS_OFF) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready.""" + if not self.is_open: + raise PortNotOpenError() + if self.logger: + self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive')) + if self._dtr_state: + self.rfc2217_set_control(SET_CONTROL_DTR_ON) + else: + self.rfc2217_set_control(SET_CONTROL_DTR_OFF) + + @property + def cts(self): + """Read terminal status line: Clear To Send.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS) + + @property + def dsr(self): + """Read terminal status line: Data Set Ready.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR) + + @property + def ri(self): + """Read terminal status line: Ring Indicator.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_RI) + + @property + def cd(self): + """Read terminal status line: Carrier Detect.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_CD) + + # - - - platform specific - - - + # None so far + + # - - - RFC2217 specific - - - + + def _telnet_read_loop(self): + """Read loop for the socket.""" + mode = M_NORMAL + suboption = None + try: + while self.is_open: + try: + data = self._socket.recv(1024) + except socket.timeout: + # just need to get out of recv form time to time to check if + # still alive + continue + except socket.error as e: + # connection fails -> terminate loop + if self.logger: + self.logger.debug("socket error in reader thread: {}".format(e)) + self._read_buffer.put(None) + break + if not data: + self._read_buffer.put(None) + break # lost connection + for byte in iterbytes(data): + if mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + mode = M_IAC_SEEN + else: + # store data in read buffer or sub option buffer + # depending on state + if suboption is not None: + suboption += byte + else: + self._read_buffer.put(byte) + elif mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if suboption is not None: + suboption += IAC + else: + self._read_buffer.put(IAC) + mode = M_NORMAL + elif byte == SB: + # sub option start + suboption = bytearray() + mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnet_process_subnegotiation(bytes(suboption)) + suboption = None + mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + telnet_command = byte + mode = M_NEGOTIATE + else: + # other telnet commands + self._telnet_process_command(byte) + mode = M_NORMAL + elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnet_negotiate_option(telnet_command, byte) + mode = M_NORMAL + finally: + if self.logger: + self.logger.debug("read thread terminated") + + # - incoming telnet commands and options + + def _telnet_process_command(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: {!r}".format(command)) + + def _telnet_negotiate_option(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnet_send_option((DONT if command == WILL else WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: {!r}".format(option)) + + def _telnet_process_subnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3: + self._linestate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate)) + elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3: + self._modemstate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate)) + # update time when we think that a poll would make sense + self._modemstate_timeout.restart(0.3) + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + self._remote_suspend_flow = False + else: + for item in self._rfc2217_options.values(): + if item.ack_option == suboption[1:2]: + #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:]) + item.check_answer(bytes(suboption[2:])) + break + else: + if self.logger: + self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption)) + else: + if self.logger: + self.logger.warning("ignoring subnegotiation: {!r}".format(suboption)) + + # - outgoing telnet commands and options + + def _internal_raw_write(self, data): + """internal socket write with no data escaping. used to send telnet stuff.""" + with self._write_lock: + self._socket.sendall(data) + + def telnet_send_option(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self._internal_raw_write(IAC + action + option) + + def rfc2217_send_subnegotiation(self, option, value=b''): + """Subnegotiation of RFC2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self._internal_raw_write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE) + + def rfc2217_send_purge(self, value): + """\ + Send purge request to the remote. + (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS) + """ + item = self._rfc2217_options['purge'] + item.set(value) # transmit desired purge type + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217_set_control(self, value): + """transmit change of control line to remote""" + item = self._rfc2217_options['control'] + item.set(value) # transmit desired control type + if self._ignore_set_control_answer: + # answers are ignored when option is set. compatibility mode for + # servers that answer, but not the expected one... (or no answer + # at all) i.e. sredird + time.sleep(0.1) # this helps getting the unit tests passed + else: + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217_flow_server_ready(self): + """\ + check if server is ready to receive data. block for some time when + not. + """ + #~ if self._remote_suspend_flow: + #~ wait--- + + def get_modem_state(self): + """\ + get last modem state (cached value. If value is "old", request a new + one. This cache helps that we don't issue to many requests when e.g. all + status lines, one after the other is queried by the user (CTS, DSR + etc.) + """ + # active modem state polling enabled? is the value fresh enough? + if self._poll_modem_state and self._modemstate_timeout.expired(): + if self.logger: + self.logger.debug('polling modem state') + # when it is older, request an update + self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE) + timeout = Timeout(self._network_timeout) + while not timeout.expired(): + time.sleep(0.05) # prevent 100% CPU load + # when expiration time is updated, it means that there is a new + # value + if not self._modemstate_timeout.expired(): + break + else: + if self.logger: + self.logger.warning('poll for modem state failed') + # even when there is a timeout, do not generate an error just + # return the last known value. this way we can support buggy + # servers that do not respond to polls, but send automatic + # updates. + if self._modemstate is not None: + if self.logger: + self.logger.debug('using cached modem state') + return self._modemstate + else: + # never received a notification from the server + raise SerialException("remote sends no NOTIFY_MODEMSTATE") + + +############################################################################# +# The following is code that helps implementing an RFC 2217 server. + +class PortManager(object): + """\ + This class manages the state of Telnet and RFC 2217. It needs a serial + instance and a connection to work with. Connection is expected to implement + a (thread safe) write function, that writes the string to the network. + """ + + def __init__(self, serial_port, connection, logger=None): + self.serial = serial_port + self.connection = connection + self.logger = logger + self._client_is_rfc2217 = False + + # filter state machine + self.mode = M_NORMAL + self.suboption = None + self.telnet_command = None + + # states for modem/line control events + self.modemstate_mask = 255 + self.last_modemstate = None + self.linstate_mask = 0 + + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok), + ] + + # negotiate Telnet/RFC2217 -> send initial requests + if self.logger: + self.logger.debug("requesting initial Telnet/RFC 2217 options") + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnet_send_option(option.send_yes, option.option) + # issue 1st modem state notification + + def _client_ok(self): + """\ + callback of telnet option. It gets called when option is activated. + This one here is used to detect when the client agrees on RFC 2217. A + flag is set so that other functions like check_modem_lines know if the + client is OK. + """ + # The callback is used for we and they so if one party agrees, we're + # already happy. it seems not all servers do the negotiation correctly + # and i guess there are incorrect clients too.. so be happy if client + # answers one or the other positively. + self._client_is_rfc2217 = True + if self.logger: + self.logger.info("client accepts RFC 2217") + # this is to ensure that the client gets a notification, even if there + # was no change + self.check_modem_lines(force_notification=True) + + # - outgoing telnet commands and options + + def telnet_send_option(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self.connection.write(IAC + action + option) + + def rfc2217_send_subnegotiation(self, option, value=b''): + """Subnegotiation of RFC 2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self.connection.write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE) + + # - check modem lines, needs to be called periodically from user to + # establish polling + + def check_modem_lines(self, force_notification=False): + """\ + read control lines from serial port and compare the last value sent to remote. + send updates on changes. + """ + modemstate = ( + (self.serial.cts and MODEMSTATE_MASK_CTS) | + (self.serial.dsr and MODEMSTATE_MASK_DSR) | + (self.serial.ri and MODEMSTATE_MASK_RI) | + (self.serial.cd and MODEMSTATE_MASK_CD)) + # check what has changed + deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0 + if deltas & MODEMSTATE_MASK_CTS: + modemstate |= MODEMSTATE_MASK_CTS_CHANGE + if deltas & MODEMSTATE_MASK_DSR: + modemstate |= MODEMSTATE_MASK_DSR_CHANGE + if deltas & MODEMSTATE_MASK_RI: + modemstate |= MODEMSTATE_MASK_RI_CHANGE + if deltas & MODEMSTATE_MASK_CD: + modemstate |= MODEMSTATE_MASK_CD_CHANGE + # if new state is different and the mask allows this change, send + # notification. suppress notifications when client is not rfc2217 + if modemstate != self.last_modemstate or force_notification: + if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification: + self.rfc2217_send_subnegotiation( + SERVER_NOTIFY_MODEMSTATE, + to_bytes([modemstate & self.modemstate_mask])) + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate)) + # save last state, but forget about deltas. + # otherwise it would also notify about changing deltas which is + # probably not very useful + self.last_modemstate = modemstate & 0xf0 + + # - outgoing data escaping + + def escape(self, data): + """\ + This generator function is for the user. All outgoing data has to be + properly escaped, so that no IAC character in the data stream messes up + the Telnet state machine in the server. + + socket.sendall(escape(data)) + """ + for byte in iterbytes(data): + if byte == IAC: + yield IAC + yield IAC + else: + yield byte + + # - incoming data filter + + def filter(self, data): + """\ + Handle a bunch of incoming bytes. This is a generator. It will yield + all characters not of interest for Telnet/RFC 2217. + + The idea is that the reader thread pushes data from the socket through + this filter: + + for byte in filter(socket.recv(1024)): + # do things like CR/LF conversion/whatever + # and write data to the serial port + serial.write(byte) + + (socket error handling code left as exercise for the reader) + """ + for byte in iterbytes(data): + if self.mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + self.mode = M_IAC_SEEN + else: + # store data in sub option buffer or pass it to our + # consumer depending on state + if self.suboption is not None: + self.suboption += byte + else: + yield byte + elif self.mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if self.suboption is not None: + self.suboption += byte + else: + yield byte + self.mode = M_NORMAL + elif byte == SB: + # sub option start + self.suboption = bytearray() + self.mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnet_process_subnegotiation(bytes(self.suboption)) + self.suboption = None + self.mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + self.telnet_command = byte + self.mode = M_NEGOTIATE + else: + # other telnet commands + self._telnet_process_command(byte) + self.mode = M_NORMAL + elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnet_negotiate_option(self.telnet_command, byte) + self.mode = M_NORMAL + + # - incoming telnet commands and options + + def _telnet_process_command(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: {!r}".format(command)) + + def _telnet_negotiate_option(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnet_send_option((DONT if command == WILL else WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: {!r}".format(option)) + + def _telnet_process_subnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if self.logger: + self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption)) + if suboption[1:2] == SET_BAUDRATE: + backup = self.serial.baudrate + try: + (baudrate,) = struct.unpack(b"!I", suboption[2:6]) + if baudrate != 0: + self.serial.baudrate = baudrate + except ValueError as e: + if self.logger: + self.logger.error("failed to set baud rate: {}".format(e)) + self.serial.baudrate = backup + else: + if self.logger: + self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate)) + self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate)) + elif suboption[1:2] == SET_DATASIZE: + backup = self.serial.bytesize + try: + (datasize,) = struct.unpack(b"!B", suboption[2:3]) + if datasize != 0: + self.serial.bytesize = datasize + except ValueError as e: + if self.logger: + self.logger.error("failed to set data size: {}".format(e)) + self.serial.bytesize = backup + else: + if self.logger: + self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize)) + self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize)) + elif suboption[1:2] == SET_PARITY: + backup = self.serial.parity + try: + parity = struct.unpack(b"!B", suboption[2:3])[0] + if parity != 0: + self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity] + except ValueError as e: + if self.logger: + self.logger.error("failed to set parity: {}".format(e)) + self.serial.parity = backup + else: + if self.logger: + self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity)) + self.rfc2217_send_subnegotiation( + SERVER_SET_PARITY, + struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity])) + elif suboption[1:2] == SET_STOPSIZE: + backup = self.serial.stopbits + try: + stopbits = struct.unpack(b"!B", suboption[2:3])[0] + if stopbits != 0: + self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits] + except ValueError as e: + if self.logger: + self.logger.error("failed to set stop bits: {}".format(e)) + self.serial.stopbits = backup + else: + if self.logger: + self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits)) + self.rfc2217_send_subnegotiation( + SERVER_SET_STOPSIZE, + struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])) + elif suboption[1:2] == SET_CONTROL: + if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING: + if self.serial.xonxoff: + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif self.serial.rtscts: + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + else: + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL: + self.serial.xonxoff = False + self.serial.rtscts = False + if self.logger: + self.logger.info("changed flow control to None") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL: + self.serial.xonxoff = True + if self.logger: + self.logger.info("changed flow control to XON/XOFF") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL: + self.serial.rtscts = True + if self.logger: + self.logger.info("changed flow control to RTS/CTS") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE: + if self.logger: + self.logger.warning("requested break state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_BREAK_ON: + self.serial.break_condition = True + if self.logger: + self.logger.info("changed BREAK to active") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON) + elif suboption[2:3] == SET_CONTROL_BREAK_OFF: + self.serial.break_condition = False + if self.logger: + self.logger.info("changed BREAK to inactive") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_DTR: + if self.logger: + self.logger.warning("requested DTR state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_DTR_ON: + self.serial.dtr = True + if self.logger: + self.logger.info("changed DTR to active") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON) + elif suboption[2:3] == SET_CONTROL_DTR_OFF: + self.serial.dtr = False + if self.logger: + self.logger.info("changed DTR to inactive") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_RTS: + if self.logger: + self.logger.warning("requested RTS state - not implemented") + pass # XXX needs cached value + #~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_ON: + self.serial.rts = True + if self.logger: + self.logger.info("changed RTS to active") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_OFF: + self.serial.rts = False + if self.logger: + self.logger.info("changed RTS to inactive") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF) + #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL: + elif suboption[1:2] == NOTIFY_LINESTATE: + # client polls for current state + self.rfc2217_send_subnegotiation( + SERVER_NOTIFY_LINESTATE, + to_bytes([0])) # sorry, nothing like that implemented + elif suboption[1:2] == NOTIFY_MODEMSTATE: + if self.logger: + self.logger.info("request for modem state") + # client polls for current state + self.check_modem_lines(force_notification=True) + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + if self.logger: + self.logger.info("suspend") + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + if self.logger: + self.logger.info("resume") + self._remote_suspend_flow = False + elif suboption[1:2] == SET_LINESTATE_MASK: + self.linstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask)) + elif suboption[1:2] == SET_MODEMSTATE_MASK: + self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask)) + elif suboption[1:2] == PURGE_DATA: + if suboption[2:3] == PURGE_RECEIVE_BUFFER: + self.serial.reset_input_buffer() + if self.logger: + self.logger.info("purge in") + self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER) + elif suboption[2:3] == PURGE_TRANSMIT_BUFFER: + self.serial.reset_output_buffer() + if self.logger: + self.logger.info("purge out") + self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER) + elif suboption[2:3] == PURGE_BOTH_BUFFERS: + self.serial.reset_input_buffer() + self.serial.reset_output_buffer() + if self.logger: + self.logger.info("purge both") + self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS) + else: + if self.logger: + self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:]))) + else: + if self.logger: + self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:]))) + else: + if self.logger: + self.logger.warning("unknown subnegotiation: {!r}".format(suboption)) + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('rfc2217://localhost:7000', 115200) + sys.stdout.write('{}\n'.format(s)) + + sys.stdout.write("write...\n") + s.write(b"hello\n") + s.flush() + sys.stdout.write("read: {}\n".format(s.read(5))) + s.close() diff --git a/packages_for_clean_installation /serial/rfc2217.pyc b/packages_for_clean_installation /serial/rfc2217.pyc new file mode 100644 index 0000000..9d4727e Binary files /dev/null and b/packages_for_clean_installation /serial/rfc2217.pyc differ diff --git a/packages_for_clean_installation /serial/rs485.py b/packages_for_clean_installation /serial/rs485.py new file mode 100644 index 0000000..d7aff6f --- /dev/null +++ b/packages_for_clean_installation /serial/rs485.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# RS485 support +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +"""\ +The settings for RS485 are stored in a dedicated object that can be applied to +serial ports (where supported). +NOTE: Some implementations may only support a subset of the settings. +""" + +from __future__ import absolute_import + +import time +import serial + + +class RS485Settings(object): + def __init__( + self, + rts_level_for_tx=True, + rts_level_for_rx=False, + loopback=False, + delay_before_tx=None, + delay_before_rx=None): + self.rts_level_for_tx = rts_level_for_tx + self.rts_level_for_rx = rts_level_for_rx + self.loopback = loopback + self.delay_before_tx = delay_before_tx + self.delay_before_rx = delay_before_rx + + +class RS485(serial.Serial): + """\ + A subclass that replaces the write method with one that toggles RTS + according to the RS485 settings. + + NOTE: This may work unreliably on some serial ports (control signals not + synchronized or delayed compared to data). Using delays may be + unreliable (varying times, larger than expected) as the OS may not + support very fine grained delays (no smaller than in the order of + tens of milliseconds). + + NOTE: Some implementations support this natively. Better performance + can be expected when the native version is used. + + NOTE: The loopback property is ignored by this implementation. The actual + behavior depends on the used hardware. + + Usage: + + ser = RS485(...) + ser.rs485_mode = RS485Settings(...) + ser.write(b'hello') + """ + + def __init__(self, *args, **kwargs): + super(RS485, self).__init__(*args, **kwargs) + self._alternate_rs485_settings = None + + def write(self, b): + """Write to port, controlling RTS before and after transmitting.""" + if self._alternate_rs485_settings is not None: + # apply level for TX and optional delay + self.setRTS(self._alternate_rs485_settings.rts_level_for_tx) + if self._alternate_rs485_settings.delay_before_tx is not None: + time.sleep(self._alternate_rs485_settings.delay_before_tx) + # write and wait for data to be written + super(RS485, self).write(b) + super(RS485, self).flush() + # optional delay and apply level for RX + if self._alternate_rs485_settings.delay_before_rx is not None: + time.sleep(self._alternate_rs485_settings.delay_before_rx) + self.setRTS(self._alternate_rs485_settings.rts_level_for_rx) + else: + super(RS485, self).write(b) + + # redirect where the property stores the settings so that underlying Serial + # instance does not see them + @property + def rs485_mode(self): + """\ + Enable RS485 mode and apply new settings, set to None to disable. + See serial.rs485.RS485Settings for more info about the value. + """ + return self._alternate_rs485_settings + + @rs485_mode.setter + def rs485_mode(self, rs485_settings): + self._alternate_rs485_settings = rs485_settings diff --git a/packages_for_clean_installation /serial/rs485.pyc b/packages_for_clean_installation /serial/rs485.pyc new file mode 100644 index 0000000..09650af Binary files /dev/null and b/packages_for_clean_installation /serial/rs485.pyc differ diff --git a/packages_for_clean_installation /serial/serialcli.py b/packages_for_clean_installation /serial/serialcli.py new file mode 100644 index 0000000..4614736 --- /dev/null +++ b/packages_for_clean_installation /serial/serialcli.py @@ -0,0 +1,253 @@ +#! python +# +# Backend for .NET/Mono (IronPython), .NET >= 2 +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2008-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import System +import System.IO.Ports +from serial.serialutil import * + +# must invoke function with byte array, make a helper to convert strings +# to byte arrays +sab = System.Array[System.Byte] + + +def as_byte_array(string): + return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython + + +class Serial(SerialBase): + """Serial port implementation for .NET/Mono.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + try: + self._port_handle = System.IO.Ports.SerialPort(self.portstr) + except Exception as msg: + self._port_handle = None + raise SerialException("could not open port %s: %s" % (self.portstr, msg)) + + # if RTS and/or DTR are not set before open, they default to True + if self._rts_state is None: + self._rts_state = True + if self._dtr_state is None: + self._dtr_state = True + + self._reconfigure_port() + self._port_handle.Open() + self.is_open = True + if not self._dsrdtr: + self._update_dtr_state() + if not self._rtscts: + self._update_rts_state() + self.reset_input_buffer() + + def _reconfigure_port(self): + """Set communication parameters on opened port.""" + if not self._port_handle: + raise SerialException("Can only operate on a valid port handle") + + #~ self._port_handle.ReceivedBytesThreshold = 1 + + if self._timeout is None: + self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout + else: + self._port_handle.ReadTimeout = int(self._timeout * 1000) + + # if self._timeout != 0 and self._interCharTimeout is not None: + # timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:] + + if self._write_timeout is None: + self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout + else: + self._port_handle.WriteTimeout = int(self._write_timeout * 1000) + + # Setup the connection info. + try: + self._port_handle.BaudRate = self._baudrate + except IOError as e: + # catch errors from illegal baudrate settings + raise ValueError(str(e)) + + if self._bytesize == FIVEBITS: + self._port_handle.DataBits = 5 + elif self._bytesize == SIXBITS: + self._port_handle.DataBits = 6 + elif self._bytesize == SEVENBITS: + self._port_handle.DataBits = 7 + elif self._bytesize == EIGHTBITS: + self._port_handle.DataBits = 8 + else: + raise ValueError("Unsupported number of data bits: %r" % self._bytesize) + + if self._parity == PARITY_NONE: + self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k + elif self._parity == PARITY_EVEN: + self._port_handle.Parity = System.IO.Ports.Parity.Even + elif self._parity == PARITY_ODD: + self._port_handle.Parity = System.IO.Ports.Parity.Odd + elif self._parity == PARITY_MARK: + self._port_handle.Parity = System.IO.Ports.Parity.Mark + elif self._parity == PARITY_SPACE: + self._port_handle.Parity = System.IO.Ports.Parity.Space + else: + raise ValueError("Unsupported parity mode: %r" % self._parity) + + if self._stopbits == STOPBITS_ONE: + self._port_handle.StopBits = System.IO.Ports.StopBits.One + elif self._stopbits == STOPBITS_ONE_POINT_FIVE: + self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive + elif self._stopbits == STOPBITS_TWO: + self._port_handle.StopBits = System.IO.Ports.StopBits.Two + else: + raise ValueError("Unsupported number of stop bits: %r" % self._stopbits) + + if self._rtscts and self._xonxoff: + self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff + elif self._rtscts: + self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend + elif self._xonxoff: + self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff + else: + self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k + + #~ def __del__(self): + #~ self.close() + + def close(self): + """Close port""" + if self.is_open: + if self._port_handle: + try: + self._port_handle.Close() + except System.IO.Ports.InvalidOperationException: + # ignore errors. can happen for unplugged USB serial devices + pass + self._port_handle = None + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of characters currently in the input buffer.""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.BytesToRead + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + # must use single byte reads as this is the only way to read + # without applying encodings + data = bytearray() + while size: + try: + data.append(self._port_handle.ReadByte()) + except System.TimeoutException: + break + else: + size -= 1 + return bytes(data) + + def write(self, data): + """Output the given string over the serial port.""" + if not self.is_open: + raise PortNotOpenError() + #~ if not isinstance(data, (bytes, bytearray)): + #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + try: + # must call overloaded method with byte array argument + # as this is the only one not applying encodings + self._port_handle.Write(as_byte_array(data), 0, len(data)) + except System.TimeoutException: + raise SerialTimeoutException('Write timeout') + return len(data) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + self._port_handle.DiscardInBuffer() + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + self._port_handle.DiscardOutBuffer() + + def _update_break_state(self): + """ + Set break: Controls TXD. When active, to transmitting is possible. + """ + if not self.is_open: + raise PortNotOpenError() + self._port_handle.BreakState = bool(self._break_state) + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if not self.is_open: + raise PortNotOpenError() + self._port_handle.RtsEnable = bool(self._rts_state) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if not self.is_open: + raise PortNotOpenError() + self._port_handle.DtrEnable = bool(self._dtr_state) + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.CtsHolding + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.DsrHolding + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + if not self.is_open: + raise PortNotOpenError() + #~ return self._port_handle.XXX + return False # XXX an error would be better + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.CDHolding + + # - - platform specific - - - - + # none diff --git a/packages_for_clean_installation /serial/serialcli.pyc b/packages_for_clean_installation /serial/serialcli.pyc new file mode 100644 index 0000000..d1caa60 Binary files /dev/null and b/packages_for_clean_installation /serial/serialcli.pyc differ diff --git a/packages_for_clean_installation /serial/serialjava.py b/packages_for_clean_installation /serial/serialjava.py new file mode 100644 index 0000000..0789a78 --- /dev/null +++ b/packages_for_clean_installation /serial/serialjava.py @@ -0,0 +1,251 @@ +#!jython +# +# Backend Jython with JavaComm +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2002-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +from serial.serialutil import * + + +def my_import(name): + mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + +def detect_java_comm(names): + """try given list of modules and return that imports""" + for name in names: + try: + mod = my_import(name) + mod.SerialPort + return mod + except (ImportError, AttributeError): + pass + raise ImportError("No Java Communications API implementation found") + + +# Java Communications API implementations +# http://mho.republika.pl/java/comm/ + +comm = detect_java_comm([ + 'javax.comm', # Sun/IBM + 'gnu.io', # RXTX +]) + + +def device(portnumber): + """Turn a port number into a device name""" + enum = comm.CommPortIdentifier.getPortIdentifiers() + ports = [] + while enum.hasMoreElements(): + el = enum.nextElement() + if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL: + ports.append(el) + return ports[portnumber].getName() + + +class Serial(SerialBase): + """\ + Serial port class, implemented with Java Communications API and + thus usable with jython and the appropriate java extension. + """ + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + if type(self._port) == type(''): # strings are taken directly + portId = comm.CommPortIdentifier.getPortIdentifier(self._port) + else: + portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port)) # numbers are transformed to a comport id obj + try: + self.sPort = portId.open("python serial module", 10) + except Exception as msg: + self.sPort = None + raise SerialException("Could not open port: %s" % msg) + self._reconfigurePort() + self._instream = self.sPort.getInputStream() + self._outstream = self.sPort.getOutputStream() + self.is_open = True + + def _reconfigurePort(self): + """Set communication parameters on opened port.""" + if not self.sPort: + raise SerialException("Can only operate on a valid port handle") + + self.sPort.enableReceiveTimeout(30) + if self._bytesize == FIVEBITS: + jdatabits = comm.SerialPort.DATABITS_5 + elif self._bytesize == SIXBITS: + jdatabits = comm.SerialPort.DATABITS_6 + elif self._bytesize == SEVENBITS: + jdatabits = comm.SerialPort.DATABITS_7 + elif self._bytesize == EIGHTBITS: + jdatabits = comm.SerialPort.DATABITS_8 + else: + raise ValueError("unsupported bytesize: %r" % self._bytesize) + + if self._stopbits == STOPBITS_ONE: + jstopbits = comm.SerialPort.STOPBITS_1 + elif self._stopbits == STOPBITS_ONE_POINT_FIVE: + jstopbits = comm.SerialPort.STOPBITS_1_5 + elif self._stopbits == STOPBITS_TWO: + jstopbits = comm.SerialPort.STOPBITS_2 + else: + raise ValueError("unsupported number of stopbits: %r" % self._stopbits) + + if self._parity == PARITY_NONE: + jparity = comm.SerialPort.PARITY_NONE + elif self._parity == PARITY_EVEN: + jparity = comm.SerialPort.PARITY_EVEN + elif self._parity == PARITY_ODD: + jparity = comm.SerialPort.PARITY_ODD + elif self._parity == PARITY_MARK: + jparity = comm.SerialPort.PARITY_MARK + elif self._parity == PARITY_SPACE: + jparity = comm.SerialPort.PARITY_SPACE + else: + raise ValueError("unsupported parity type: %r" % self._parity) + + jflowin = jflowout = 0 + if self._rtscts: + jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN + jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT + if self._xonxoff: + jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN + jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT + + self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity) + self.sPort.setFlowControlMode(jflowin | jflowout) + + if self._timeout >= 0: + self.sPort.enableReceiveTimeout(int(self._timeout*1000)) + else: + self.sPort.disableReceiveTimeout() + + def close(self): + """Close port""" + if self.is_open: + if self.sPort: + self._instream.close() + self._outstream.close() + self.sPort.close() + self.sPort = None + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of characters currently in the input buffer.""" + if not self.sPort: + raise PortNotOpenError() + return self._instream.available() + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.sPort: + raise PortNotOpenError() + read = bytearray() + if size > 0: + while len(read) < size: + x = self._instream.read() + if x == -1: + if self.timeout >= 0: + break + else: + read.append(x) + return bytes(read) + + def write(self, data): + """Output the given string over the serial port.""" + if not self.sPort: + raise PortNotOpenError() + if not isinstance(data, (bytes, bytearray)): + raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + self._outstream.write(data) + return len(data) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.sPort: + raise PortNotOpenError() + self._instream.skip(self._instream.available()) + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self.sPort: + raise PortNotOpenError() + self._outstream.flush() + + def send_break(self, duration=0.25): + """Send break condition. Timed, returns to idle state after given duration.""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.sendBreak(duration*1000.0) + + def _update_break_state(self): + """Set break: Controls TXD. When active, to transmitting is possible.""" + if self.fd is None: + raise PortNotOpenError() + raise SerialException("The _update_break_state function is not implemented in java.") + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.setRTS(self._rts_state) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.setDTR(self._dtr_state) + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isCTS() + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isDSR() + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isRI() + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isCD() diff --git a/packages_for_clean_installation /serial/serialjava.pyc b/packages_for_clean_installation /serial/serialjava.pyc new file mode 100644 index 0000000..7db4a58 Binary files /dev/null and b/packages_for_clean_installation /serial/serialjava.pyc differ diff --git a/packages_for_clean_installation /serial/serialposix.py b/packages_for_clean_installation /serial/serialposix.py new file mode 100644 index 0000000..7aceb76 --- /dev/null +++ b/packages_for_clean_installation /serial/serialposix.py @@ -0,0 +1,900 @@ +#!/usr/bin/env python +# +# backend for serial IO for POSIX compatible systems, like Linux, OSX +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +# +# parts based on code from Grant B. Edwards : +# ftp://ftp.visi.com/users/grante/python/PosixSerial.py +# +# references: http://www.easysw.com/~mike/serial/serial.html + +# Collection of port names (was previously used by number_to_device which was +# removed. +# - Linux /dev/ttyS%d (confirmed) +# - cygwin/win32 /dev/com%d (confirmed) +# - openbsd (OpenBSD) /dev/cua%02d +# - bsd*, freebsd* /dev/cuad%d +# - darwin (OS X) /dev/cuad%d +# - netbsd /dev/dty%02d (NetBSD 1.6 testing by Erk) +# - irix (IRIX) /dev/ttyf%d (partially tested) names depending on flow control +# - hp (HP-UX) /dev/tty%dp0 (not tested) +# - sunos (Solaris/SunOS) /dev/tty%c (letters, 'a'..'z') (confirmed) +# - aix (AIX) /dev/tty%d + + +from __future__ import absolute_import + +# pylint: disable=abstract-method +import errno +import fcntl +import os +import select +import struct +import sys +import termios + +import serial +from serial.serialutil import SerialBase, SerialException, to_bytes, \ + PortNotOpenError, SerialTimeoutException, Timeout + + +class PlatformSpecificBase(object): + BAUDRATE_CONSTANTS = {} + + def _set_special_baudrate(self, baudrate): + raise NotImplementedError('non-standard baudrates are not supported on this platform') + + def _set_rs485_mode(self, rs485_settings): + raise NotImplementedError('RS485 not supported on this platform') + + def set_low_latency_mode(self, low_latency_settings): + raise NotImplementedError('Low latency not supported on this platform') + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, no transmitting is possible. + """ + if self._break_state: + fcntl.ioctl(self.fd, TIOCSBRK) + else: + fcntl.ioctl(self.fd, TIOCCBRK) + + +# some systems support an extra flag to enable the two in POSIX unsupported +# paritiy settings for MARK and SPACE +CMSPAR = 0 # default, for unsupported platforms, override below + +# try to detect the OS so that a device can be selected... +# this code block should supply a device() and set_special_baudrate() function +# for the platform +plat = sys.platform.lower() + +if plat[:5] == 'linux': # Linux (confirmed) # noqa + import array + + # extra termios flags + CMSPAR = 0o10000000000 # Use "stick" (mark/space) parity + + # baudrate ioctls + TCGETS2 = 0x802C542A + TCSETS2 = 0x402C542B + BOTHER = 0o010000 + + # RS485 ioctls + TIOCGRS485 = 0x542E + TIOCSRS485 = 0x542F + SER_RS485_ENABLED = 0b00000001 + SER_RS485_RTS_ON_SEND = 0b00000010 + SER_RS485_RTS_AFTER_SEND = 0b00000100 + SER_RS485_RX_DURING_TX = 0b00010000 + + class PlatformSpecific(PlatformSpecificBase): + BAUDRATE_CONSTANTS = { + 0: 0o000000, # hang up + 50: 0o000001, + 75: 0o000002, + 110: 0o000003, + 134: 0o000004, + 150: 0o000005, + 200: 0o000006, + 300: 0o000007, + 600: 0o000010, + 1200: 0o000011, + 1800: 0o000012, + 2400: 0o000013, + 4800: 0o000014, + 9600: 0o000015, + 19200: 0o000016, + 38400: 0o000017, + 57600: 0o010001, + 115200: 0o010002, + 230400: 0o010003, + 460800: 0o010004, + 500000: 0o010005, + 576000: 0o010006, + 921600: 0o010007, + 1000000: 0o010010, + 1152000: 0o010011, + 1500000: 0o010012, + 2000000: 0o010013, + 2500000: 0o010014, + 3000000: 0o010015, + 3500000: 0o010016, + 4000000: 0o010017 + } + + def set_low_latency_mode(self, low_latency_settings): + buf = array.array('i', [0] * 32) + + try: + # get serial_struct + fcntl.ioctl(self.fd, termios.TIOCGSERIAL, buf) + + # set or unset ASYNC_LOW_LATENCY flag + if low_latency_settings: + buf[4] |= 0x2000 + else: + buf[4] &= ~0x2000 + + # set serial_struct + fcntl.ioctl(self.fd, termios.TIOCSSERIAL, buf) + except IOError as e: + raise ValueError('Failed to update ASYNC_LOW_LATENCY flag to {}: {}'.format(low_latency_settings, e)) + + def _set_special_baudrate(self, baudrate): + # right size is 44 on x86_64, allow for some growth + buf = array.array('i', [0] * 64) + try: + # get serial_struct + fcntl.ioctl(self.fd, TCGETS2, buf) + # set custom speed + buf[2] &= ~termios.CBAUD + buf[2] |= BOTHER + buf[9] = buf[10] = baudrate + + # set serial_struct + fcntl.ioctl(self.fd, TCSETS2, buf) + except IOError as e: + raise ValueError('Failed to set custom baud rate ({}): {}'.format(baudrate, e)) + + def _set_rs485_mode(self, rs485_settings): + buf = array.array('i', [0] * 8) # flags, delaytx, delayrx, padding + try: + fcntl.ioctl(self.fd, TIOCGRS485, buf) + buf[0] |= SER_RS485_ENABLED + if rs485_settings is not None: + if rs485_settings.loopback: + buf[0] |= SER_RS485_RX_DURING_TX + else: + buf[0] &= ~SER_RS485_RX_DURING_TX + if rs485_settings.rts_level_for_tx: + buf[0] |= SER_RS485_RTS_ON_SEND + else: + buf[0] &= ~SER_RS485_RTS_ON_SEND + if rs485_settings.rts_level_for_rx: + buf[0] |= SER_RS485_RTS_AFTER_SEND + else: + buf[0] &= ~SER_RS485_RTS_AFTER_SEND + if rs485_settings.delay_before_tx is not None: + buf[1] = int(rs485_settings.delay_before_tx * 1000) + if rs485_settings.delay_before_rx is not None: + buf[2] = int(rs485_settings.delay_before_rx * 1000) + else: + buf[0] = 0 # clear SER_RS485_ENABLED + fcntl.ioctl(self.fd, TIOCSRS485, buf) + except IOError as e: + raise ValueError('Failed to set RS485 mode: {}'.format(e)) + + +elif plat == 'cygwin': # cygwin/win32 (confirmed) + + class PlatformSpecific(PlatformSpecificBase): + BAUDRATE_CONSTANTS = { + 128000: 0x01003, + 256000: 0x01005, + 500000: 0x01007, + 576000: 0x01008, + 921600: 0x01009, + 1000000: 0x0100a, + 1152000: 0x0100b, + 1500000: 0x0100c, + 2000000: 0x0100d, + 2500000: 0x0100e, + 3000000: 0x0100f + } + + +elif plat[:6] == 'darwin': # OS X + import array + IOSSIOSPEED = 0x80045402 # _IOW('T', 2, speed_t) + + class PlatformSpecific(PlatformSpecificBase): + osx_version = os.uname()[2].split('.') + TIOCSBRK = 0x2000747B # _IO('t', 123) + TIOCCBRK = 0x2000747A # _IO('t', 122) + + # Tiger or above can support arbitrary serial speeds + if int(osx_version[0]) >= 8: + def _set_special_baudrate(self, baudrate): + # use IOKit-specific call to set up high speeds + buf = array.array('i', [baudrate]) + fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1) + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, no transmitting is possible. + """ + if self._break_state: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK) + else: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK) + +elif plat[:3] == 'bsd' or \ + plat[:7] == 'freebsd' or \ + plat[:6] == 'netbsd' or \ + plat[:7] == 'openbsd': + + class ReturnBaudrate(object): + def __getitem__(self, key): + return key + + class PlatformSpecific(PlatformSpecificBase): + # Only tested on FreeBSD: + # The baud rate may be passed in as + # a literal value. + BAUDRATE_CONSTANTS = ReturnBaudrate() + + TIOCSBRK = 0x2000747B # _IO('t', 123) + TIOCCBRK = 0x2000747A # _IO('t', 122) + + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, no transmitting is possible. + """ + if self._break_state: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK) + else: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK) + +else: + class PlatformSpecific(PlatformSpecificBase): + pass + + +# load some constants for later use. +# try to use values from termios, use defaults from linux otherwise +TIOCMGET = getattr(termios, 'TIOCMGET', 0x5415) +TIOCMBIS = getattr(termios, 'TIOCMBIS', 0x5416) +TIOCMBIC = getattr(termios, 'TIOCMBIC', 0x5417) +TIOCMSET = getattr(termios, 'TIOCMSET', 0x5418) + +# TIOCM_LE = getattr(termios, 'TIOCM_LE', 0x001) +TIOCM_DTR = getattr(termios, 'TIOCM_DTR', 0x002) +TIOCM_RTS = getattr(termios, 'TIOCM_RTS', 0x004) +# TIOCM_ST = getattr(termios, 'TIOCM_ST', 0x008) +# TIOCM_SR = getattr(termios, 'TIOCM_SR', 0x010) + +TIOCM_CTS = getattr(termios, 'TIOCM_CTS', 0x020) +TIOCM_CAR = getattr(termios, 'TIOCM_CAR', 0x040) +TIOCM_RNG = getattr(termios, 'TIOCM_RNG', 0x080) +TIOCM_DSR = getattr(termios, 'TIOCM_DSR', 0x100) +TIOCM_CD = getattr(termios, 'TIOCM_CD', TIOCM_CAR) +TIOCM_RI = getattr(termios, 'TIOCM_RI', TIOCM_RNG) +# TIOCM_OUT1 = getattr(termios, 'TIOCM_OUT1', 0x2000) +# TIOCM_OUT2 = getattr(termios, 'TIOCM_OUT2', 0x4000) +if hasattr(termios, 'TIOCINQ'): + TIOCINQ = termios.TIOCINQ +else: + TIOCINQ = getattr(termios, 'FIONREAD', 0x541B) +TIOCOUTQ = getattr(termios, 'TIOCOUTQ', 0x5411) + +TIOCM_zero_str = struct.pack('I', 0) +TIOCM_RTS_str = struct.pack('I', TIOCM_RTS) +TIOCM_DTR_str = struct.pack('I', TIOCM_DTR) + +TIOCSBRK = getattr(termios, 'TIOCSBRK', 0x5427) +TIOCCBRK = getattr(termios, 'TIOCCBRK', 0x5428) + + +class Serial(SerialBase, PlatformSpecific): + """\ + Serial port class POSIX implementation. Serial port configuration is + done with termios and fcntl. Runs on Linux and many other Un*x like + systems. + """ + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened.""" + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + self.fd = None + # open + try: + self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK) + except OSError as msg: + self.fd = None + raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) + #~ fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # set blocking + + self.pipe_abort_read_r, self.pipe_abort_read_w = None, None + self.pipe_abort_write_r, self.pipe_abort_write_w = None, None + + try: + self._reconfigure_port(force_update=True) + + try: + if not self._dsrdtr: + self._update_dtr_state() + if not self._rtscts: + self._update_rts_state() + except IOError as e: + # ignore Invalid argument and Inappropriate ioctl + if e.errno not in (errno.EINVAL, errno.ENOTTY): + raise + + self._reset_input_buffer() + + self.pipe_abort_read_r, self.pipe_abort_read_w = os.pipe() + self.pipe_abort_write_r, self.pipe_abort_write_w = os.pipe() + fcntl.fcntl(self.pipe_abort_read_r, fcntl.F_SETFL, os.O_NONBLOCK) + fcntl.fcntl(self.pipe_abort_write_r, fcntl.F_SETFL, os.O_NONBLOCK) + except BaseException: + try: + os.close(self.fd) + except Exception: + # ignore any exception when closing the port + # also to keep original exception that happened when setting up + pass + self.fd = None + + if self.pipe_abort_read_w is not None: + os.close(self.pipe_abort_read_w) + self.pipe_abort_read_w = None + if self.pipe_abort_read_r is not None: + os.close(self.pipe_abort_read_r) + self.pipe_abort_read_r = None + if self.pipe_abort_write_w is not None: + os.close(self.pipe_abort_write_w) + self.pipe_abort_write_w = None + if self.pipe_abort_write_r is not None: + os.close(self.pipe_abort_write_r) + self.pipe_abort_write_r = None + + raise + + self.is_open = True + + def _reconfigure_port(self, force_update=False): + """Set communication parameters on opened port.""" + if self.fd is None: + raise SerialException("Can only operate on a valid file descriptor") + + # if exclusive lock is requested, create it before we modify anything else + if self._exclusive is not None: + if self._exclusive: + try: + fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError as msg: + raise SerialException(msg.errno, "Could not exclusively lock port {}: {}".format(self._port, msg)) + else: + fcntl.flock(self.fd, fcntl.LOCK_UN) + + custom_baud = None + + vmin = vtime = 0 # timeout is done via select + if self._inter_byte_timeout is not None: + vmin = 1 + vtime = int(self._inter_byte_timeout * 10) + try: + orig_attr = termios.tcgetattr(self.fd) + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr + except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here + raise SerialException("Could not configure port: {}".format(msg)) + # set up raw mode / no echo / binary + cflag |= (termios.CLOCAL | termios.CREAD) + lflag &= ~(termios.ICANON | termios.ECHO | termios.ECHOE | + termios.ECHOK | termios.ECHONL | + termios.ISIG | termios.IEXTEN) # |termios.ECHOPRT + for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk + if hasattr(termios, flag): + lflag &= ~getattr(termios, flag) + + oflag &= ~(termios.OPOST | termios.ONLCR | termios.OCRNL) + iflag &= ~(termios.INLCR | termios.IGNCR | termios.ICRNL | termios.IGNBRK) + if hasattr(termios, 'IUCLC'): + iflag &= ~termios.IUCLC + if hasattr(termios, 'PARMRK'): + iflag &= ~termios.PARMRK + + # setup baud rate + try: + ispeed = ospeed = getattr(termios, 'B{}'.format(self._baudrate)) + except AttributeError: + try: + ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate] + except KeyError: + #~ raise ValueError('Invalid baud rate: %r' % self._baudrate) + + # See if BOTHER is defined for this platform; if it is, use + # this for a speed not defined in the baudrate constants list. + try: + ispeed = ospeed = BOTHER + except NameError: + # may need custom baud rate, it isn't in our list. + ispeed = ospeed = getattr(termios, 'B38400') + + try: + custom_baud = int(self._baudrate) # store for later + except ValueError: + raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate)) + else: + if custom_baud < 0: + raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate)) + + # setup char len + cflag &= ~termios.CSIZE + if self._bytesize == 8: + cflag |= termios.CS8 + elif self._bytesize == 7: + cflag |= termios.CS7 + elif self._bytesize == 6: + cflag |= termios.CS6 + elif self._bytesize == 5: + cflag |= termios.CS5 + else: + raise ValueError('Invalid char len: {!r}'.format(self._bytesize)) + # setup stop bits + if self._stopbits == serial.STOPBITS_ONE: + cflag &= ~(termios.CSTOPB) + elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: + cflag |= (termios.CSTOPB) # XXX same as TWO.. there is no POSIX support for 1.5 + elif self._stopbits == serial.STOPBITS_TWO: + cflag |= (termios.CSTOPB) + else: + raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits)) + # setup parity + iflag &= ~(termios.INPCK | termios.ISTRIP) + if self._parity == serial.PARITY_NONE: + cflag &= ~(termios.PARENB | termios.PARODD | CMSPAR) + elif self._parity == serial.PARITY_EVEN: + cflag &= ~(termios.PARODD | CMSPAR) + cflag |= (termios.PARENB) + elif self._parity == serial.PARITY_ODD: + cflag &= ~CMSPAR + cflag |= (termios.PARENB | termios.PARODD) + elif self._parity == serial.PARITY_MARK and CMSPAR: + cflag |= (termios.PARENB | CMSPAR | termios.PARODD) + elif self._parity == serial.PARITY_SPACE and CMSPAR: + cflag |= (termios.PARENB | CMSPAR) + cflag &= ~(termios.PARODD) + else: + raise ValueError('Invalid parity: {!r}'.format(self._parity)) + # setup flow control + # xonxoff + if hasattr(termios, 'IXANY'): + if self._xonxoff: + iflag |= (termios.IXON | termios.IXOFF) # |termios.IXANY) + else: + iflag &= ~(termios.IXON | termios.IXOFF | termios.IXANY) + else: + if self._xonxoff: + iflag |= (termios.IXON | termios.IXOFF) + else: + iflag &= ~(termios.IXON | termios.IXOFF) + # rtscts + if hasattr(termios, 'CRTSCTS'): + if self._rtscts: + cflag |= (termios.CRTSCTS) + else: + cflag &= ~(termios.CRTSCTS) + elif hasattr(termios, 'CNEW_RTSCTS'): # try it with alternate constant name + if self._rtscts: + cflag |= (termios.CNEW_RTSCTS) + else: + cflag &= ~(termios.CNEW_RTSCTS) + # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails?? + + # buffer + # vmin "minimal number of characters to be read. 0 for non blocking" + if vmin < 0 or vmin > 255: + raise ValueError('Invalid vmin: {!r}'.format(vmin)) + cc[termios.VMIN] = vmin + # vtime + if vtime < 0 or vtime > 255: + raise ValueError('Invalid vtime: {!r}'.format(vtime)) + cc[termios.VTIME] = vtime + # activate settings + if force_update or [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr: + termios.tcsetattr( + self.fd, + termios.TCSANOW, + [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) + + # apply custom baud rate, if any + if custom_baud is not None: + self._set_special_baudrate(custom_baud) + + if self._rs485_mode is not None: + self._set_rs485_mode(self._rs485_mode) + + def close(self): + """Close port""" + if self.is_open: + if self.fd is not None: + os.close(self.fd) + self.fd = None + os.close(self.pipe_abort_read_w) + os.close(self.pipe_abort_read_r) + os.close(self.pipe_abort_write_w) + os.close(self.pipe_abort_write_r) + self.pipe_abort_read_r, self.pipe_abort_read_w = None, None + self.pipe_abort_write_r, self.pipe_abort_write_w = None, None + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of bytes currently in the input buffer.""" + #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str) + s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str) + return struct.unpack('I', s)[0] + + # select based implementation, proved to work on many systems + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + read = bytearray() + timeout = Timeout(self._timeout) + while len(read) < size: + try: + ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left()) + if self.pipe_abort_read_r in ready: + os.read(self.pipe_abort_read_r, 1000) + break + # If select was used with a timeout, and the timeout occurs, it + # returns with empty lists -> thus abort read operation. + # For timeout == 0 (non-blocking operation) also abort when + # there is nothing to read. + if not ready: + break # timeout + buf = os.read(self.fd, size - len(read)) + except OSError as e: + # this is for Python 3.x where select.error is a subclass of + # OSError ignore BlockingIOErrors and EINTR. other errors are shown + # https://www.python.org/dev/peps/pep-0475. + if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('read failed: {}'.format(e)) + except select.error as e: + # this is for Python 2.x + # ignore BlockingIOErrors and EINTR. all errors are shown + # see also http://www.python.org/dev/peps/pep-3151/#select + if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('read failed: {}'.format(e)) + else: + # read should always return some data as select reported it was + # ready to read when we get to this point. + if not buf: + # Disconnected devices, at least on Linux, show the + # behavior that they are always ready to read immediately + # but reading returns nothing. + raise SerialException( + 'device reports readiness to read but returned no data ' + '(device disconnected or multiple access on port?)') + read.extend(buf) + + if timeout.expired(): + break + return bytes(read) + + def cancel_read(self): + if self.is_open: + os.write(self.pipe_abort_read_w, b"x") + + def cancel_write(self): + if self.is_open: + os.write(self.pipe_abort_write_w, b"x") + + def write(self, data): + """Output the given byte string over the serial port.""" + if not self.is_open: + raise PortNotOpenError() + d = to_bytes(data) + tx_len = length = len(d) + timeout = Timeout(self._write_timeout) + while tx_len > 0: + try: + n = os.write(self.fd, d) + if timeout.is_non_blocking: + # Zero timeout indicates non-blocking - simply return the + # number of bytes of data actually written + return n + elif not timeout.is_infinite: + # when timeout is set, use select to wait for being ready + # with the time left as timeout + if timeout.expired(): + raise SerialTimeoutException('Write timeout') + abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], timeout.time_left()) + if abort: + os.read(self.pipe_abort_write_r, 1000) + break + if not ready: + raise SerialTimeoutException('Write timeout') + else: + assert timeout.time_left() is None + # wait for write operation + abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], None) + if abort: + os.read(self.pipe_abort_write_r, 1) + break + if not ready: + raise SerialException('write failed (select)') + d = d[n:] + tx_len -= n + except SerialException: + raise + except OSError as e: + # this is for Python 3.x where select.error is a subclass of + # OSError ignore BlockingIOErrors and EINTR. other errors are shown + # https://www.python.org/dev/peps/pep-0475. + if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('write failed: {}'.format(e)) + except select.error as e: + # this is for Python 2.x + # ignore BlockingIOErrors and EINTR. all errors are shown + # see also http://www.python.org/dev/peps/pep-3151/#select + if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('write failed: {}'.format(e)) + if not timeout.is_non_blocking and timeout.expired(): + raise SerialTimeoutException('Write timeout') + return length - len(d) + + def flush(self): + """\ + Flush of file like objects. In this case, wait until all data + is written. + """ + if not self.is_open: + raise PortNotOpenError() + termios.tcdrain(self.fd) + + def _reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + termios.tcflush(self.fd, termios.TCIFLUSH) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + self._reset_input_buffer() + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and discarding all + that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + termios.tcflush(self.fd, termios.TCOFLUSH) + + def send_break(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self.is_open: + raise PortNotOpenError() + termios.tcsendbreak(self.fd, int(duration / 0.25)) + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if self._rts_state: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if self._dtr_state: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str) + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_CTS != 0 + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_DSR != 0 + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_RI != 0 + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_CD != 0 + + # - - platform specific - - - - + + @property + def out_waiting(self): + """Return the number of bytes currently in the output buffer.""" + #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str) + s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str) + return struct.unpack('I', s)[0] + + def fileno(self): + """\ + For easier use of the serial port instance with select. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + return self.fd + + def set_input_flow_control(self, enable=True): + """\ + Manually control flow - when software flow control is enabled. + This will send XON (true) or XOFF (false) to the other device. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + if enable: + termios.tcflow(self.fd, termios.TCION) + else: + termios.tcflow(self.fd, termios.TCIOFF) + + def set_output_flow_control(self, enable=True): + """\ + Manually control flow of outgoing data - when hardware or software flow + control is enabled. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + if enable: + termios.tcflow(self.fd, termios.TCOON) + else: + termios.tcflow(self.fd, termios.TCOOFF) + + def nonblocking(self): + """DEPRECATED - has no use""" + import warnings + warnings.warn("nonblocking() has no effect, already nonblocking", DeprecationWarning) + + +class PosixPollSerial(Serial): + """\ + Poll based read implementation. Not all systems support poll properly. + However this one has better handling of errors, such as a device + disconnecting while it's in use (e.g. USB-serial unplugged). + """ + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + read = bytearray() + timeout = Timeout(self._timeout) + poll = select.poll() + poll.register(self.fd, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL) + poll.register(self.pipe_abort_read_r, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL) + if size > 0: + while len(read) < size: + # print "\tread(): size",size, "have", len(read) #debug + # wait until device becomes ready to read (or something fails) + for fd, event in poll.poll(None if timeout.is_infinite else (timeout.time_left() * 1000)): + if fd == self.pipe_abort_read_r: + break + if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL): + raise SerialException('device reports error (poll)') + # we don't care if it is select.POLLIN or timeout, that's + # handled below + if fd == self.pipe_abort_read_r: + os.read(self.pipe_abort_read_r, 1000) + break + buf = os.read(self.fd, size - len(read)) + read.extend(buf) + if timeout.expired() \ + or (self._inter_byte_timeout is not None and self._inter_byte_timeout > 0) and not buf: + break # early abort on timeout + return bytes(read) + + +class VTIMESerial(Serial): + """\ + Implement timeout using vtime of tty device instead of using select. + This means that no inter character timeout can be specified and that + the error handling is degraded. + + Overall timeout is disabled when inter-character timeout is used. + + Note that this implementation does NOT support cancel_read(), it will + just ignore that. + """ + + def _reconfigure_port(self, force_update=True): + """Set communication parameters on opened port.""" + super(VTIMESerial, self)._reconfigure_port() + fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # clear O_NONBLOCK + + if self._inter_byte_timeout is not None: + vmin = 1 + vtime = int(self._inter_byte_timeout * 10) + elif self._timeout is None: + vmin = 1 + vtime = 0 + else: + vmin = 0 + vtime = int(self._timeout * 10) + try: + orig_attr = termios.tcgetattr(self.fd) + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr + except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here + raise serial.SerialException("Could not configure port: {}".format(msg)) + + if vtime < 0 or vtime > 255: + raise ValueError('Invalid vtime: {!r}'.format(vtime)) + cc[termios.VTIME] = vtime + cc[termios.VMIN] = vmin + + termios.tcsetattr( + self.fd, + termios.TCSANOW, + [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + read = bytearray() + while len(read) < size: + buf = os.read(self.fd, size - len(read)) + if not buf: + break + read.extend(buf) + return bytes(read) + + # hack to make hasattr return false + cancel_read = property() diff --git a/packages_for_clean_installation /serial/serialposix.pyc b/packages_for_clean_installation /serial/serialposix.pyc new file mode 100644 index 0000000..f008f2b Binary files /dev/null and b/packages_for_clean_installation /serial/serialposix.pyc differ diff --git a/packages_for_clean_installation /serial/serialutil.py b/packages_for_clean_installation /serial/serialutil.py new file mode 100644 index 0000000..789219e --- /dev/null +++ b/packages_for_clean_installation /serial/serialutil.py @@ -0,0 +1,697 @@ +#! python +# +# Base class and support functions used by various backends. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import io +import time + +# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)`` +# isn't returning the contents (very unfortunate). Therefore we need special +# cases and test for it. Ensure that there is a ``memoryview`` object for older +# Python versions. This is easier than making every test dependent on its +# existence. +try: + memoryview +except (NameError, AttributeError): + # implementation does not matter as we do not really use it. + # it just must not inherit from something else we might care for. + class memoryview(object): # pylint: disable=redefined-builtin,invalid-name + pass + +try: + unicode +except (NameError, AttributeError): + unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name + +try: + basestring +except (NameError, AttributeError): + basestring = (str,) # for Python 3, pylint: disable=redefined-builtin,invalid-name + + +# "for byte in data" fails for python3 as it returns ints instead of bytes +def iterbytes(b): + """Iterate over bytes, returning bytes instead of ints (python3)""" + if isinstance(b, memoryview): + b = b.tobytes() + i = 0 + while True: + a = b[i:i + 1] + i += 1 + if a: + yield a + else: + break + + +# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11' +# so a simple ``bytes(sequence)`` doesn't work for all versions +def to_bytes(seq): + """convert a sequence to a bytes type""" + if isinstance(seq, bytes): + return seq + elif isinstance(seq, bytearray): + return bytes(seq) + elif isinstance(seq, memoryview): + return seq.tobytes() + elif isinstance(seq, unicode): + raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq)) + else: + # handle list of integers and bytes (one or more items) for Python 2 and 3 + return bytes(bytearray(seq)) + + +# create control bytes +XON = to_bytes([17]) +XOFF = to_bytes([19]) + +CR = to_bytes([13]) +LF = to_bytes([10]) + + +PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S' +STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2) +FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8) + +PARITY_NAMES = { + PARITY_NONE: 'None', + PARITY_EVEN: 'Even', + PARITY_ODD: 'Odd', + PARITY_MARK: 'Mark', + PARITY_SPACE: 'Space', +} + + +class SerialException(IOError): + """Base class for serial port related exceptions.""" + + +class SerialTimeoutException(SerialException): + """Write timeouts give an exception""" + + +class PortNotOpenError(SerialException): + """Port is not open""" + def __init__(self): + super(PortNotOpenError, self).__init__('Attempting to use a port that is not open') + + +class Timeout(object): + """\ + Abstraction for timeout operations. Using time.monotonic() if available + or time.time() in all other cases. + + The class can also be initialized with 0 or None, in order to support + non-blocking and fully blocking I/O operations. The attributes + is_non_blocking and is_infinite are set accordingly. + """ + if hasattr(time, 'monotonic'): + # Timeout implementation with time.monotonic(). This function is only + # supported by Python 3.3 and above. It returns a time in seconds + # (float) just as time.time(), but is not affected by system clock + # adjustments. + TIME = time.monotonic + else: + # Timeout implementation with time.time(). This is compatible with all + # Python versions but has issues if the clock is adjusted while the + # timeout is running. + TIME = time.time + + def __init__(self, duration): + """Initialize a timeout with given duration""" + self.is_infinite = (duration is None) + self.is_non_blocking = (duration == 0) + self.duration = duration + if duration is not None: + self.target_time = self.TIME() + duration + else: + self.target_time = None + + def expired(self): + """Return a boolean, telling if the timeout has expired""" + return self.target_time is not None and self.time_left() <= 0 + + def time_left(self): + """Return how many seconds are left until the timeout expires""" + if self.is_non_blocking: + return 0 + elif self.is_infinite: + return None + else: + delta = self.target_time - self.TIME() + if delta > self.duration: + # clock jumped, recalculate + self.target_time = self.TIME() + self.duration + return self.duration + else: + return max(0, delta) + + def restart(self, duration): + """\ + Restart a timeout, only supported if a timeout was already set up + before. + """ + self.duration = duration + self.target_time = self.TIME() + duration + + +class SerialBase(io.RawIOBase): + """\ + Serial port base class. Provides __init__ function and properties to + get/set port settings. + """ + + # default values, may be overridden in subclasses that do not support all values + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, + 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, + 3000000, 3500000, 4000000) + BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS) + PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE) + STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO) + + def __init__(self, + port=None, + baudrate=9600, + bytesize=EIGHTBITS, + parity=PARITY_NONE, + stopbits=STOPBITS_ONE, + timeout=None, + xonxoff=False, + rtscts=False, + write_timeout=None, + dsrdtr=False, + inter_byte_timeout=None, + exclusive=None, + **kwargs): + """\ + Initialize comm port object. If a "port" is given, then the port will be + opened immediately. Otherwise a Serial port object in closed state + is returned. + """ + + self.is_open = False + self.portstr = None + self.name = None + # correct values are assigned below through properties + self._port = None + self._baudrate = None + self._bytesize = None + self._parity = None + self._stopbits = None + self._timeout = None + self._write_timeout = None + self._xonxoff = None + self._rtscts = None + self._dsrdtr = None + self._inter_byte_timeout = None + self._rs485_mode = None # disabled by default + self._rts_state = True + self._dtr_state = True + self._break_state = False + self._exclusive = None + + # assign values using get/set methods using the properties feature + self.port = port + self.baudrate = baudrate + self.bytesize = bytesize + self.parity = parity + self.stopbits = stopbits + self.timeout = timeout + self.write_timeout = write_timeout + self.xonxoff = xonxoff + self.rtscts = rtscts + self.dsrdtr = dsrdtr + self.inter_byte_timeout = inter_byte_timeout + self.exclusive = exclusive + + # watch for backward compatible kwargs + if 'writeTimeout' in kwargs: + self.write_timeout = kwargs.pop('writeTimeout') + if 'interCharTimeout' in kwargs: + self.inter_byte_timeout = kwargs.pop('interCharTimeout') + if kwargs: + raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs)) + + if port is not None: + self.open() + + # - - - - - - - - - - - - - - - - - - - - - - - - + + # to be implemented by subclasses: + # def open(self): + # def close(self): + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def port(self): + """\ + Get the current port setting. The value that was passed on init or using + setPort() is passed back. + """ + return self._port + + @port.setter + def port(self, port): + """\ + Change the port. + """ + if port is not None and not isinstance(port, basestring): + raise ValueError('"port" must be None or a string, not {}'.format(type(port))) + was_open = self.is_open + if was_open: + self.close() + self.portstr = port + self._port = port + self.name = self.portstr + if was_open: + self.open() + + @property + def baudrate(self): + """Get the current baud rate setting.""" + return self._baudrate + + @baudrate.setter + def baudrate(self, baudrate): + """\ + Change baud rate. It raises a ValueError if the port is open and the + baud rate is not possible. If the port is closed, then the value is + accepted and the exception is raised when the port is opened. + """ + try: + b = int(baudrate) + except TypeError: + raise ValueError("Not a valid baudrate: {!r}".format(baudrate)) + else: + if b < 0: + raise ValueError("Not a valid baudrate: {!r}".format(baudrate)) + self._baudrate = b + if self.is_open: + self._reconfigure_port() + + @property + def bytesize(self): + """Get the current byte size setting.""" + return self._bytesize + + @bytesize.setter + def bytesize(self, bytesize): + """Change byte size.""" + if bytesize not in self.BYTESIZES: + raise ValueError("Not a valid byte size: {!r}".format(bytesize)) + self._bytesize = bytesize + if self.is_open: + self._reconfigure_port() + + @property + def exclusive(self): + """Get the current exclusive access setting.""" + return self._exclusive + + @exclusive.setter + def exclusive(self, exclusive): + """Change the exclusive access setting.""" + self._exclusive = exclusive + if self.is_open: + self._reconfigure_port() + + @property + def parity(self): + """Get the current parity setting.""" + return self._parity + + @parity.setter + def parity(self, parity): + """Change parity setting.""" + if parity not in self.PARITIES: + raise ValueError("Not a valid parity: {!r}".format(parity)) + self._parity = parity + if self.is_open: + self._reconfigure_port() + + @property + def stopbits(self): + """Get the current stop bits setting.""" + return self._stopbits + + @stopbits.setter + def stopbits(self, stopbits): + """Change stop bits size.""" + if stopbits not in self.STOPBITS: + raise ValueError("Not a valid stop bit size: {!r}".format(stopbits)) + self._stopbits = stopbits + if self.is_open: + self._reconfigure_port() + + @property + def timeout(self): + """Get the current timeout setting.""" + return self._timeout + + @timeout.setter + def timeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + try: + timeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + if timeout < 0: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + self._timeout = timeout + if self.is_open: + self._reconfigure_port() + + @property + def write_timeout(self): + """Get the current timeout setting.""" + return self._write_timeout + + @write_timeout.setter + def write_timeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + if timeout < 0: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + try: + timeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + + self._write_timeout = timeout + if self.is_open: + self._reconfigure_port() + + @property + def inter_byte_timeout(self): + """Get the current inter-character timeout setting.""" + return self._inter_byte_timeout + + @inter_byte_timeout.setter + def inter_byte_timeout(self, ic_timeout): + """Change inter-byte timeout setting.""" + if ic_timeout is not None: + if ic_timeout < 0: + raise ValueError("Not a valid timeout: {!r}".format(ic_timeout)) + try: + ic_timeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: {!r}".format(ic_timeout)) + + self._inter_byte_timeout = ic_timeout + if self.is_open: + self._reconfigure_port() + + @property + def xonxoff(self): + """Get the current XON/XOFF setting.""" + return self._xonxoff + + @xonxoff.setter + def xonxoff(self, xonxoff): + """Change XON/XOFF setting.""" + self._xonxoff = xonxoff + if self.is_open: + self._reconfigure_port() + + @property + def rtscts(self): + """Get the current RTS/CTS flow control setting.""" + return self._rtscts + + @rtscts.setter + def rtscts(self, rtscts): + """Change RTS/CTS flow control setting.""" + self._rtscts = rtscts + if self.is_open: + self._reconfigure_port() + + @property + def dsrdtr(self): + """Get the current DSR/DTR flow control setting.""" + return self._dsrdtr + + @dsrdtr.setter + def dsrdtr(self, dsrdtr=None): + """Change DsrDtr flow control setting.""" + if dsrdtr is None: + # if not set, keep backwards compatibility and follow rtscts setting + self._dsrdtr = self._rtscts + else: + # if defined independently, follow its value + self._dsrdtr = dsrdtr + if self.is_open: + self._reconfigure_port() + + @property + def rts(self): + return self._rts_state + + @rts.setter + def rts(self, value): + self._rts_state = value + if self.is_open: + self._update_rts_state() + + @property + def dtr(self): + return self._dtr_state + + @dtr.setter + def dtr(self, value): + self._dtr_state = value + if self.is_open: + self._update_dtr_state() + + @property + def break_condition(self): + return self._break_state + + @break_condition.setter + def break_condition(self, value): + self._break_state = value + if self.is_open: + self._update_break_state() + + # - - - - - - - - - - - - - - - - - - - - - - - - + # functions useful for RS-485 adapters + + @property + def rs485_mode(self): + """\ + Enable RS485 mode and apply new settings, set to None to disable. + See serial.rs485.RS485Settings for more info about the value. + """ + return self._rs485_mode + + @rs485_mode.setter + def rs485_mode(self, rs485_settings): + self._rs485_mode = rs485_settings + if self.is_open: + self._reconfigure_port() + + # - - - - - - - - - - - - - - - - - - - - - - - - + + _SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff', + 'dsrdtr', 'rtscts', 'timeout', 'write_timeout', + 'inter_byte_timeout') + + def get_settings(self): + """\ + Get current port settings as a dictionary. For use with + apply_settings(). + """ + return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS]) + + def apply_settings(self, d): + """\ + Apply stored settings from a dictionary returned from + get_settings(). It's allowed to delete keys from the dictionary. These + values will simply left unchanged. + """ + for key in self._SAVED_SETTINGS: + if key in d and d[key] != getattr(self, '_' + key): # check against internal "_" value + setattr(self, key, d[key]) # set non "_" value to use properties write function + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def __repr__(self): + """String representation of the current port settings and its state.""" + return '{name}(port={p.portstr!r}, ' \ + 'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \ + 'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \ + 'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format( + name=self.__class__.__name__, id=id(self), p=self) + + # - - - - - - - - - - - - - - - - - - - - - - - - + # compatibility with io library + # pylint: disable=invalid-name,missing-docstring + + def readable(self): + return True + + def writable(self): + return True + + def seekable(self): + return False + + def readinto(self, b): + data = self.read(len(b)) + n = len(data) + try: + b[:n] = data + except TypeError as err: + import array + if not isinstance(b, array.array): + raise err + b[:n] = array.array('b', data) + return n + + # - - - - - - - - - - - - - - - - - - - - - - - - + # context manager + + def __enter__(self): + if self._port is not None and not self.is_open: + self.open() + return self + + def __exit__(self, *args, **kwargs): + self.close() + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def send_break(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self.is_open: + raise PortNotOpenError() + self.break_condition = True + time.sleep(duration) + self.break_condition = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + # backwards compatibility / deprecated functions + + def flushInput(self): + self.reset_input_buffer() + + def flushOutput(self): + self.reset_output_buffer() + + def inWaiting(self): + return self.in_waiting + + def sendBreak(self, duration=0.25): + self.send_break(duration) + + def setRTS(self, value=1): + self.rts = value + + def setDTR(self, value=1): + self.dtr = value + + def getCTS(self): + return self.cts + + def getDSR(self): + return self.dsr + + def getRI(self): + return self.ri + + def getCD(self): + return self.cd + + def setPort(self, port): + self.port = port + + @property + def writeTimeout(self): + return self.write_timeout + + @writeTimeout.setter + def writeTimeout(self, timeout): + self.write_timeout = timeout + + @property + def interCharTimeout(self): + return self.inter_byte_timeout + + @interCharTimeout.setter + def interCharTimeout(self, interCharTimeout): + self.inter_byte_timeout = interCharTimeout + + def getSettingsDict(self): + return self.get_settings() + + def applySettingsDict(self, d): + self.apply_settings(d) + + def isOpen(self): + return self.is_open + + # - - - - - - - - - - - - - - - - - - - - - - - - + # additional functionality + + def read_all(self): + """\ + Read all bytes currently available in the buffer of the OS. + """ + return self.read(self.in_waiting) + + def read_until(self, expected=LF, size=None): + """\ + Read until an expected sequence is found ('\n' by default), the size + is exceeded or until timeout occurs. + """ + lenterm = len(expected) + line = bytearray() + timeout = Timeout(self._timeout) + while True: + c = self.read(1) + if c: + line += c + if line[-lenterm:] == expected: + break + if size is not None and len(line) >= size: + break + else: + break + if timeout.expired(): + break + return bytes(line) + + def iread_until(self, *args, **kwargs): + """\ + Read lines, implemented as generator. It will raise StopIteration on + timeout (empty read). + """ + while True: + line = self.read_until(*args, **kwargs) + if not line: + break + yield line + + +# - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + import sys + s = SerialBase() + sys.stdout.write('port name: {}\n'.format(s.name)) + sys.stdout.write('baud rates: {}\n'.format(s.BAUDRATES)) + sys.stdout.write('byte sizes: {}\n'.format(s.BYTESIZES)) + sys.stdout.write('parities: {}\n'.format(s.PARITIES)) + sys.stdout.write('stop bits: {}\n'.format(s.STOPBITS)) + sys.stdout.write('{}\n'.format(s)) diff --git a/packages_for_clean_installation /serial/serialutil.pyc b/packages_for_clean_installation /serial/serialutil.pyc new file mode 100644 index 0000000..3800ba7 Binary files /dev/null and b/packages_for_clean_installation /serial/serialutil.pyc differ diff --git a/packages_for_clean_installation /serial/serialwin32.py b/packages_for_clean_installation /serial/serialwin32.py new file mode 100644 index 0000000..65f24d7 --- /dev/null +++ b/packages_for_clean_installation /serial/serialwin32.py @@ -0,0 +1,514 @@ +#! python +# +# backend for Windows ("win32" incl. 32/64 bit support) +# +# (C) 2001-2020 Chris Liechti +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# SPDX-License-Identifier: BSD-3-Clause +# +# Initial patch to use ctypes by Giovanni Bajo + +from __future__ import absolute_import + +# pylint: disable=invalid-name,too-few-public-methods +import ctypes +import time +from serial import win32 + +import serial +from serial.serialutil import SerialBase, SerialException, to_bytes, PortNotOpenError, SerialTimeoutException + + +class Serial(SerialBase): + """Serial port implementation for Win32 based on ctypes.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def __init__(self, *args, **kwargs): + self._port_handle = None + self._overlapped_read = None + self._overlapped_write = None + super(Serial, self).__init__(*args, **kwargs) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + # the "\\.\COMx" format is required for devices other than COM1-COM8 + # not all versions of windows seem to support this properly + # so that the first few ports are used with the DOS device name + port = self.name + try: + if port.upper().startswith('COM') and int(port[3:]) > 8: + port = '\\\\.\\' + port + except ValueError: + # for like COMnotanumber + pass + self._port_handle = win32.CreateFile( + port, + win32.GENERIC_READ | win32.GENERIC_WRITE, + 0, # exclusive access + None, # no security + win32.OPEN_EXISTING, + win32.FILE_ATTRIBUTE_NORMAL | win32.FILE_FLAG_OVERLAPPED, + 0) + if self._port_handle == win32.INVALID_HANDLE_VALUE: + self._port_handle = None # 'cause __del__ is called anyway + raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError())) + + try: + self._overlapped_read = win32.OVERLAPPED() + self._overlapped_read.hEvent = win32.CreateEvent(None, 1, 0, None) + self._overlapped_write = win32.OVERLAPPED() + #~ self._overlapped_write.hEvent = win32.CreateEvent(None, 1, 0, None) + self._overlapped_write.hEvent = win32.CreateEvent(None, 0, 0, None) + + # Setup a 4k buffer + win32.SetupComm(self._port_handle, 4096, 4096) + + # Save original timeout values: + self._orgTimeouts = win32.COMMTIMEOUTS() + win32.GetCommTimeouts(self._port_handle, ctypes.byref(self._orgTimeouts)) + + self._reconfigure_port() + + # Clear buffers: + # Remove anything that was there + win32.PurgeComm( + self._port_handle, + win32.PURGE_TXCLEAR | win32.PURGE_TXABORT | + win32.PURGE_RXCLEAR | win32.PURGE_RXABORT) + except: + try: + self._close() + except: + # ignore any exception when closing the port + # also to keep original exception that happened when setting up + pass + self._port_handle = None + raise + else: + self.is_open = True + + def _reconfigure_port(self): + """Set communication parameters on opened port.""" + if not self._port_handle: + raise SerialException("Can only operate on a valid port handle") + + # Set Windows timeout values + # timeouts is a tuple with the following items: + # (ReadIntervalTimeout,ReadTotalTimeoutMultiplier, + # ReadTotalTimeoutConstant,WriteTotalTimeoutMultiplier, + # WriteTotalTimeoutConstant) + timeouts = win32.COMMTIMEOUTS() + if self._timeout is None: + pass # default of all zeros is OK + elif self._timeout == 0: + timeouts.ReadIntervalTimeout = win32.MAXDWORD + else: + timeouts.ReadTotalTimeoutConstant = max(int(self._timeout * 1000), 1) + if self._timeout != 0 and self._inter_byte_timeout is not None: + timeouts.ReadIntervalTimeout = max(int(self._inter_byte_timeout * 1000), 1) + + if self._write_timeout is None: + pass + elif self._write_timeout == 0: + timeouts.WriteTotalTimeoutConstant = win32.MAXDWORD + else: + timeouts.WriteTotalTimeoutConstant = max(int(self._write_timeout * 1000), 1) + win32.SetCommTimeouts(self._port_handle, ctypes.byref(timeouts)) + + win32.SetCommMask(self._port_handle, win32.EV_ERR) + + # Setup the connection info. + # Get state and modify it: + comDCB = win32.DCB() + win32.GetCommState(self._port_handle, ctypes.byref(comDCB)) + comDCB.BaudRate = self._baudrate + + if self._bytesize == serial.FIVEBITS: + comDCB.ByteSize = 5 + elif self._bytesize == serial.SIXBITS: + comDCB.ByteSize = 6 + elif self._bytesize == serial.SEVENBITS: + comDCB.ByteSize = 7 + elif self._bytesize == serial.EIGHTBITS: + comDCB.ByteSize = 8 + else: + raise ValueError("Unsupported number of data bits: {!r}".format(self._bytesize)) + + if self._parity == serial.PARITY_NONE: + comDCB.Parity = win32.NOPARITY + comDCB.fParity = 0 # Disable Parity Check + elif self._parity == serial.PARITY_EVEN: + comDCB.Parity = win32.EVENPARITY + comDCB.fParity = 1 # Enable Parity Check + elif self._parity == serial.PARITY_ODD: + comDCB.Parity = win32.ODDPARITY + comDCB.fParity = 1 # Enable Parity Check + elif self._parity == serial.PARITY_MARK: + comDCB.Parity = win32.MARKPARITY + comDCB.fParity = 1 # Enable Parity Check + elif self._parity == serial.PARITY_SPACE: + comDCB.Parity = win32.SPACEPARITY + comDCB.fParity = 1 # Enable Parity Check + else: + raise ValueError("Unsupported parity mode: {!r}".format(self._parity)) + + if self._stopbits == serial.STOPBITS_ONE: + comDCB.StopBits = win32.ONESTOPBIT + elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: + comDCB.StopBits = win32.ONE5STOPBITS + elif self._stopbits == serial.STOPBITS_TWO: + comDCB.StopBits = win32.TWOSTOPBITS + else: + raise ValueError("Unsupported number of stop bits: {!r}".format(self._stopbits)) + + comDCB.fBinary = 1 # Enable Binary Transmission + # Char. w/ Parity-Err are replaced with 0xff (if fErrorChar is set to TRUE) + if self._rs485_mode is None: + if self._rtscts: + comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE + else: + comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE + comDCB.fOutxCtsFlow = self._rtscts + else: + # checks for unsupported settings + # XXX verify if platform really does not have a setting for those + if not self._rs485_mode.rts_level_for_tx: + raise ValueError( + 'Unsupported value for RS485Settings.rts_level_for_tx: {!r} (only True is allowed)'.format( + self._rs485_mode.rts_level_for_tx,)) + if self._rs485_mode.rts_level_for_rx: + raise ValueError( + 'Unsupported value for RS485Settings.rts_level_for_rx: {!r} (only False is allowed)'.format( + self._rs485_mode.rts_level_for_rx,)) + if self._rs485_mode.delay_before_tx is not None: + raise ValueError( + 'Unsupported value for RS485Settings.delay_before_tx: {!r} (only None is allowed)'.format( + self._rs485_mode.delay_before_tx,)) + if self._rs485_mode.delay_before_rx is not None: + raise ValueError( + 'Unsupported value for RS485Settings.delay_before_rx: {!r} (only None is allowed)'.format( + self._rs485_mode.delay_before_rx,)) + if self._rs485_mode.loopback: + raise ValueError( + 'Unsupported value for RS485Settings.loopback: {!r} (only False is allowed)'.format( + self._rs485_mode.loopback,)) + comDCB.fRtsControl = win32.RTS_CONTROL_TOGGLE + comDCB.fOutxCtsFlow = 0 + + if self._dsrdtr: + comDCB.fDtrControl = win32.DTR_CONTROL_HANDSHAKE + else: + comDCB.fDtrControl = win32.DTR_CONTROL_ENABLE if self._dtr_state else win32.DTR_CONTROL_DISABLE + comDCB.fOutxDsrFlow = self._dsrdtr + comDCB.fOutX = self._xonxoff + comDCB.fInX = self._xonxoff + comDCB.fNull = 0 + comDCB.fErrorChar = 0 + comDCB.fAbortOnError = 0 + comDCB.XonChar = serial.XON + comDCB.XoffChar = serial.XOFF + + if not win32.SetCommState(self._port_handle, ctypes.byref(comDCB)): + raise SerialException( + 'Cannot configure port, something went wrong. ' + 'Original message: {!r}'.format(ctypes.WinError())) + + #~ def __del__(self): + #~ self.close() + + def _close(self): + """internal close port helper""" + if self._port_handle is not None: + # Restore original timeout values: + win32.SetCommTimeouts(self._port_handle, self._orgTimeouts) + if self._overlapped_read is not None: + self.cancel_read() + win32.CloseHandle(self._overlapped_read.hEvent) + self._overlapped_read = None + if self._overlapped_write is not None: + self.cancel_write() + win32.CloseHandle(self._overlapped_write.hEvent) + self._overlapped_write = None + win32.CloseHandle(self._port_handle) + self._port_handle = None + + def close(self): + """Close port""" + if self.is_open: + self._close() + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of bytes currently in the input buffer.""" + flags = win32.DWORD() + comstat = win32.COMSTAT() + if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): + raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) + return comstat.cbInQue + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + if size > 0: + win32.ResetEvent(self._overlapped_read.hEvent) + flags = win32.DWORD() + comstat = win32.COMSTAT() + if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): + raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) + n = min(comstat.cbInQue, size) if self.timeout == 0 else size + if n > 0: + buf = ctypes.create_string_buffer(n) + rc = win32.DWORD() + read_ok = win32.ReadFile( + self._port_handle, + buf, + n, + ctypes.byref(rc), + ctypes.byref(self._overlapped_read)) + if not read_ok and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + raise SerialException("ReadFile failed ({!r})".format(ctypes.WinError())) + result_ok = win32.GetOverlappedResult( + self._port_handle, + ctypes.byref(self._overlapped_read), + ctypes.byref(rc), + True) + if not result_ok: + if win32.GetLastError() != win32.ERROR_OPERATION_ABORTED: + raise SerialException("GetOverlappedResult failed ({!r})".format(ctypes.WinError())) + read = buf.raw[:rc.value] + else: + read = bytes() + else: + read = bytes() + return bytes(read) + + def write(self, data): + """Output the given byte string over the serial port.""" + if not self.is_open: + raise PortNotOpenError() + #~ if not isinstance(data, (bytes, bytearray)): + #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + # convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview + data = to_bytes(data) + if data: + #~ win32event.ResetEvent(self._overlapped_write.hEvent) + n = win32.DWORD() + success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write) + if self._write_timeout != 0: # if blocking (None) or w/ write timeout (>0) + if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) + + # Wait for the write to complete. + #~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE) + win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True) + if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED: + return n.value # canceled IO is no error + if n.value != len(data): + raise SerialTimeoutException('Write timeout') + return n.value + else: + errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError() + if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_OPERATION_ABORTED): + return 0 + elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + # no info on true length provided by OS function in async mode + return len(data) + else: + raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) + else: + return 0 + + def writePN(self, data): + """Output the given byte string over the serial port.""" + if not self.is_open: + raise PortNotOpenError() + #~ if not isinstance(data, (bytes, bytearray)): + #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + # convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview + data = to_bytes(data) + if data: + #~ win32event.ResetEvent(self._overlapped_write.hEvent) + n = win32.DWORD() + success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write) + if self._write_timeout != 0: # if blocking (None) or w/ write timeout (>0) + if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) + + # Wait for the write to complete. + #~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE) + win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True) + if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED: + return n.value # canceled IO is no error + if n.value != len(data): + raise SerialTimeoutException('Write timeout') + return n.value + else: + errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError() + if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_OPERATION_ABORTED): + return 0 + elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + # no info on true length provided by OS function in async mode + return len(data) + else: + raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) + else: + return 0 + + def flush(self): + """\ + Flush of file like objects. In this case, wait until all data + is written. + """ + while self.out_waiting: + time.sleep(0.05) + # XXX could also use WaitCommEvent with mask EV_TXEMPTY, but it would + # require overlapped IO and it's also only possible to set a single mask + # on the port--- + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT) + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and discarding all + that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT) + + def _update_break_state(self): + """Set break: Controls TXD. When active, to transmitting is possible.""" + if not self.is_open: + raise PortNotOpenError() + if self._break_state: + win32.SetCommBreak(self._port_handle) + else: + win32.ClearCommBreak(self._port_handle) + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if self._rts_state: + win32.EscapeCommFunction(self._port_handle, win32.SETRTS) + else: + win32.EscapeCommFunction(self._port_handle, win32.CLRRTS) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if self._dtr_state: + win32.EscapeCommFunction(self._port_handle, win32.SETDTR) + else: + win32.EscapeCommFunction(self._port_handle, win32.CLRDTR) + + def _GetCommModemStatus(self): + if not self.is_open: + raise PortNotOpenError() + stat = win32.DWORD() + win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat)) + return stat.value + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + return win32.MS_CTS_ON & self._GetCommModemStatus() != 0 + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + return win32.MS_DSR_ON & self._GetCommModemStatus() != 0 + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + return win32.MS_RING_ON & self._GetCommModemStatus() != 0 + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + return win32.MS_RLSD_ON & self._GetCommModemStatus() != 0 + + # - - platform specific - - - - + + def set_buffer_size(self, rx_size=4096, tx_size=None): + """\ + Recommend a buffer size to the driver (device driver can ignore this + value). Must be called after the port is opened. + """ + if tx_size is None: + tx_size = rx_size + win32.SetupComm(self._port_handle, rx_size, tx_size) + + def set_output_flow_control(self, enable=True): + """\ + Manually control flow - when software flow control is enabled. + This will do the same as if XON (true) or XOFF (false) are received + from the other device and control the transmission accordingly. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + if enable: + win32.EscapeCommFunction(self._port_handle, win32.SETXON) + else: + win32.EscapeCommFunction(self._port_handle, win32.SETXOFF) + + @property + def out_waiting(self): + """Return how many bytes the in the outgoing buffer""" + flags = win32.DWORD() + comstat = win32.COMSTAT() + if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): + raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) + return comstat.cbOutQue + + def _cancel_overlapped_io(self, overlapped): + """Cancel a blocking read operation, may be called from other thread""" + # check if read operation is pending + rc = win32.DWORD() + err = win32.GetOverlappedResult( + self._port_handle, + ctypes.byref(overlapped), + ctypes.byref(rc), + False) + if not err and win32.GetLastError() in (win32.ERROR_IO_PENDING, win32.ERROR_IO_INCOMPLETE): + # cancel, ignoring any errors (e.g. it may just have finished on its own) + win32.CancelIoEx(self._port_handle, overlapped) + + def cancel_read(self): + """Cancel a blocking read operation, may be called from other thread""" + self._cancel_overlapped_io(self._overlapped_read) + + def cancel_write(self): + """Cancel a blocking write operation, may be called from other thread""" + self._cancel_overlapped_io(self._overlapped_write) + + @SerialBase.exclusive.setter + def exclusive(self, exclusive): + """Change the exclusive access setting.""" + if exclusive is not None and not exclusive: + raise ValueError('win32 only supports exclusive access (not: {})'.format(exclusive)) + else: + serial.SerialBase.exclusive.__set__(self, exclusive) diff --git a/packages_for_clean_installation /serial/serialwin32.pyc b/packages_for_clean_installation /serial/serialwin32.pyc new file mode 100644 index 0000000..f994e45 Binary files /dev/null and b/packages_for_clean_installation /serial/serialwin32.pyc differ diff --git a/packages_for_clean_installation /serial/threaded/__init__.py b/packages_for_clean_installation /serial/threaded/__init__.py new file mode 100644 index 0000000..b8940b6 --- /dev/null +++ b/packages_for_clean_installation /serial/threaded/__init__.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +# +# Working with threading and pySerial +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015-2016 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +"""\ +Support threading with serial ports. +""" +from __future__ import absolute_import + +import serial +import threading + + +class Protocol(object): + """\ + Protocol as used by the ReaderThread. This base class provides empty + implementations of all methods. + """ + + def connection_made(self, transport): + """Called when reader thread is started""" + + def data_received(self, data): + """Called with snippets received from the serial port""" + + def connection_lost(self, exc): + """\ + Called when the serial port is closed or the reader loop terminated + otherwise. + """ + if isinstance(exc, Exception): + raise exc + + +class Packetizer(Protocol): + """ + Read binary packets from serial port. Packets are expected to be terminated + with a TERMINATOR byte (null byte by default). + + The class also keeps track of the transport. + """ + + TERMINATOR = b'\0' + + def __init__(self): + self.buffer = bytearray() + self.transport = None + + def connection_made(self, transport): + """Store transport""" + self.transport = transport + + def connection_lost(self, exc): + """Forget transport""" + self.transport = None + super(Packetizer, self).connection_lost(exc) + + def data_received(self, data): + """Buffer received data, find TERMINATOR, call handle_packet""" + self.buffer.extend(data) + while self.TERMINATOR in self.buffer: + packet, self.buffer = self.buffer.split(self.TERMINATOR, 1) + self.handle_packet(packet) + + def handle_packet(self, packet): + """Process packets - to be overridden by subclassing""" + raise NotImplementedError('please implement functionality in handle_packet') + + +class FramedPacket(Protocol): + """ + Read binary packets. Packets are expected to have a start and stop marker. + + The class also keeps track of the transport. + """ + + START = b'(' + STOP = b')' + + def __init__(self): + self.packet = bytearray() + self.in_packet = False + self.transport = None + + def connection_made(self, transport): + """Store transport""" + self.transport = transport + + def connection_lost(self, exc): + """Forget transport""" + self.transport = None + self.in_packet = False + del self.packet[:] + super(FramedPacket, self).connection_lost(exc) + + def data_received(self, data): + """Find data enclosed in START/STOP, call handle_packet""" + for byte in serial.iterbytes(data): + if byte == self.START: + self.in_packet = True + elif byte == self.STOP: + self.in_packet = False + self.handle_packet(bytes(self.packet)) # make read-only copy + del self.packet[:] + elif self.in_packet: + self.packet.extend(byte) + else: + self.handle_out_of_packet_data(byte) + + def handle_packet(self, packet): + """Process packets - to be overridden by subclassing""" + raise NotImplementedError('please implement functionality in handle_packet') + + def handle_out_of_packet_data(self, data): + """Process data that is received outside of packets""" + pass + + +class LineReader(Packetizer): + """ + Read and write (Unicode) lines from/to serial port. + The encoding is applied. + """ + + TERMINATOR = b'\r\n' + ENCODING = 'utf-8' + UNICODE_HANDLING = 'replace' + + def handle_packet(self, packet): + self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING)) + + def handle_line(self, line): + """Process one line - to be overridden by subclassing""" + raise NotImplementedError('please implement functionality in handle_line') + + def write_line(self, text): + """ + Write text to the transport. ``text`` is a Unicode string and the encoding + is applied before sending ans also the newline is append. + """ + # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call + self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR) + + +class ReaderThread(threading.Thread): + """\ + Implement a serial port read loop and dispatch to a Protocol instance (like + the asyncio.Protocol) but do it with threads. + + Calls to close() will close the serial port but it is also possible to just + stop() this thread and continue the serial port instance otherwise. + """ + + def __init__(self, serial_instance, protocol_factory): + """\ + Initialize thread. + + Note that the serial_instance' timeout is set to one second! + Other settings are not changed. + """ + super(ReaderThread, self).__init__() + self.daemon = True + self.serial = serial_instance + self.protocol_factory = protocol_factory + self.alive = True + self._lock = threading.Lock() + self._connection_made = threading.Event() + self.protocol = None + + def stop(self): + """Stop the reader thread""" + self.alive = False + if hasattr(self.serial, 'cancel_read'): + self.serial.cancel_read() + self.join(2) + + def run(self): + """Reader loop""" + if not hasattr(self.serial, 'cancel_read'): + self.serial.timeout = 1 + self.protocol = self.protocol_factory() + try: + self.protocol.connection_made(self) + except Exception as e: + self.alive = False + self.protocol.connection_lost(e) + self._connection_made.set() + return + error = None + self._connection_made.set() + while self.alive and self.serial.is_open: + try: + # read all that is there or wait for one byte (blocking) + data = self.serial.read(self.serial.in_waiting or 1) + except serial.SerialException as e: + # probably some I/O problem such as disconnected USB serial + # adapters -> exit + error = e + break + else: + if data: + # make a separated try-except for called user code + try: + self.protocol.data_received(data) + except Exception as e: + error = e + break + self.alive = False + self.protocol.connection_lost(error) + self.protocol = None + + def write(self, data): + """Thread safe writing (uses lock)""" + with self._lock: + return self.serial.write(data) + + def close(self): + """Close the serial port and exit reader thread (uses lock)""" + # use the lock to let other threads finish writing + with self._lock: + # first stop reading, so that closing can be done on idle port + self.stop() + self.serial.close() + + def connect(self): + """ + Wait until connection is set up and return the transport and protocol + instances. + """ + if self.alive: + self._connection_made.wait() + if not self.alive: + raise RuntimeError('connection_lost already called') + return (self, self.protocol) + else: + raise RuntimeError('already stopped') + + # - - context manager, returns protocol + + def __enter__(self): + """\ + Enter context handler. May raise RuntimeError in case the connection + could not be created. + """ + self.start() + self._connection_made.wait() + if not self.alive: + raise RuntimeError('connection_lost already called') + return self.protocol + + def __exit__(self, exc_type, exc_val, exc_tb): + """Leave context: close port""" + self.close() + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + # pylint: disable=wrong-import-position + import sys + import time + import traceback + + #~ PORT = 'spy:///dev/ttyUSB0' + PORT = 'loop://' + + class PrintLines(LineReader): + def connection_made(self, transport): + super(PrintLines, self).connection_made(transport) + sys.stdout.write('port opened\n') + self.write_line('hello world') + + def handle_line(self, data): + sys.stdout.write('line received: {!r}\n'.format(data)) + + def connection_lost(self, exc): + if exc: + traceback.print_exc(exc) + sys.stdout.write('port closed\n') + + ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) + with ReaderThread(ser, PrintLines) as protocol: + protocol.write_line('hello') + time.sleep(2) + + # alternative usage + ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) + t = ReaderThread(ser, PrintLines) + t.start() + transport, protocol = t.connect() + protocol.write_line('hello') + time.sleep(2) + t.close() diff --git a/packages_for_clean_installation /serial/threaded/__pycache__/__init__.cpython-38.pyc b/packages_for_clean_installation /serial/threaded/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..c8d3bca Binary files /dev/null and b/packages_for_clean_installation /serial/threaded/__pycache__/__init__.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__init__.py b/packages_for_clean_installation /serial/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages_for_clean_installation /serial/tools/__init__.pyc b/packages_for_clean_installation /serial/tools/__init__.pyc new file mode 100644 index 0000000..318d7d9 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__init__.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/__init__.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..beadb65 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/__init__.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/hexlify_codec.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/hexlify_codec.cpython-38.pyc new file mode 100644 index 0000000..f17109e Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/hexlify_codec.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/list_ports.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/list_ports.cpython-38.pyc new file mode 100644 index 0000000..3c5bdb1 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/list_ports.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/list_ports_common.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_common.cpython-38.pyc new file mode 100644 index 0000000..334bcb9 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_common.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/list_ports_linux.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_linux.cpython-38.pyc new file mode 100644 index 0000000..3f90f04 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_linux.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/list_ports_osx.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_osx.cpython-38.pyc new file mode 100644 index 0000000..7b65e4b Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_osx.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/list_ports_posix.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_posix.cpython-38.pyc new file mode 100644 index 0000000..1f51eb0 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_posix.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/list_ports_windows.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_windows.cpython-38.pyc new file mode 100644 index 0000000..e079ef2 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/list_ports_windows.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/__pycache__/miniterm.cpython-38.pyc b/packages_for_clean_installation /serial/tools/__pycache__/miniterm.cpython-38.pyc new file mode 100644 index 0000000..0dddd4f Binary files /dev/null and b/packages_for_clean_installation /serial/tools/__pycache__/miniterm.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/tools/hexlify_codec.py b/packages_for_clean_installation /serial/tools/hexlify_codec.py new file mode 100644 index 0000000..bd8f6b0 --- /dev/null +++ b/packages_for_clean_installation /serial/tools/hexlify_codec.py @@ -0,0 +1,126 @@ +#! python +# +# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015-2016 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +"""\ +Python 'hex' Codec - 2-digit hex with spaces content transfer encoding. + +Encode and decode may be a bit missleading at first sight... + +The textual representation is a hex dump: e.g. "40 41" +The "encoded" data of this is the binary form, e.g. b"@A" + +Therefore decoding is binary to text and thus converting binary data to hex dump. + +""" + +from __future__ import absolute_import + +import codecs +import serial + + +try: + unicode +except (NameError, AttributeError): + unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name + + +HEXDIGITS = '0123456789ABCDEF' + + +# Codec APIs + +def hex_encode(data, errors='strict'): + """'40 41 42' -> b'@ab'""" + return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data)) + + +def hex_decode(data, errors='strict'): + """b'@ab' -> '40 41 42'""" + return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data)) + + +class Codec(codecs.Codec): + def encode(self, data, errors='strict'): + """'40 41 42' -> b'@ab'""" + return serial.to_bytes([int(h, 16) for h in data.split()]) + + def decode(self, data, errors='strict'): + """b'@ab' -> '40 41 42'""" + return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) + + +class IncrementalEncoder(codecs.IncrementalEncoder): + """Incremental hex encoder""" + + def __init__(self, errors='strict'): + self.errors = errors + self.state = 0 + + def reset(self): + self.state = 0 + + def getstate(self): + return self.state + + def setstate(self, state): + self.state = state + + def encode(self, data, final=False): + """\ + Incremental encode, keep track of digits and emit a byte when a pair + of hex digits is found. The space is optional unless the error + handling is defined to be 'strict'. + """ + state = self.state + encoded = [] + for c in data.upper(): + if c in HEXDIGITS: + z = HEXDIGITS.index(c) + if state: + encoded.append(z + (state & 0xf0)) + state = 0 + else: + state = 0x100 + (z << 4) + elif c == ' ': # allow spaces to separate values + if state and self.errors == 'strict': + raise UnicodeError('odd number of hex digits') + state = 0 + else: + if self.errors == 'strict': + raise UnicodeError('non-hex digit found: {!r}'.format(c)) + self.state = state + return serial.to_bytes(encoded) + + +class IncrementalDecoder(codecs.IncrementalDecoder): + """Incremental decoder""" + def decode(self, data, final=False): + return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) + + +class StreamWriter(Codec, codecs.StreamWriter): + """Combination of hexlify codec and StreamWriter""" + + +class StreamReader(Codec, codecs.StreamReader): + """Combination of hexlify codec and StreamReader""" + + +def getregentry(): + """encodings module API""" + return codecs.CodecInfo( + name='hexlify', + encode=hex_encode, + decode=hex_decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + #~ _is_text_encoding=True, + ) diff --git a/packages_for_clean_installation /serial/tools/hexlify_codec.pyc b/packages_for_clean_installation /serial/tools/hexlify_codec.pyc new file mode 100644 index 0000000..d8cfc11 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/hexlify_codec.pyc differ diff --git a/packages_for_clean_installation /serial/tools/list_ports.py b/packages_for_clean_installation /serial/tools/list_ports.py new file mode 100644 index 0000000..0d7e3d4 --- /dev/null +++ b/packages_for_clean_installation /serial/tools/list_ports.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# +# Serial port enumeration. Console tool and backend selection. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +"""\ +This module will provide a function called comports that returns an +iterable (generator or list) that will enumerate available com ports. Note that +on some systems non-existent ports may be listed. + +Additionally a grep function is supplied that can be used to search for ports +based on their descriptions or hardware ID. +""" + +from __future__ import absolute_import + +import sys +import os +import re + +# chose an implementation, depending on os +#~ if sys.platform == 'cli': +#~ else: +if os.name == 'nt': # sys.platform == 'win32': + from serial.tools.list_ports_windows import comports +elif os.name == 'posix': + from serial.tools.list_ports_posix import comports +#~ elif os.name == 'java': +else: + raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def grep(regexp, include_links=False): + """\ + Search for ports using a regular expression. Port name, description and + hardware ID are searched. The function returns an iterable that returns the + same tuples as comport() would do. + """ + r = re.compile(regexp, re.I) + for info in comports(include_links): + port, desc, hwid = info + if r.search(port) or r.search(desc) or r.search(hwid): + yield info + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Serial port enumeration') + + parser.add_argument( + 'regexp', + nargs='?', + help='only show ports that match this regex') + + parser.add_argument( + '-v', '--verbose', + action='store_true', + help='show more messages') + + parser.add_argument( + '-q', '--quiet', + action='store_true', + help='suppress all messages') + + parser.add_argument( + '-n', + type=int, + help='only output the N-th entry') + + parser.add_argument( + '-s', '--include-links', + action='store_true', + help='include entries that are symlinks to real devices') + + args = parser.parse_args() + + hits = 0 + # get iteraror w/ or w/o filter + if args.regexp: + if not args.quiet: + sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp)) + iterator = sorted(grep(args.regexp, include_links=args.include_links)) + else: + iterator = sorted(comports(include_links=args.include_links)) + # list them + for n, (port, desc, hwid) in enumerate(iterator, 1): + if args.n is None or args.n == n: + sys.stdout.write("{:20}\n".format(port)) + if args.verbose: + sys.stdout.write(" desc: {}\n".format(desc)) + sys.stdout.write(" hwid: {}\n".format(hwid)) + hits += 1 + if not args.quiet: + if hits: + sys.stderr.write("{} ports found\n".format(hits)) + else: + sys.stderr.write("no ports found\n") + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + main() diff --git a/packages_for_clean_installation /serial/tools/list_ports.pyc b/packages_for_clean_installation /serial/tools/list_ports.pyc new file mode 100644 index 0000000..7532ec7 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/list_ports.pyc differ diff --git a/packages_for_clean_installation /serial/tools/list_ports_common.py b/packages_for_clean_installation /serial/tools/list_ports_common.py new file mode 100644 index 0000000..617f3dc --- /dev/null +++ b/packages_for_clean_installation /serial/tools/list_ports_common.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# +# This is a helper module for the various platform dependent list_port +# implementations. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import re +import glob +import os +import os.path + + +def numsplit(text): + """\ + Convert string into a list of texts and numbers in order to support a + natural sorting. + """ + result = [] + for group in re.split(r'(\d+)', text): + if group: + try: + group = int(group) + except ValueError: + pass + result.append(group) + return result + + +class ListPortInfo(object): + """Info collection base class for serial ports""" + + def __init__(self, device, skip_link_detection=False): + self.device = device + self.name = os.path.basename(device) + self.description = 'n/a' + self.hwid = 'n/a' + # USB specific data + self.vid = None + self.pid = None + self.serial_number = None + self.location = None + self.manufacturer = None + self.product = None + self.interface = None + # special handling for links + if not skip_link_detection and device is not None and os.path.islink(device): + self.hwid = 'LINK={}'.format(os.path.realpath(device)) + + def usb_description(self): + """return a short string to name the port based on USB info""" + if self.interface is not None: + return '{} - {}'.format(self.product, self.interface) + elif self.product is not None: + return self.product + else: + return self.name + + def usb_info(self): + """return a string with USB related information about device""" + return 'USB VID:PID={:04X}:{:04X}{}{}'.format( + self.vid or 0, + self.pid or 0, + ' SER={}'.format(self.serial_number) if self.serial_number is not None else '', + ' LOCATION={}'.format(self.location) if self.location is not None else '') + + def apply_usb_info(self): + """update description and hwid from USB data""" + self.description = self.usb_description() + self.hwid = self.usb_info() + + def __eq__(self, other): + return isinstance(other, ListPortInfo) and self.device == other.device + + def __hash__(self): + return hash(self.device) + + def __lt__(self, other): + if not isinstance(other, ListPortInfo): + raise TypeError('unorderable types: {}() and {}()'.format( + type(self).__name__, + type(other).__name__)) + return numsplit(self.device) < numsplit(other.device) + + def __str__(self): + return '{} - {}'.format(self.device, self.description) + + def __getitem__(self, index): + """Item access: backwards compatible -> (port, desc, hwid)""" + if index == 0: + return self.device + elif index == 1: + return self.description + elif index == 2: + return self.hwid + else: + raise IndexError('{} > 2'.format(index)) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def list_links(devices): + """\ + search all /dev devices and look for symlinks to known ports already + listed in devices. + """ + links = [] + for device in glob.glob('/dev/*'): + if os.path.islink(device) and os.path.realpath(device) in devices: + links.append(device) + return links + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + print(ListPortInfo('dummy')) diff --git a/packages_for_clean_installation /serial/tools/list_ports_common.pyc b/packages_for_clean_installation /serial/tools/list_ports_common.pyc new file mode 100644 index 0000000..67c57f4 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/list_ports_common.pyc differ diff --git a/packages_for_clean_installation /serial/tools/list_ports_linux.py b/packages_for_clean_installation /serial/tools/list_ports_linux.py new file mode 100644 index 0000000..c8c1cfc --- /dev/null +++ b/packages_for_clean_installation /serial/tools/list_ports_linux.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# +# This is a module that gathers a list of serial ports including details on +# GNU/Linux systems. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import glob +import os +from serial.tools import list_ports_common + + +class SysFS(list_ports_common.ListPortInfo): + """Wrapper for easy sysfs access and device info""" + + def __init__(self, device): + super(SysFS, self).__init__(device) + # special handling for links + if device is not None and os.path.islink(device): + device = os.path.realpath(device) + is_link = True + else: + is_link = False + self.usb_device_path = None + if os.path.exists('/sys/class/tty/{}/device'.format(self.name)): + self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name)) + self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem'))) + else: + self.device_path = None + self.subsystem = None + # check device type + if self.subsystem == 'usb-serial': + self.usb_interface_path = os.path.dirname(self.device_path) + elif self.subsystem == 'usb': + self.usb_interface_path = self.device_path + else: + self.usb_interface_path = None + # fill-in info for USB devices + if self.usb_interface_path is not None: + self.usb_device_path = os.path.dirname(self.usb_interface_path) + + try: + num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces')) + except ValueError: + num_if = 1 + + self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16) + self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16) + self.serial_number = self.read_line(self.usb_device_path, 'serial') + if num_if > 1: # multi interface devices like FT4232 + self.location = os.path.basename(self.usb_interface_path) + else: + self.location = os.path.basename(self.usb_device_path) + + self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer') + self.product = self.read_line(self.usb_device_path, 'product') + self.interface = self.read_line(self.usb_interface_path, 'interface') + + if self.subsystem in ('usb', 'usb-serial'): + self.apply_usb_info() + #~ elif self.subsystem in ('pnp', 'amba'): # PCI based devices, raspi + elif self.subsystem == 'pnp': # PCI based devices + self.description = self.name + self.hwid = self.read_line(self.device_path, 'id') + elif self.subsystem == 'amba': # raspi + self.description = self.name + self.hwid = os.path.basename(self.device_path) + + if is_link: + self.hwid += ' LINK={}'.format(device) + + def read_line(self, *args): + """\ + Helper function to read a single line from a file. + One or more parameters are allowed, they are joined with os.path.join. + Returns None on errors.. + """ + try: + with open(os.path.join(*args)) as f: + line = f.readline().strip() + return line + except IOError: + return None + + +def comports(include_links=False): + devices = glob.glob('/dev/ttyS*') # built-in serial ports + devices.extend(glob.glob('/dev/ttyUSB*')) # usb-serial with own driver + devices.extend(glob.glob('/dev/ttyXRUSB*')) # xr-usb-serial port exar (DELL Edge 3001) + devices.extend(glob.glob('/dev/ttyACM*')) # usb-serial with CDC-ACM profile + devices.extend(glob.glob('/dev/ttyAMA*')) # ARM internal port (raspi) + devices.extend(glob.glob('/dev/rfcomm*')) # BT serial devices + devices.extend(glob.glob('/dev/ttyAP*')) # Advantech multi-port serial controllers + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [info + for info in [SysFS(d) for d in devices] + if info.subsystem != "platform"] # hide non-present internal serial ports + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + for info in sorted(comports()): + print("{0}: {0.subsystem}".format(info)) diff --git a/packages_for_clean_installation /serial/tools/list_ports_linux.pyc b/packages_for_clean_installation /serial/tools/list_ports_linux.pyc new file mode 100644 index 0000000..08ffc25 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/list_ports_linux.pyc differ diff --git a/packages_for_clean_installation /serial/tools/list_ports_osx.py b/packages_for_clean_installation /serial/tools/list_ports_osx.py new file mode 100644 index 0000000..51b4e8c --- /dev/null +++ b/packages_for_clean_installation /serial/tools/list_ports_osx.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python +# +# This is a module that gathers a list of serial ports including details on OSX +# +# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools +# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston +# and modifications by cliechti, hoihu, hardkrash +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2013-2020 +# +# SPDX-License-Identifier: BSD-3-Clause + + +# List all of the callout devices in OS/X by querying IOKit. + +# See the following for a reference of how to do this: +# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD + +# More help from darwin_hid.py + +# Also see the 'IORegistryExplorer' for an idea of what we are actually searching + +from __future__ import absolute_import + +import ctypes + +from serial.tools import list_ports_common + +iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit') +cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation') + +# kIOMasterPortDefault is no longer exported in BigSur but no biggie, using NULL works just the same +kIOMasterPortDefault = 0 # WAS: ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") +kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault") + +kCFStringEncodingMacRoman = 0 +kCFStringEncodingUTF8 = 0x08000100 + +# defined in `IOKit/usb/USBSpec.h` +kUSBVendorString = 'USB Vendor Name' +kUSBSerialNumberString = 'USB Serial Number' + +# `io_name_t` defined as `typedef char io_name_t[128];` +# in `device/device_types.h` +io_name_size = 128 + +# defined in `mach/kern_return.h` +KERN_SUCCESS = 0 +# kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h` +kern_return_t = ctypes.c_int + +iokit.IOServiceMatching.restype = ctypes.c_void_p + +iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +iokit.IOServiceGetMatchingServices.restype = kern_return_t + +iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +iokit.IOServiceGetMatchingServices.restype = kern_return_t + +iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32] +iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p + +iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +iokit.IORegistryEntryGetPath.restype = kern_return_t + +iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +iokit.IORegistryEntryGetName.restype = kern_return_t + +iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +iokit.IOObjectGetClass.restype = kern_return_t + +iokit.IOObjectRelease.argtypes = [ctypes.c_void_p] + + +cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32] +cf.CFStringCreateWithCString.restype = ctypes.c_void_p + +cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32] +cf.CFStringGetCStringPtr.restype = ctypes.c_char_p + +cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32] +cf.CFStringGetCString.restype = ctypes.c_bool + +cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p] +cf.CFNumberGetValue.restype = ctypes.c_void_p + +# void CFRelease ( CFTypeRef cf ); +cf.CFRelease.argtypes = [ctypes.c_void_p] +cf.CFRelease.restype = None + +# CFNumber type defines +kCFNumberSInt8Type = 1 +kCFNumberSInt16Type = 2 +kCFNumberSInt32Type = 3 +kCFNumberSInt64Type = 4 + + +def get_string_property(device_type, property): + """ + Search the given device for the specified string property + + @param device_type Type of Device + @param property String to search for + @return Python string containing the value, or None if not found. + """ + key = cf.CFStringCreateWithCString( + kCFAllocatorDefault, + property.encode("utf-8"), + kCFStringEncodingUTF8) + + CFContainer = iokit.IORegistryEntryCreateCFProperty( + device_type, + key, + kCFAllocatorDefault, + 0) + output = None + + if CFContainer: + output = cf.CFStringGetCStringPtr(CFContainer, 0) + if output is not None: + output = output.decode('utf-8') + else: + buffer = ctypes.create_string_buffer(io_name_size); + success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8) + if success: + output = buffer.value.decode('utf-8') + cf.CFRelease(CFContainer) + return output + + +def get_int_property(device_type, property, cf_number_type): + """ + Search the given device for the specified string property + + @param device_type Device to search + @param property String to search for + @param cf_number_type CFType number + + @return Python string containing the value, or None if not found. + """ + key = cf.CFStringCreateWithCString( + kCFAllocatorDefault, + property.encode("utf-8"), + kCFStringEncodingUTF8) + + CFContainer = iokit.IORegistryEntryCreateCFProperty( + device_type, + key, + kCFAllocatorDefault, + 0) + + if CFContainer: + if (cf_number_type == kCFNumberSInt32Type): + number = ctypes.c_uint32() + elif (cf_number_type == kCFNumberSInt16Type): + number = ctypes.c_uint16() + cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number)) + cf.CFRelease(CFContainer) + return number.value + return None + +def IORegistryEntryGetName(device): + devicename = ctypes.create_string_buffer(io_name_size); + res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename)) + if res != KERN_SUCCESS: + return None + # this works in python2 but may not be valid. Also I don't know if + # this encoding is guaranteed. It may be dependent on system locale. + return devicename.value.decode('utf-8') + +def IOObjectGetClass(device): + classname = ctypes.create_string_buffer(io_name_size) + iokit.IOObjectGetClass(device, ctypes.byref(classname)) + return classname.value + +def GetParentDeviceByType(device, parent_type): + """ Find the first parent of a device that implements the parent_type + @param IOService Service to inspect + @return Pointer to the parent type, or None if it was not found. + """ + # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice. + parent_type = parent_type.encode('utf-8') + while IOObjectGetClass(device) != parent_type: + parent = ctypes.c_void_p() + response = iokit.IORegistryEntryGetParentEntry( + device, + "IOService".encode("utf-8"), + ctypes.byref(parent)) + # If we weren't able to find a parent for the device, we're done. + if response != KERN_SUCCESS: + return None + device = parent + return device + + +def GetIOServicesByType(service_type): + """ + returns iterator over specified service_type + """ + serial_port_iterator = ctypes.c_void_p() + + iokit.IOServiceGetMatchingServices( + kIOMasterPortDefault, + iokit.IOServiceMatching(service_type.encode('utf-8')), + ctypes.byref(serial_port_iterator)) + + services = [] + while iokit.IOIteratorIsValid(serial_port_iterator): + service = iokit.IOIteratorNext(serial_port_iterator) + if not service: + break + services.append(service) + iokit.IOObjectRelease(serial_port_iterator) + return services + + +def location_to_string(locationID): + """ + helper to calculate port and bus number from locationID + """ + loc = ['{}-'.format(locationID >> 24)] + while locationID & 0xf00000: + if len(loc) > 1: + loc.append('.') + loc.append('{}'.format((locationID >> 20) & 0xf)) + locationID <<= 4 + return ''.join(loc) + + +class SuitableSerialInterface(object): + pass + + +def scan_interfaces(): + """ + helper function to scan USB interfaces + returns a list of SuitableSerialInterface objects with name and id attributes + """ + interfaces = [] + for service in GetIOServicesByType('IOSerialBSDClient'): + device = get_string_property(service, "IOCalloutDevice") + if device: + usb_device = GetParentDeviceByType(service, "IOUSBInterface") + if usb_device: + name = get_string_property(usb_device, "USB Interface Name") or None + locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or '' + i = SuitableSerialInterface() + i.id = locationID + i.name = name + interfaces.append(i) + return interfaces + + +def search_for_locationID_in_interfaces(serial_interfaces, locationID): + for interface in serial_interfaces: + if (interface.id == locationID): + return interface.name + return None + + +def comports(include_links=False): + # XXX include_links is currently ignored. are links in /dev even supported here? + # Scan for all iokit serial ports + services = GetIOServicesByType('IOSerialBSDClient') + ports = [] + serial_interfaces = scan_interfaces() + for service in services: + # First, add the callout device file. + device = get_string_property(service, "IOCalloutDevice") + if device: + info = list_ports_common.ListPortInfo(device) + # If the serial port is implemented by IOUSBDevice + # NOTE IOUSBDevice was deprecated as of 10.11 and finally on Apple Silicon + # devices has been completely removed. Thanks to @oskay for this patch. + usb_device = GetParentDeviceByType(service, "IOUSBHostDevice") + if not usb_device: + usb_device = GetParentDeviceByType(service, "IOUSBDevice") + if usb_device: + # fetch some useful informations from properties + info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type) + info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type) + info.serial_number = get_string_property(usb_device, kUSBSerialNumberString) + # We know this is a usb device, so the + # IORegistryEntryName should always be aliased to the + # usb product name string descriptor. + info.product = IORegistryEntryGetName(usb_device) or 'n/a' + info.manufacturer = get_string_property(usb_device, kUSBVendorString) + locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) + info.location = location_to_string(locationID) + info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID) + info.apply_usb_info() + ports.append(info) + return ports + +# test +if __name__ == '__main__': + for port, desc, hwid in sorted(comports()): + print("{}: {} [{}]".format(port, desc, hwid)) diff --git a/packages_for_clean_installation /serial/tools/list_ports_osx.pyc b/packages_for_clean_installation /serial/tools/list_ports_osx.pyc new file mode 100644 index 0000000..a5c2c2b Binary files /dev/null and b/packages_for_clean_installation /serial/tools/list_ports_osx.pyc differ diff --git a/packages_for_clean_installation /serial/tools/list_ports_posix.py b/packages_for_clean_installation /serial/tools/list_ports_posix.py new file mode 100644 index 0000000..79bc8ed --- /dev/null +++ b/packages_for_clean_installation /serial/tools/list_ports_posix.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# +# This is a module that gathers a list of serial ports on POSIXy systems. +# For some specific implementations, see also list_ports_linux, list_ports_osx +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +"""\ +The ``comports`` function is expected to return an iterable that yields tuples +of 3 strings: port name, human readable description and a hardware ID. + +As currently no method is known to get the second two strings easily, they are +currently just identical to the port name. +""" + +from __future__ import absolute_import + +import glob +import sys +import os +from serial.tools import list_ports_common + +# try to detect the OS so that a device can be selected... +plat = sys.platform.lower() + +if plat[:5] == 'linux': # Linux (confirmed) # noqa + from serial.tools.list_ports_linux import comports + +elif plat[:6] == 'darwin': # OS X (confirmed) + from serial.tools.list_ports_osx import comports + +elif plat == 'cygwin': # cygwin/win32 + # cygwin accepts /dev/com* in many contexts + # (such as 'open' call, explicit 'ls'), but 'glob.glob' + # and bare 'ls' do not; so use /dev/ttyS* instead + def comports(include_links=False): + devices = glob.glob('/dev/ttyS*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:7] == 'openbsd': # OpenBSD + def comports(include_links=False): + devices = glob.glob('/dev/cua*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:3] == 'bsd' or plat[:7] == 'freebsd': + def comports(include_links=False): + devices = glob.glob('/dev/cua*[!.init][!.lock]') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:6] == 'netbsd': # NetBSD + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/dty*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:4] == 'irix': # IRIX + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/ttyf*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:2] == 'hp': # HP-UX (not tested) + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/tty*p0') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:5] == 'sunos': # Solaris/SunOS + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/tty*c') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:3] == 'aix': # AIX + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/tty*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +else: + # platform detection has failed... + import serial + sys.stderr.write("""\ +don't know how to enumerate ttys on this system. +! I you know how the serial ports are named send this information to +! the author of this module: + +sys.platform = {!r} +os.name = {!r} +pySerial version = {} + +also add the naming scheme of the serial ports and with a bit luck you can get +this module running... +""".format(sys.platform, os.name, serial.VERSION)) + raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) + +# test +if __name__ == '__main__': + for port, desc, hwid in sorted(comports()): + print("{}: {} [{}]".format(port, desc, hwid)) diff --git a/packages_for_clean_installation /serial/tools/list_ports_posix.pyc b/packages_for_clean_installation /serial/tools/list_ports_posix.pyc new file mode 100644 index 0000000..184c3f3 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/list_ports_posix.pyc differ diff --git a/packages_for_clean_installation /serial/tools/list_ports_windows.py b/packages_for_clean_installation /serial/tools/list_ports_windows.py new file mode 100644 index 0000000..0b4a5b1 --- /dev/null +++ b/packages_for_clean_installation /serial/tools/list_ports_windows.py @@ -0,0 +1,427 @@ +#! python +# +# Enumerate serial ports on Windows including a human readable description +# and hardware information. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2016 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +# pylint: disable=invalid-name,too-few-public-methods +import re +import ctypes +from ctypes.wintypes import BOOL +from ctypes.wintypes import HWND +from ctypes.wintypes import DWORD +from ctypes.wintypes import WORD +from ctypes.wintypes import LONG +from ctypes.wintypes import ULONG +from ctypes.wintypes import HKEY +from ctypes.wintypes import BYTE +import serial +from serial.win32 import ULONG_PTR +from serial.tools import list_ports_common + + +def ValidHandle(value, func, arguments): + if value == 0: + raise ctypes.WinError() + return value + + +NULL = 0 +HDEVINFO = ctypes.c_void_p +LPCTSTR = ctypes.c_wchar_p +PCTSTR = ctypes.c_wchar_p +PTSTR = ctypes.c_wchar_p +LPDWORD = PDWORD = ctypes.POINTER(DWORD) +#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE) +LPBYTE = PBYTE = ctypes.c_void_p # XXX avoids error about types + +ACCESS_MASK = DWORD +REGSAM = ACCESS_MASK + + +class GUID(ctypes.Structure): + _fields_ = [ + ('Data1', DWORD), + ('Data2', WORD), + ('Data3', WORD), + ('Data4', BYTE * 8), + ] + + def __str__(self): + return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format( + self.Data1, + self.Data2, + self.Data3, + ''.join(["{:02x}".format(d) for d in self.Data4[:2]]), + ''.join(["{:02x}".format(d) for d in self.Data4[2:]]), + ) + + +class SP_DEVINFO_DATA(ctypes.Structure): + _fields_ = [ + ('cbSize', DWORD), + ('ClassGuid', GUID), + ('DevInst', DWORD), + ('Reserved', ULONG_PTR), + ] + + def __str__(self): + return "ClassGuid:{} DevInst:{}".format(self.ClassGuid, self.DevInst) + + +PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA) + +PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p + +setupapi = ctypes.windll.LoadLibrary("setupapi") +SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList +SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO] +SetupDiDestroyDeviceInfoList.restype = BOOL + +SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW +SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD] +SetupDiClassGuidsFromName.restype = BOOL + +SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo +SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA] +SetupDiEnumDeviceInfo.restype = BOOL + +SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW +SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD] +SetupDiGetClassDevs.restype = HDEVINFO +SetupDiGetClassDevs.errcheck = ValidHandle + +SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW +SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD] +SetupDiGetDeviceRegistryProperty.restype = BOOL + +SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW +SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD] +SetupDiGetDeviceInstanceId.restype = BOOL + +SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey +SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM] +SetupDiOpenDevRegKey.restype = HKEY + +advapi32 = ctypes.windll.LoadLibrary("Advapi32") +RegCloseKey = advapi32.RegCloseKey +RegCloseKey.argtypes = [HKEY] +RegCloseKey.restype = LONG + +RegQueryValueEx = advapi32.RegQueryValueExW +RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD] +RegQueryValueEx.restype = LONG + +cfgmgr32 = ctypes.windll.LoadLibrary("Cfgmgr32") +CM_Get_Parent = cfgmgr32.CM_Get_Parent +CM_Get_Parent.argtypes = [PDWORD, DWORD, ULONG] +CM_Get_Parent.restype = LONG + +CM_Get_Device_IDW = cfgmgr32.CM_Get_Device_IDW +CM_Get_Device_IDW.argtypes = [DWORD, PTSTR, ULONG, ULONG] +CM_Get_Device_IDW.restype = LONG + +CM_MapCrToWin32Err = cfgmgr32.CM_MapCrToWin32Err +CM_MapCrToWin32Err.argtypes = [DWORD, DWORD] +CM_MapCrToWin32Err.restype = DWORD + + +DIGCF_PRESENT = 2 +DIGCF_DEVICEINTERFACE = 16 +INVALID_HANDLE_VALUE = 0 +ERROR_INSUFFICIENT_BUFFER = 122 +ERROR_NOT_FOUND = 1168 +SPDRP_HARDWAREID = 1 +SPDRP_FRIENDLYNAME = 12 +SPDRP_LOCATION_PATHS = 35 +SPDRP_MFG = 11 +DICS_FLAG_GLOBAL = 1 +DIREG_DEV = 0x00000001 +KEY_READ = 0x20019 + + +MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH = 5 + + +def get_parent_serial_number(child_devinst, child_vid, child_pid, depth=0, last_serial_number=None): + """ Get the serial number of the parent of a device. + + Args: + child_devinst: The device instance handle to get the parent serial number of. + child_vid: The vendor ID of the child device. + child_pid: The product ID of the child device. + depth: The current iteration depth of the USB device tree. + """ + + # If the traversal depth is beyond the max, abandon attempting to find the serial number. + if depth > MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH: + return '' if not last_serial_number else last_serial_number + + # Get the parent device instance. + devinst = DWORD() + ret = CM_Get_Parent(ctypes.byref(devinst), child_devinst, 0) + + if ret: + win_error = CM_MapCrToWin32Err(DWORD(ret), DWORD(0)) + + # If there is no parent available, the child was the root device. We cannot traverse + # further. + if win_error == ERROR_NOT_FOUND: + return '' if not last_serial_number else last_serial_number + + raise ctypes.WinError(win_error) + + # Get the ID of the parent device and parse it for vendor ID, product ID, and serial number. + parentHardwareID = ctypes.create_unicode_buffer(250) + + ret = CM_Get_Device_IDW( + devinst, + parentHardwareID, + ctypes.sizeof(parentHardwareID) - 1, + 0) + + if ret: + raise ctypes.WinError(CM_MapCrToWin32Err(DWORD(ret), DWORD(0))) + + parentHardwareID_str = parentHardwareID.value + m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', + parentHardwareID_str, + re.I) + + # return early if we have no matches (likely malformed serial, traversed too far) + if not m: + return '' if not last_serial_number else last_serial_number + + vid = None + pid = None + serial_number = None + if m.group(1): + vid = int(m.group(1), 16) + if m.group(3): + pid = int(m.group(3), 16) + if m.group(7): + serial_number = m.group(7) + + # store what we found as a fallback for malformed serial values up the chain + found_serial_number = serial_number + + # Check that the USB serial number only contains alpha-numeric characters. It may be a windows + # device ID (ephemeral ID). + if serial_number and not re.match(r'^\w+$', serial_number): + serial_number = None + + if not vid or not pid: + # If pid and vid are not available at this device level, continue to the parent. + return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number) + + if pid != child_pid or vid != child_vid: + # If the VID or PID has changed, we are no longer looking at the same physical device. The + # serial number is unknown. + return '' if not last_serial_number else last_serial_number + + # In this case, the vid and pid of the parent device are identical to the child. However, if + # there still isn't a serial number available, continue to the next parent. + if not serial_number: + return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number) + + # Finally, the VID and PID are identical to the child and a serial number is present, so return + # it. + return serial_number + + +def iterate_comports(): + """Return a generator that yields descriptions for serial ports""" + PortsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough... + ports_guids_size = DWORD() + if not SetupDiClassGuidsFromName( + "Ports", + PortsGUIDs, + ctypes.sizeof(PortsGUIDs), + ctypes.byref(ports_guids_size)): + raise ctypes.WinError() + + ModemsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough... + modems_guids_size = DWORD() + if not SetupDiClassGuidsFromName( + "Modem", + ModemsGUIDs, + ctypes.sizeof(ModemsGUIDs), + ctypes.byref(modems_guids_size)): + raise ctypes.WinError() + + GUIDs = PortsGUIDs[:ports_guids_size.value] + ModemsGUIDs[:modems_guids_size.value] + + # repeat for all possible GUIDs + for index in range(len(GUIDs)): + bInterfaceNumber = None + g_hdi = SetupDiGetClassDevs( + ctypes.byref(GUIDs[index]), + None, + NULL, + DIGCF_PRESENT) # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports + + devinfo = SP_DEVINFO_DATA() + devinfo.cbSize = ctypes.sizeof(devinfo) + index = 0 + while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)): + index += 1 + + # get the real com port name + hkey = SetupDiOpenDevRegKey( + g_hdi, + ctypes.byref(devinfo), + DICS_FLAG_GLOBAL, + 0, + DIREG_DEV, # DIREG_DRV for SW info + KEY_READ) + port_name_buffer = ctypes.create_unicode_buffer(250) + port_name_length = ULONG(ctypes.sizeof(port_name_buffer)) + RegQueryValueEx( + hkey, + "PortName", + None, + None, + ctypes.byref(port_name_buffer), + ctypes.byref(port_name_length)) + RegCloseKey(hkey) + + # unfortunately does this method also include parallel ports. + # we could check for names starting with COM or just exclude LPT + # and hope that other "unknown" names are serial ports... + if port_name_buffer.value.startswith('LPT'): + continue + + # hardware ID + szHardwareID = ctypes.create_unicode_buffer(250) + # try to get ID that includes serial number + if not SetupDiGetDeviceInstanceId( + g_hdi, + ctypes.byref(devinfo), + #~ ctypes.byref(szHardwareID), + szHardwareID, + ctypes.sizeof(szHardwareID) - 1, + None): + # fall back to more generic hardware ID if that would fail + if not SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_HARDWAREID, + None, + ctypes.byref(szHardwareID), + ctypes.sizeof(szHardwareID) - 1, + None): + # Ignore ERROR_INSUFFICIENT_BUFFER + if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: + raise ctypes.WinError() + # stringify + szHardwareID_str = szHardwareID.value + + info = list_ports_common.ListPortInfo(port_name_buffer.value, skip_link_detection=True) + + # in case of USB, make a more readable string, similar to that form + # that we also generate on other platforms + if szHardwareID_str.startswith('USB'): + m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', szHardwareID_str, re.I) + if m: + info.vid = int(m.group(1), 16) + if m.group(3): + info.pid = int(m.group(3), 16) + if m.group(5): + bInterfaceNumber = int(m.group(5)) + + # Check that the USB serial number only contains alpha-numeric characters. It + # may be a windows device ID (ephemeral ID) for composite devices. + if m.group(7) and re.match(r'^\w+$', m.group(7)): + info.serial_number = m.group(7) + else: + info.serial_number = get_parent_serial_number(devinfo.DevInst, info.vid, info.pid) + + # calculate a location string + loc_path_str = ctypes.create_unicode_buffer(250) + if SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_LOCATION_PATHS, + None, + ctypes.byref(loc_path_str), + ctypes.sizeof(loc_path_str) - 1, + None): + m = re.finditer(r'USBROOT\((\w+)\)|#USB\((\w+)\)', loc_path_str.value) + location = [] + for g in m: + if g.group(1): + location.append('{:d}'.format(int(g.group(1)) + 1)) + else: + if len(location) > 1: + location.append('.') + else: + location.append('-') + location.append(g.group(2)) + if bInterfaceNumber is not None: + location.append(':{}.{}'.format( + 'x', # XXX how to determine correct bConfigurationValue? + bInterfaceNumber)) + if location: + info.location = ''.join(location) + info.hwid = info.usb_info() + elif szHardwareID_str.startswith('FTDIBUS'): + m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w+))?', szHardwareID_str, re.I) + if m: + info.vid = int(m.group(1), 16) + info.pid = int(m.group(2), 16) + if m.group(4): + info.serial_number = m.group(4) + # USB location is hidden by FDTI driver :( + info.hwid = info.usb_info() + else: + info.hwid = szHardwareID_str + + # friendly name + szFriendlyName = ctypes.create_unicode_buffer(250) + if SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_FRIENDLYNAME, + #~ SPDRP_DEVICEDESC, + None, + ctypes.byref(szFriendlyName), + ctypes.sizeof(szFriendlyName) - 1, + None): + info.description = szFriendlyName.value + #~ else: + # Ignore ERROR_INSUFFICIENT_BUFFER + #~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: + #~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value)) + # ignore errors and still include the port in the list, friendly name will be same as port name + + # manufacturer + szManufacturer = ctypes.create_unicode_buffer(250) + if SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_MFG, + #~ SPDRP_DEVICEDESC, + None, + ctypes.byref(szManufacturer), + ctypes.sizeof(szManufacturer) - 1, + None): + info.manufacturer = szManufacturer.value + yield info + SetupDiDestroyDeviceInfoList(g_hdi) + + +def comports(include_links=False): + """Return a list of info objects about serial ports""" + return list(iterate_comports()) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + for port, desc, hwid in sorted(comports()): + print("{}: {} [{}]".format(port, desc, hwid)) diff --git a/packages_for_clean_installation /serial/tools/list_ports_windows.pyc b/packages_for_clean_installation /serial/tools/list_ports_windows.pyc new file mode 100644 index 0000000..fa5f4b6 Binary files /dev/null and b/packages_for_clean_installation /serial/tools/list_ports_windows.pyc differ diff --git a/packages_for_clean_installation /serial/tools/miniterm.py b/packages_for_clean_installation /serial/tools/miniterm.py new file mode 100644 index 0000000..2cceff6 --- /dev/null +++ b/packages_for_clean_installation /serial/tools/miniterm.py @@ -0,0 +1,1042 @@ +#!/usr/bin/env python +# +# Very simple serial terminal +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C)2002-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import codecs +import os +import sys +import threading + +import serial +from serial.tools.list_ports import comports +from serial.tools import hexlify_codec + +# pylint: disable=wrong-import-order,wrong-import-position + +codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None) + +try: + raw_input +except NameError: + # pylint: disable=redefined-builtin,invalid-name + raw_input = input # in python3 it's "raw" + unichr = chr + + +def key_description(character): + """generate a readable description for a key""" + ascii_code = ord(character) + if ascii_code < 32: + return 'Ctrl+{:c}'.format(ord('@') + ascii_code) + else: + return repr(character) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class ConsoleBase(object): + """OS abstraction for console (input/output codec, no echo)""" + + def __init__(self): + if sys.version_info >= (3, 0): + self.byte_output = sys.stdout.buffer + else: + self.byte_output = sys.stdout + self.output = sys.stdout + + def setup(self): + """Set console to read single characters, no echo""" + + def cleanup(self): + """Restore default console settings""" + + def getkey(self): + """Read a single key from the console""" + return None + + def write_bytes(self, byte_string): + """Write bytes (already encoded)""" + self.byte_output.write(byte_string) + self.byte_output.flush() + + def write(self, text): + """Write string""" + self.output.write(text) + self.output.flush() + + def cancel(self): + """Cancel getkey operation""" + + # - - - - - - - - - - - - - - - - - - - - - - - - + # context manager: + # switch terminal temporary to normal mode (e.g. to get user input) + + def __enter__(self): + self.cleanup() + return self + + def __exit__(self, *args, **kwargs): + self.setup() + + +if os.name == 'nt': # noqa + import msvcrt + import ctypes + import platform + + class Out(object): + """file-like wrapper that uses os.write""" + + def __init__(self, fd): + self.fd = fd + + def flush(self): + pass + + def write(self, s): + os.write(self.fd, s) + + class Console(ConsoleBase): + fncodes = { + ';': '\1bOP', # F1 + '<': '\1bOQ', # F2 + '=': '\1bOR', # F3 + '>': '\1bOS', # F4 + '?': '\1b[15~', # F5 + '@': '\1b[17~', # F6 + 'A': '\1b[18~', # F7 + 'B': '\1b[19~', # F8 + 'C': '\1b[20~', # F9 + 'D': '\1b[21~', # F10 + } + navcodes = { + 'H': '\x1b[A', # UP + 'P': '\x1b[B', # DOWN + 'K': '\x1b[D', # LEFT + 'M': '\x1b[C', # RIGHT + 'G': '\x1b[H', # HOME + 'O': '\x1b[F', # END + 'R': '\x1b[2~', # INSERT + 'S': '\x1b[3~', # DELETE + 'I': '\x1b[5~', # PGUP + 'Q': '\x1b[6~', # PGDN + } + + def __init__(self): + super(Console, self).__init__() + self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP() + self._saved_icp = ctypes.windll.kernel32.GetConsoleCP() + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + ctypes.windll.kernel32.SetConsoleCP(65001) + # ANSI handling available through SetConsoleMode since Windows 10 v1511 + # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1 + if platform.release() == '10' and int(platform.version().split('.')[2]) > 10586: + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + import ctypes.wintypes as wintypes + if not hasattr(wintypes, 'LPDWORD'): # PY2 + wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD) + SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode + GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode + GetStdHandle = ctypes.windll.kernel32.GetStdHandle + mode = wintypes.DWORD() + GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode)) + if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: + SetConsoleMode(GetStdHandle(-11), mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self._saved_cm = mode + self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace') + # the change of the code page is not propagated to Python, manually fix it + sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace') + sys.stdout = self.output + self.output.encoding = 'UTF-8' # needed for input + + def __del__(self): + ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp) + ctypes.windll.kernel32.SetConsoleCP(self._saved_icp) + try: + ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm) + except AttributeError: # in case no _saved_cm + pass + + def getkey(self): + while True: + z = msvcrt.getwch() + if z == unichr(13): + return unichr(10) + elif z is unichr(0) or z is unichr(0xe0): + try: + code = msvcrt.getwch() + if z is unichr(0): + return self.fncodes[code] + else: + return self.navcodes[code] + except KeyError: + pass + else: + return z + + def cancel(self): + # CancelIo, CancelSynchronousIo do not seem to work when using + # getwch, so instead, send a key to the window with the console + hwnd = ctypes.windll.kernel32.GetConsoleWindow() + ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0) + +elif os.name == 'posix': + import atexit + import termios + import fcntl + + class Console(ConsoleBase): + def __init__(self): + super(Console, self).__init__() + self.fd = sys.stdin.fileno() + self.old = termios.tcgetattr(self.fd) + atexit.register(self.cleanup) + if sys.version_info < (3, 0): + self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin) + else: + self.enc_stdin = sys.stdin + + def setup(self): + new = termios.tcgetattr(self.fd) + new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG + new[6][termios.VMIN] = 1 + new[6][termios.VTIME] = 0 + termios.tcsetattr(self.fd, termios.TCSANOW, new) + + def getkey(self): + c = self.enc_stdin.read(1) + if c == unichr(0x7f): + c = unichr(8) # map the BS key (which yields DEL) to backspace + return c + + def cancel(self): + fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0') + + def cleanup(self): + termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old) + +else: + raise NotImplementedError( + 'Sorry no implementation for your platform ({}) available.'.format(sys.platform)) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +class Transform(object): + """do-nothing: forward all data unchanged""" + def rx(self, text): + """text received from serial port""" + return text + + def tx(self, text): + """text to be sent to serial port""" + return text + + def echo(self, text): + """text to be sent but displayed on console""" + return text + + +class CRLF(Transform): + """ENTER sends CR+LF""" + + def tx(self, text): + return text.replace('\n', '\r\n') + + +class CR(Transform): + """ENTER sends CR""" + + def rx(self, text): + return text.replace('\r', '\n') + + def tx(self, text): + return text.replace('\n', '\r') + + +class LF(Transform): + """ENTER sends LF""" + + +class NoTerminal(Transform): + """remove typical terminal control codes from input""" + + REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t') + REPLACEMENT_MAP.update( + { + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + }) + + def rx(self, text): + return text.translate(self.REPLACEMENT_MAP) + + echo = rx + + +class NoControls(NoTerminal): + """Remove all control codes, incl. CR+LF""" + + REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32)) + REPLACEMENT_MAP.update( + { + 0x20: 0x2423, # visual space + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + }) + + +class Printable(Transform): + """Show decimal code for all non-ASCII characters and replace most control codes""" + + def rx(self, text): + r = [] + for c in text: + if ' ' <= c < '\x7f' or c in '\r\n\b\t': + r.append(c) + elif c < ' ': + r.append(unichr(0x2400 + ord(c))) + else: + r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c))) + r.append(' ') + return ''.join(r) + + echo = rx + + +class Colorize(Transform): + """Apply different colors for received and echo""" + + def __init__(self): + # XXX make it configurable, use colorama? + self.input_color = '\x1b[37m' + self.echo_color = '\x1b[31m' + + def rx(self, text): + return self.input_color + text + + def echo(self, text): + return self.echo_color + text + + +class DebugIO(Transform): + """Print what is sent and received""" + + def rx(self, text): + sys.stderr.write(' [RX:{!r}] '.format(text)) + sys.stderr.flush() + return text + + def tx(self, text): + sys.stderr.write(' [TX:{!r}] '.format(text)) + sys.stderr.flush() + return text + + +# other ideas: +# - add date/time for each newline +# - insert newline after: a) timeout b) packet end character + +EOL_TRANSFORMATIONS = { + 'crlf': CRLF, + 'cr': CR, + 'lf': LF, +} + +TRANSFORMATIONS = { + 'direct': Transform, # no transformation + 'default': NoTerminal, + 'nocontrol': NoControls, + 'printable': Printable, + 'colorize': Colorize, + 'debug': DebugIO, +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def ask_for_port(): + """\ + Show a list of ports and ask the user for a choice. To make selection + easier on systems with long device names, also allow the input of an + index. + """ + sys.stderr.write('\n--- Available ports:\n') + ports = [] + for n, (port, desc, hwid) in enumerate(sorted(comports()), 1): + sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc)) + ports.append(port) + while True: + port = raw_input('--- Enter port index or full name: ') + try: + index = int(port) - 1 + if not 0 <= index < len(ports): + sys.stderr.write('--- Invalid index!\n') + continue + except ValueError: + pass + else: + port = ports[index] + return port + + +class Miniterm(object): + """\ + Terminal application. Copy data from serial port to console and vice versa. + Handle special keys from the console to show menu etc. + """ + + def __init__(self, serial_instance, echo=False, eol='crlf', filters=()): + self.console = Console() + self.serial = serial_instance + self.echo = echo + self.raw = False + self.input_encoding = 'UTF-8' + self.output_encoding = 'UTF-8' + self.eol = eol + self.filters = filters + self.update_transformations() + self.exit_character = unichr(0x1d) # GS/CTRL+] + self.menu_character = unichr(0x14) # Menu: CTRL+T + self.alive = None + self._reader_alive = None + self.receiver_thread = None + self.rx_decoder = None + self.tx_decoder = None + + def _start_reader(self): + """Start reader thread""" + self._reader_alive = True + # start serial->console thread + self.receiver_thread = threading.Thread(target=self.reader, name='rx') + self.receiver_thread.daemon = True + self.receiver_thread.start() + + def _stop_reader(self): + """Stop reader thread only, wait for clean exit of thread""" + self._reader_alive = False + if hasattr(self.serial, 'cancel_read'): + self.serial.cancel_read() + self.receiver_thread.join() + + def start(self): + """start worker threads""" + self.alive = True + self._start_reader() + # enter console->serial loop + self.transmitter_thread = threading.Thread(target=self.writer, name='tx') + self.transmitter_thread.daemon = True + self.transmitter_thread.start() + self.console.setup() + + def stop(self): + """set flag to stop worker threads""" + self.alive = False + + def join(self, transmit_only=False): + """wait for worker threads to terminate""" + self.transmitter_thread.join() + if not transmit_only: + if hasattr(self.serial, 'cancel_read'): + self.serial.cancel_read() + self.receiver_thread.join() + + def close(self): + self.serial.close() + + def update_transformations(self): + """take list of transformation classes and instantiate them for rx and tx""" + transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] + for f in self.filters] + self.tx_transformations = [t() for t in transformations] + self.rx_transformations = list(reversed(self.tx_transformations)) + + def set_rx_encoding(self, encoding, errors='replace'): + """set encoding for received data""" + self.input_encoding = encoding + self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors) + + def set_tx_encoding(self, encoding, errors='replace'): + """set encoding for transmitted data""" + self.output_encoding = encoding + self.tx_encoder = codecs.getincrementalencoder(encoding)(errors) + + def dump_port_settings(self): + """Write current settings to sys.stderr""" + sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format( + p=self.serial)) + sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format( + ('active' if self.serial.rts else 'inactive'), + ('active' if self.serial.dtr else 'inactive'), + ('active' if self.serial.break_condition else 'inactive'))) + try: + sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format( + ('active' if self.serial.cts else 'inactive'), + ('active' if self.serial.dsr else 'inactive'), + ('active' if self.serial.ri else 'inactive'), + ('active' if self.serial.cd else 'inactive'))) + except serial.SerialException: + # on RFC 2217 ports, it can happen if no modem state notification was + # yet received. ignore this error. + pass + sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive')) + sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive')) + sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding)) + sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding)) + sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper())) + sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters))) + + def reader(self): + """loop and copy serial->console""" + try: + while self.alive and self._reader_alive: + # read all that is there or wait for one byte + data = self.serial.read(self.serial.in_waiting or 1) + if data: + if self.raw: + self.console.write_bytes(data) + else: + text = self.rx_decoder.decode(data) + for transformation in self.rx_transformations: + text = transformation.rx(text) + self.console.write(text) + except serial.SerialException: + self.alive = False + self.console.cancel() + raise # XXX handle instead of re-raise? + + def writer(self): + """\ + Loop and copy console->serial until self.exit_character character is + found. When self.menu_character is found, interpret the next key + locally. + """ + menu_active = False + try: + while self.alive: + try: + c = self.console.getkey() + except KeyboardInterrupt: + c = '\x03' + if not self.alive: + break + if menu_active: + self.handle_menu_key(c) + menu_active = False + elif c == self.menu_character: + menu_active = True # next char will be for menu + elif c == self.exit_character: + self.stop() # exit app + break + else: + #~ if self.raw: + text = c + for transformation in self.tx_transformations: + text = transformation.tx(text) + self.serial.write(self.tx_encoder.encode(text)) + if self.echo: + echo_text = c + for transformation in self.tx_transformations: + echo_text = transformation.echo(echo_text) + self.console.write(echo_text) + except: + self.alive = False + raise + + def handle_menu_key(self, c): + """Implement a simple menu / settings""" + if c == self.menu_character or c == self.exit_character: + # Menu/exit character again -> send itself + self.serial.write(self.tx_encoder.encode(c)) + if self.echo: + self.console.write(c) + elif c == '\x15': # CTRL+U -> upload file + self.upload_file() + elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help + sys.stderr.write(self.get_help_text()) + elif c == '\x12': # CTRL+R -> Toggle RTS + self.serial.rts = not self.serial.rts + sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive')) + elif c == '\x04': # CTRL+D -> Toggle DTR + self.serial.dtr = not self.serial.dtr + sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive')) + elif c == '\x02': # CTRL+B -> toggle BREAK condition + self.serial.break_condition = not self.serial.break_condition + sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive')) + elif c == '\x05': # CTRL+E -> toggle local echo + self.echo = not self.echo + sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive')) + elif c == '\x06': # CTRL+F -> edit filters + self.change_filter() + elif c == '\x0c': # CTRL+L -> EOL mode + modes = list(EOL_TRANSFORMATIONS) # keys + eol = modes.index(self.eol) + 1 + if eol >= len(modes): + eol = 0 + self.eol = modes[eol] + sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper())) + self.update_transformations() + elif c == '\x01': # CTRL+A -> set encoding + self.change_encoding() + elif c == '\x09': # CTRL+I -> info + self.dump_port_settings() + #~ elif c == '\x01': # CTRL+A -> cycle escape mode + #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode + elif c in 'pP': # P -> change port + self.change_port() + elif c in 'zZ': # S -> suspend / open port temporarily + self.suspend_port() + elif c in 'bB': # B -> change baudrate + self.change_baudrate() + elif c == '8': # 8 -> change to 8 bits + self.serial.bytesize = serial.EIGHTBITS + self.dump_port_settings() + elif c == '7': # 7 -> change to 8 bits + self.serial.bytesize = serial.SEVENBITS + self.dump_port_settings() + elif c in 'eE': # E -> change to even parity + self.serial.parity = serial.PARITY_EVEN + self.dump_port_settings() + elif c in 'oO': # O -> change to odd parity + self.serial.parity = serial.PARITY_ODD + self.dump_port_settings() + elif c in 'mM': # M -> change to mark parity + self.serial.parity = serial.PARITY_MARK + self.dump_port_settings() + elif c in 'sS': # S -> change to space parity + self.serial.parity = serial.PARITY_SPACE + self.dump_port_settings() + elif c in 'nN': # N -> change to no parity + self.serial.parity = serial.PARITY_NONE + self.dump_port_settings() + elif c == '1': # 1 -> change to 1 stop bits + self.serial.stopbits = serial.STOPBITS_ONE + self.dump_port_settings() + elif c == '2': # 2 -> change to 2 stop bits + self.serial.stopbits = serial.STOPBITS_TWO + self.dump_port_settings() + elif c == '3': # 3 -> change to 1.5 stop bits + self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE + self.dump_port_settings() + elif c in 'xX': # X -> change software flow control + self.serial.xonxoff = (c == 'X') + self.dump_port_settings() + elif c in 'rR': # R -> change hardware flow control + self.serial.rtscts = (c == 'R') + self.dump_port_settings() + elif c in 'qQ': + self.stop() # Q -> exit app + else: + sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c))) + + def upload_file(self): + """Ask user for filenname and send its contents""" + sys.stderr.write('\n--- File to upload: ') + sys.stderr.flush() + with self.console: + filename = sys.stdin.readline().rstrip('\r\n') + if filename: + try: + with open(filename, 'rb') as f: + sys.stderr.write('--- Sending file {} ---\n'.format(filename)) + while True: + block = f.read(1024) + if not block: + break + self.serial.write(block) + # Wait for output buffer to drain. + self.serial.flush() + sys.stderr.write('.') # Progress indicator. + sys.stderr.write('\n--- File {} sent ---\n'.format(filename)) + except IOError as e: + sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e)) + + def change_filter(self): + """change the i/o transformations""" + sys.stderr.write('\n--- Available Filters:\n') + sys.stderr.write('\n'.join( + '--- {:<10} = {.__doc__}'.format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()))) + sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters))) + with self.console: + new_filters = sys.stdin.readline().lower().split() + if new_filters: + for f in new_filters: + if f not in TRANSFORMATIONS: + sys.stderr.write('--- unknown filter: {!r}\n'.format(f)) + break + else: + self.filters = new_filters + self.update_transformations() + sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters))) + + def change_encoding(self): + """change encoding on the serial port""" + sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding)) + with self.console: + new_encoding = sys.stdin.readline().strip() + if new_encoding: + try: + codecs.lookup(new_encoding) + except LookupError: + sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding)) + else: + self.set_rx_encoding(new_encoding) + self.set_tx_encoding(new_encoding) + sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding)) + sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding)) + + def change_baudrate(self): + """change the baudrate""" + sys.stderr.write('\n--- Baudrate: ') + sys.stderr.flush() + with self.console: + backup = self.serial.baudrate + try: + self.serial.baudrate = int(sys.stdin.readline().strip()) + except ValueError as e: + sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e)) + self.serial.baudrate = backup + else: + self.dump_port_settings() + + def change_port(self): + """Have a conversation with the user to change the serial port""" + with self.console: + try: + port = ask_for_port() + except KeyboardInterrupt: + port = None + if port and port != self.serial.port: + # reader thread needs to be shut down + self._stop_reader() + # save settings + settings = self.serial.getSettingsDict() + try: + new_serial = serial.serial_for_url(port, do_not_open=True) + # restore settings and open + new_serial.applySettingsDict(settings) + new_serial.rts = self.serial.rts + new_serial.dtr = self.serial.dtr + new_serial.open() + new_serial.break_condition = self.serial.break_condition + except Exception as e: + sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e)) + new_serial.close() + else: + self.serial.close() + self.serial = new_serial + sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port)) + # and restart the reader thread + self._start_reader() + + def suspend_port(self): + """\ + open port temporarily, allow reconnect, exit and port change to get + out of the loop + """ + # reader thread needs to be shut down + self._stop_reader() + self.serial.close() + sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port)) + do_change_port = False + while not self.serial.is_open: + sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format( + exit=key_description(self.exit_character))) + k = self.console.getkey() + if k == self.exit_character: + self.stop() # exit app + break + elif k in 'pP': + do_change_port = True + break + try: + self.serial.open() + except Exception as e: + sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e)) + if do_change_port: + self.change_port() + else: + # and restart the reader thread + self._start_reader() + sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port)) + + def get_help_text(self): + """return the help text""" + # help text, starts with blank line! + return """ +--- pySerial ({version}) - miniterm - help +--- +--- {exit:8} Exit program (alias {menu} Q) +--- {menu:8} Menu escape key, followed by: +--- Menu keys: +--- {menu:7} Send the menu character itself to remote +--- {exit:7} Send the exit character itself to remote +--- {info:7} Show info +--- {upload:7} Upload file (prompt will be shown) +--- {repr:7} encoding +--- {filter:7} edit filters +--- Toggles: +--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK +--- {echo:7} echo {eol:7} EOL +--- +--- Port settings ({menu} followed by the following): +--- p change port +--- 7 8 set data bits +--- N E O S M change parity (None, Even, Odd, Space, Mark) +--- 1 2 3 set stop bits (1, 2, 1.5) +--- b change baud rate +--- x X disable/enable software flow control +--- r R disable/enable hardware flow control +""".format(version=getattr(serial, 'VERSION', 'unknown version'), + exit=key_description(self.exit_character), + menu=key_description(self.menu_character), + rts=key_description('\x12'), + dtr=key_description('\x04'), + brk=key_description('\x02'), + echo=key_description('\x05'), + info=key_description('\x09'), + upload=key_description('\x15'), + repr=key_description('\x01'), + filter=key_description('\x06'), + eol=key_description('\x0c')) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# default args can be used to override when calling main() from an other script +# e.g to create a miniterm-my-device.py +def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None): + """Command line tool, entry point""" + + import argparse + + parser = argparse.ArgumentParser( + description='Miniterm - A simple terminal program for the serial port.') + + parser.add_argument( + 'port', + nargs='?', + help='serial port name ("-" to show port list)', + default=default_port) + + parser.add_argument( + 'baudrate', + nargs='?', + type=int, + help='set baud rate, default: %(default)s', + default=default_baudrate) + + group = parser.add_argument_group('port settings') + + group.add_argument( + '--parity', + choices=['N', 'E', 'O', 'S', 'M'], + type=lambda c: c.upper(), + help='set parity, one of {N E O S M}, default: N', + default='N') + + group.add_argument( + '--rtscts', + action='store_true', + help='enable RTS/CTS flow control (default off)', + default=False) + + group.add_argument( + '--xonxoff', + action='store_true', + help='enable software flow control (default off)', + default=False) + + group.add_argument( + '--rts', + type=int, + help='set initial RTS line state (possible values: 0, 1)', + default=default_rts) + + group.add_argument( + '--dtr', + type=int, + help='set initial DTR line state (possible values: 0, 1)', + default=default_dtr) + + group.add_argument( + '--non-exclusive', + dest='exclusive', + action='store_false', + help='disable locking for native ports', + default=True) + + group.add_argument( + '--ask', + action='store_true', + help='ask again for port when open fails', + default=False) + + group = parser.add_argument_group('data handling') + + group.add_argument( + '-e', '--echo', + action='store_true', + help='enable local echo (default off)', + default=False) + + group.add_argument( + '--encoding', + dest='serial_port_encoding', + metavar='CODEC', + help='set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s', + default='UTF-8') + + group.add_argument( + '-f', '--filter', + action='append', + metavar='NAME', + help='add text transformation', + default=[]) + + group.add_argument( + '--eol', + choices=['CR', 'LF', 'CRLF'], + type=lambda c: c.upper(), + help='end of line mode', + default='CRLF') + + group.add_argument( + '--raw', + action='store_true', + help='Do no apply any encodings/transformations', + default=False) + + group = parser.add_argument_group('hotkeys') + + group.add_argument( + '--exit-char', + type=int, + metavar='NUM', + help='Unicode of special character that is used to exit the application, default: %(default)s', + default=0x1d) # GS/CTRL+] + + group.add_argument( + '--menu-char', + type=int, + metavar='NUM', + help='Unicode code of special character that is used to control miniterm (menu), default: %(default)s', + default=0x14) # Menu: CTRL+T + + group = parser.add_argument_group('diagnostics') + + group.add_argument( + '-q', '--quiet', + action='store_true', + help='suppress non-error messages', + default=False) + + group.add_argument( + '--develop', + action='store_true', + help='show Python traceback on error', + default=False) + + args = parser.parse_args() + + if args.menu_char == args.exit_char: + parser.error('--exit-char can not be the same as --menu-char') + + if args.filter: + if 'help' in args.filter: + sys.stderr.write('Available filters:\n') + sys.stderr.write('\n'.join( + '{:<10} = {.__doc__}'.format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()))) + sys.stderr.write('\n') + sys.exit(1) + filters = args.filter + else: + filters = ['default'] + + while True: + # no port given on command line -> ask user now + if args.port is None or args.port == '-': + try: + args.port = ask_for_port() + except KeyboardInterrupt: + sys.stderr.write('\n') + parser.error('user aborted and port is not given') + else: + if not args.port: + parser.error('port is not given') + try: + serial_instance = serial.serial_for_url( + args.port, + args.baudrate, + parity=args.parity, + rtscts=args.rtscts, + xonxoff=args.xonxoff, + do_not_open=True) + + if not hasattr(serial_instance, 'cancel_read'): + # enable timeout for alive flag polling if cancel_read is not available + serial_instance.timeout = 1 + + if args.dtr is not None: + if not args.quiet: + sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive')) + serial_instance.dtr = args.dtr + if args.rts is not None: + if not args.quiet: + sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive')) + serial_instance.rts = args.rts + + if isinstance(serial_instance, serial.Serial): + serial_instance.exclusive = args.exclusive + + serial_instance.open() + except serial.SerialException as e: + sys.stderr.write('could not open port {!r}: {}\n'.format(args.port, e)) + if args.develop: + raise + if not args.ask: + sys.exit(1) + else: + args.port = '-' + else: + break + + miniterm = Miniterm( + serial_instance, + echo=args.echo, + eol=args.eol.lower(), + filters=filters) + miniterm.exit_character = unichr(args.exit_char) + miniterm.menu_character = unichr(args.menu_char) + miniterm.raw = args.raw + miniterm.set_rx_encoding(args.serial_port_encoding) + miniterm.set_tx_encoding(args.serial_port_encoding) + + if not args.quiet: + sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format( + p=miniterm.serial)) + sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format( + key_description(miniterm.exit_character), + key_description(miniterm.menu_character), + key_description(miniterm.menu_character), + key_description('\x08'))) + + miniterm.start() + try: + miniterm.join(True) + except KeyboardInterrupt: + pass + if not args.quiet: + sys.stderr.write('\n--- exit ---\n') + miniterm.join() + miniterm.close() + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + main() diff --git a/packages_for_clean_installation /serial/tools/miniterm.pyc b/packages_for_clean_installation /serial/tools/miniterm.pyc new file mode 100644 index 0000000..3e6e5cb Binary files /dev/null and b/packages_for_clean_installation /serial/tools/miniterm.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__init__.py b/packages_for_clean_installation /serial/urlhandler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages_for_clean_installation /serial/urlhandler/__init__.pyc b/packages_for_clean_installation /serial/urlhandler/__init__.pyc new file mode 100644 index 0000000..17d71fe Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__init__.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/__init__.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..084a746 Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/__init__.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_alt.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_alt.cpython-38.pyc new file mode 100644 index 0000000..3deef45 Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_alt.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_cp2110.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_cp2110.cpython-38.pyc new file mode 100644 index 0000000..5846ed3 Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_cp2110.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_hwgrep.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_hwgrep.cpython-38.pyc new file mode 100644 index 0000000..44ed7a9 Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_hwgrep.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_loop.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_loop.cpython-38.pyc new file mode 100644 index 0000000..65b65cf Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_loop.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_rfc2217.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_rfc2217.cpython-38.pyc new file mode 100644 index 0000000..2a66b33 Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_rfc2217.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_socket.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_socket.cpython-38.pyc new file mode 100644 index 0000000..b03bf14 Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_socket.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_spy.cpython-38.pyc b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_spy.cpython-38.pyc new file mode 100644 index 0000000..b854cfd Binary files /dev/null and b/packages_for_clean_installation /serial/urlhandler/__pycache__/protocol_spy.cpython-38.pyc differ diff --git a/packages_for_clean_installation /serial/urlhandler/protocol_alt.py b/packages_for_clean_installation /serial/urlhandler/protocol_alt.py new file mode 100644 index 0000000..2e666ca --- /dev/null +++ b/packages_for_clean_installation /serial/urlhandler/protocol_alt.py @@ -0,0 +1,57 @@ +#! python +# +# This module implements a special URL handler that allows selecting an +# alternate implementation provided by some backends. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +# +# URL format: alt://port[?option[=value][&option[=value]]] +# options: +# - class=X used class named X instead of Serial +# +# example: +# use poll based implementation on Posix (Linux): +# python -m serial.tools.miniterm alt:///dev/ttyUSB0?class=PosixPollSerial + +from __future__ import absolute_import + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +import serial + + +def serial_class_for_url(url): + """extract host and port from an URL string""" + parts = urlparse.urlsplit(url) + if parts.scheme != 'alt': + raise serial.SerialException( + 'expected a string in the form "alt://port[?option[=value][&option[=value]]]": ' + 'not starting with alt:// ({!r})'.format(parts.scheme)) + class_name = 'Serial' + try: + for option, values in urlparse.parse_qs(parts.query, True).items(): + if option == 'class': + class_name = values[0] + else: + raise ValueError('unknown option: {!r}'.format(option)) + except ValueError as e: + raise serial.SerialException( + 'expected a string in the form ' + '"alt://port[?option[=value][&option[=value]]]": {!r}'.format(e)) + if not hasattr(serial, class_name): + raise ValueError('unknown class: {!r}'.format(class_name)) + cls = getattr(serial, class_name) + if not issubclass(cls, serial.Serial): + raise ValueError('class {!r} is not an instance of Serial'.format(class_name)) + return (''.join([parts.netloc, parts.path]), cls) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + s = serial.serial_for_url('alt:///dev/ttyS0?class=PosixPollSerial') + print(s) diff --git a/packages_for_clean_installation /serial/urlhandler/protocol_cp2110.py b/packages_for_clean_installation /serial/urlhandler/protocol_cp2110.py new file mode 100644 index 0000000..44ad4eb --- /dev/null +++ b/packages_for_clean_installation /serial/urlhandler/protocol_cp2110.py @@ -0,0 +1,258 @@ +#! python +# +# Backend for Silicon Labs CP2110/4 HID-to-UART devices. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2015 Chris Liechti +# (C) 2019 Google LLC +# +# SPDX-License-Identifier: BSD-3-Clause + +# This backend implements support for HID-to-UART devices manufactured +# by Silicon Labs and marketed as CP2110 and CP2114. The +# implementation is (mostly) OS-independent and in userland. It relies +# on cython-hidapi (https://github.com/trezor/cython-hidapi). + +# The HID-to-UART protocol implemented by CP2110/4 is described in the +# AN434 document from Silicon Labs: +# https://www.silabs.com/documents/public/application-notes/AN434-CP2110-4-Interface-Specification.pdf + +# TODO items: + +# - rtscts support is configured for hardware flow control, but the +# signaling is missing (AN434 suggests this is done through GPIO). +# - Cancelling reads and writes is not supported. +# - Baudrate validation is not implemented, as it depends on model and configuration. + +import struct +import threading + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +try: + import Queue +except ImportError: + import queue as Queue + +import hid # hidapi + +import serial +from serial.serialutil import SerialBase, SerialException, PortNotOpenError, to_bytes, Timeout + + +# Report IDs and related constant +_REPORT_GETSET_UART_ENABLE = 0x41 +_DISABLE_UART = 0x00 +_ENABLE_UART = 0x01 + +_REPORT_SET_PURGE_FIFOS = 0x43 +_PURGE_TX_FIFO = 0x01 +_PURGE_RX_FIFO = 0x02 + +_REPORT_GETSET_UART_CONFIG = 0x50 + +_REPORT_SET_TRANSMIT_LINE_BREAK = 0x51 +_REPORT_SET_STOP_LINE_BREAK = 0x52 + + +class Serial(SerialBase): + # This is not quite correct. AN343 specifies that the minimum + # baudrate is different between CP2110 and CP2114, and it's halved + # when using non-8-bit symbols. + BAUDRATES = (300, 375, 600, 1200, 1800, 2400, 4800, 9600, 19200, + 38400, 57600, 115200, 230400, 460800, 500000, 576000, + 921600, 1000000) + + def __init__(self, *args, **kwargs): + self._hid_handle = None + self._read_buffer = None + self._thread = None + super(Serial, self).__init__(*args, **kwargs) + + def open(self): + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + + self._read_buffer = Queue.Queue() + + self._hid_handle = hid.device() + try: + portpath = self.from_url(self.portstr) + self._hid_handle.open_path(portpath) + except OSError as msg: + raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) + + try: + self._reconfigure_port() + except: + try: + self._hid_handle.close() + except: + pass + self._hid_handle = None + raise + else: + self.is_open = True + self._thread = threading.Thread(target=self._hid_read_loop) + self._thread.setDaemon(True) + self._thread.setName('pySerial CP2110 reader thread for {}'.format(self._port)) + self._thread.start() + + def from_url(self, url): + parts = urlparse.urlsplit(url) + if parts.scheme != "cp2110": + raise SerialException( + 'expected a string in the forms ' + '"cp2110:///dev/hidraw9" or "cp2110://0001:0023:00": ' + 'not starting with cp2110:// {{!r}}'.format(parts.scheme)) + if parts.netloc: # cp2100://BUS:DEVICE:ENDPOINT, for libusb + return parts.netloc.encode('utf-8') + return parts.path.encode('utf-8') + + def close(self): + self.is_open = False + if self._thread: + self._thread.join(1) # read timeout is 0.1 + self._thread = None + self._hid_handle.close() + self._hid_handle = None + + def _reconfigure_port(self): + parity_value = None + if self._parity == serial.PARITY_NONE: + parity_value = 0x00 + elif self._parity == serial.PARITY_ODD: + parity_value = 0x01 + elif self._parity == serial.PARITY_EVEN: + parity_value = 0x02 + elif self._parity == serial.PARITY_MARK: + parity_value = 0x03 + elif self._parity == serial.PARITY_SPACE: + parity_value = 0x04 + else: + raise ValueError('Invalid parity: {!r}'.format(self._parity)) + + if self.rtscts: + flow_control_value = 0x01 + else: + flow_control_value = 0x00 + + data_bits_value = None + if self._bytesize == 5: + data_bits_value = 0x00 + elif self._bytesize == 6: + data_bits_value = 0x01 + elif self._bytesize == 7: + data_bits_value = 0x02 + elif self._bytesize == 8: + data_bits_value = 0x03 + else: + raise ValueError('Invalid char len: {!r}'.format(self._bytesize)) + + stop_bits_value = None + if self._stopbits == serial.STOPBITS_ONE: + stop_bits_value = 0x00 + elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: + stop_bits_value = 0x01 + elif self._stopbits == serial.STOPBITS_TWO: + stop_bits_value = 0x01 + else: + raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits)) + + configuration_report = struct.pack( + '>BLBBBB', + _REPORT_GETSET_UART_CONFIG, + self._baudrate, + parity_value, + flow_control_value, + data_bits_value, + stop_bits_value) + + self._hid_handle.send_feature_report(configuration_report) + + self._hid_handle.send_feature_report( + bytes((_REPORT_GETSET_UART_ENABLE, _ENABLE_UART))) + self._update_break_state() + + @property + def in_waiting(self): + return self._read_buffer.qsize() + + def reset_input_buffer(self): + if not self.is_open: + raise PortNotOpenError() + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_RX_FIFO))) + # empty read buffer + while self._read_buffer.qsize(): + self._read_buffer.get(False) + + def reset_output_buffer(self): + if not self.is_open: + raise PortNotOpenError() + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_TX_FIFO))) + + def _update_break_state(self): + if not self._hid_handle: + raise PortNotOpenError() + + if self._break_state: + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_TRANSMIT_LINE_BREAK, 0))) + else: + # Note that while AN434 states "There are no data bytes in + # the payload other than the Report ID", either hidapi or + # Linux does not seem to send the report otherwise. + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_STOP_LINE_BREAK, 0))) + + def read(self, size=1): + if not self.is_open: + raise PortNotOpenError() + + data = bytearray() + try: + timeout = Timeout(self._timeout) + while len(data) < size: + if self._thread is None: + raise SerialException('connection failed (reader thread died)') + buf = self._read_buffer.get(True, timeout.time_left()) + if buf is None: + return bytes(data) + data += buf + if timeout.expired(): + break + except Queue.Empty: # -> timeout + pass + return bytes(data) + + def write(self, data): + if not self.is_open: + raise PortNotOpenError() + data = to_bytes(data) + tx_len = len(data) + while tx_len > 0: + to_be_sent = min(tx_len, 0x3F) + report = to_bytes([to_be_sent]) + data[:to_be_sent] + self._hid_handle.write(report) + + data = data[to_be_sent:] + tx_len = len(data) + + def _hid_read_loop(self): + try: + while self.is_open: + data = self._hid_handle.read(64, timeout_ms=100) + if not data: + continue + data_len = data.pop(0) + assert data_len == len(data) + self._read_buffer.put(bytearray(data)) + finally: + self._thread = None diff --git a/packages_for_clean_installation /serial/urlhandler/protocol_hwgrep.py b/packages_for_clean_installation /serial/urlhandler/protocol_hwgrep.py new file mode 100644 index 0000000..1a288c9 --- /dev/null +++ b/packages_for_clean_installation /serial/urlhandler/protocol_hwgrep.py @@ -0,0 +1,91 @@ +#! python +# +# This module implements a special URL handler that uses the port listing to +# find ports by searching the string descriptions. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +# +# URL format: hwgrep://&