GUI_validator.py

from optparse import OptionParser
from pprint import pprint
from selenium import webdriver
from timed_job import Timed_Job, do_GUI_validator_test
import os
import re
import select
import signal
import subprocess
import sys
import time
#from runner._subproc import subproc, start_async_subproc, stop_async_subporc, waiting_input
from _subproc import subproc, start_async_subproc, stop_async_subporc, waiting_input

class GUI_validator() :
    def __init__(self, product_type=None, product_version=None,
                 case_id=None, browser=None,
                 base_url=None, grid_server_url=None,
                 local=False, debug=False) :
        """
        """
        self.browser_support = ['chrome', 'firefox', 'internet explorer', 'safari']
        self.platform_support = ['WINDOWS', 'LINUX', 'MAC']
        
        if case_id :
            self.case_id = case_id
        else :
            U_CUSTOM_CURRENT_CASE_ID = os.getenv('U_CUSTOM_CURRENT_CASE_ID')
            if U_CUSTOM_CURRENT_CASE_ID:
                self.case_id = U_CUSTOM_CURRENT_CASE_ID[:8]
            
        if product_type :
            self.product_type = product_type
        else :
            self.product_type = os.getenv('U_DUT_TYPE')
            
        if product_version :
            self.product_version = product_version
        else :
            self.product_version = os.getenv('U_CUSTOM_CURRENT_FW_VER')
            
        if base_url :
            self.base_url = base_url
        else :
            if local :
                self.base_url = 'http://' + os.getenv('G_PROD_IP_BR0_0_0') + '/'
            else :
                self.base_url = 'http://%s:%s/' % (os.getenv('U_TESTBED_SSH_IP', ''), os.getenv('U_SELENIUM_SERVER_DPORT', ''))

        if local :
            self.driver = webdriver.Firefox()
            self.driver.implicitly_wait(30)
            self.driver.delete_all_cookies()
            self.driver.maximize_window()
        else :
            if grid_server_url :
                self.grid_server_url = grid_server_url
            else :
                self.grid_server_url = os.getenv('G_SELENIUM_SERVER_URL',
                                                 'http://192.168.20.106:4444/wd/hub')
                
            if browser :
                self.browser = browser
            else:
                self.browser = {
                                'browserName' : 'firefox',
                                'platform' : 'WINDOWS',
                                }
                print "AT_WARNING : Not specified the browser info, use default setting browserName=firefox , platform=WINDOWS ."

            desired_capabilities = {}
            
            if self.browser.get('browserName') :
                if self.browser.get('browserName') in self.browser_support :
                    desired_capabilities.update({'browserName': self.browser.get('browserName').strip().lower()})
                else :
                    print "AT_ERROR : Not support browser type : <" , self.browser.get('browserName') , ">"
                    print self.browser_support
            else :
                print "AT_ERROR : Not define browser name"
            
            if self.browser.get('platform') :
                if self.browser.get('platform') in self.platform_support :
                    desired_capabilities.update({'platform': self.browser.get('platform').strip().upper()})
                else :
                    print "AT_ERROR : Not support platform type : <" , self.browser.get('platform') , ">"
                    print self.platform_support
            else :
                print "AT_ERROR : Not define browser platform>"
            
            if self.browser.get('version') :
                desired_capabilities.update({'version': self.browser.get('version').strip()})
            else :
                print "AT_WARNING : Not specified the browser version, use default setting version=any "
                desired_capabilities.update({'version': 'ANY'})
                
            desired_capabilities.update({"javascriptEnabled": True})
            
            self.driver = webdriver.Remote(self.grid_server_url, desired_capabilities)
            self.driver.implicitly_wait(30)
            self.driver.delete_all_cookies()
            self.driver.maximize_window()
            
        if self.product_type :
            cmd = 'from Runner import ' + self.product_type + ' as RUNNER'
            print cmd
            try:
                exec(cmd)
                Runner = RUNNER.getRunner(self.product_version)
                if Runner :
                    self.runner = Runner(self.driver, self.case_id, self.product_type, self.product_version, self.base_url, debug)
            except Exception, e :
                print 'Exception : ' + str(e)
                self.quit_drv()
            
            if not self.runner :
                self.error('Can not find Runner for ' + product_type)
                self.quit_drv()
        else:
            print 'AT_ERROR : Not specify product type'
            self.quit_drv()
    
    def quit_drv(self):
        """
        """
        driver = self.driver
        driver.quit()
        
    def login(self) :
        """
        """
        try:
            if self.runner.login():
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
        
    def tr_setting(self) :
        """
        """
        try:
            self.login()
            if self.runner.tr_setting():
                self.logout()
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
    
    def wan_setting(self, params={}) :
        """
        """
        try:
            self.login()
            if self.runner.configure_wan(params=params):
                self.logout()
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
    
    def telnet_setting(self, params={}) :
        """
        """
        
        if not params.has_key('status'):
            params.update({
                           'status':'local'
                           })
        
        try:
            print 'tring to login'
            
            self.login()
            if self.runner.telnet_setting(params=params):
                self.logout()
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
        
    def restore_default(self) :
        """
        """
        try:
            self.login()
            if self.runner.restore_default():
#                 self.logout()
                self.quit_drv()
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
        
    def gui_check(self) :
        """
        """
        try:
            if self.runner.gui_check():
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
        
    def goto(self, uri):
        """
        """
        try:
            if self.runner.goto(uri):
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
            
    def logout(self) :
        """
        """
        try:
            if self.runner.logout():
                self.quit_drv()
                return True
            else:
                self.quit_drv()
                return False
        except Exception, e :
            print 'Exception : ' + str(e)
            self.quit_drv()
            return False
    
def parseCommandLine() :
    """
    parse command line
    """
    usage = "usage: %prog [options]\n"
    usage += ('\nGet detail introduction and sample usange with command : pydoc ' + os.path.abspath(__file__) + '\n\n')
    
    parser = OptionParser(usage=usage)
    parser.add_option("-v", "--variableOption", dest="vos", action="append",
                            help="The environment to set, format is key=value")
    parser.add_option("-t", "--product_type", dest="product_type",
                            help="The product type ,such as CTLC2KA")
    parser.add_option("-f", "--product_version", dest="product_version",
                            help="The product version ,such as CAH004-31.30L.51N")
    parser.add_option("-c", "--case_id", dest="case_id",
                            help="The case id ,such as '00601162")
    parser.add_option("-b", "--browser", dest="browsers", action="append",
                            help="""
The browser info. This parameter can be set multiple 
times on the same line to define multiple types of browsers.
Parameters allowed for --browser: 
browserName={chrome, firefox, internet explorer, safari} 
version={browser version} platform={WINDOWS, LINUX, MAC}
eg :
--browser "browserName=firefox,version=22,platform=WINDOWS"
                            """
                            )
    parser.add_option("-l", "--local", dest="local", action="store_true",
                            default=False , help="choose the local mode or remote mode")
    parser.add_option("-z", "--logout", dest="logout", action="store_true",
                            default=False , help="logout after action")
    parser.add_option("-m", "--multi", dest="multi", action="store_true",
                            default=False , help="single process mode or multiprocess mode")
    parser.add_option("-d", "--debug", dest="debug", action="store_true",
                            default=False , help="print debug message")
    parser.add_option("-g", "--grid_server_url", dest="grid_server_url",
                            help="The grid server URL,such as http://192.168.8.46:4444/wd/hub")
    parser.add_option("-u", "--base_url", dest="base_url",
                            help="The base URL,how to visit DUT from grid server")
    parser.add_option("--check_element", dest="check_element", action="store_true",
                            default=False , help="check element is valid")

    (options, args) = parser.parse_args()
    
    if not len(args) == 0 :
        print args
        
    if options.vos :
        for kv in options.vos :
            parseVerb(kv)

    return args, options

def parseBrowsers(browsers) :
    """
    """
    browser_list = []
    if browsers :
        for browser in browsers :
            browser_info = browser.split(',')
            if browser_info :
                browser_list.append(browser_info)
                
    return browser_list

def parseBrowser(browser) :
    """
    """
    browserInfo = {}
    for info in browser :
        print info
        k, v = info.split('=')
        browserInfo.update({k.strip() : v.strip()})
    
    return browserInfo

def parseVerb(kv) :
    """
    """
    match = r'(\w*)=(.*)'
    res = re.findall(match, kv)
    for (k, v) in res :
        (k, v) = res[0]
        print '==', 'import env :', k, ' = ', v
        os.environ.update({k : v})

def exportCurrentPath() :
    """
    """
    import os, sys

    path = sys.path[0]
    if os.path.isdir(path) :
        pass
    elif os.path.isfile(path) :
        path = os.path.dirname(path)
        
    print '==add path :' , path
    sys.path.append(path)
    
def add_iptables_rule():
    """
    iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 10080 -j DNAT --to 192.168.0.1:80
    iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to 192.168.0.100
    """
    lan_mngmt_if = os.getenv('G_HOST_IF0_0_0', 'eth0')
    lan_active_if = os.getenv('G_HOST_IF0_1_0', 'eth1')
    dport = os.getenv('U_SELENIUM_SERVER_DPORT', '10080')
    dut_ip = os.getenv('G_PROD_IP_BR0_0_0', '192.168.1.254')
    lan_active_ip = os.getenv('G_HOST_TIP0_1_0', '192.168.1.100')
    
    cmd = "iptables -t nat -A PREROUTING -i %s -p tcp --dport %s -j DNAT --to %s:80" % (lan_mngmt_if, dport, dut_ip)
    rc, output = subproc(cmd)
    if not str(rc) == '0' :
        print "launch command <%s> failed" % cmd
        exit(1) 
    
    cmd = "iptables -t nat -A POSTROUTING -o %s -j SNAT --to %s" % (lan_active_if, lan_active_ip)
    rc, output = subproc(cmd)
    if not str(rc) == '0' :
        print "launch command <%s> failed" % cmd
        exit(1)

def del_iptables_rule():
    """
    iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 10080 -j DNAT --to 192.168.0.1:80
    iptables -t nat -D POSTROUTING -o eth1 -j SNAT --to 192.168.0.100
    """
    lan_mngmt_if = os.getenv('G_HOST_IF0_0_0', 'eth0')
    lan_active_if = os.getenv('G_HOST_IF0_1_0', 'eth1')
    dport = os.getenv('U_SELENIUM_SERVER_DPORT', '10080')
    dut_ip = os.getenv('G_PROD_IP_BR0_0_0', '192.168.1.254')
    lan_active_ip = os.getenv('G_HOST_TIP0_1_0', '192.168.1.100')
    
    cmd = "iptables -t nat -D PREROUTING -i %s -p tcp --dport %s -j DNAT --to %s:80" % (lan_mngmt_if, dport, dut_ip)
    rc, output = subproc(cmd)
    if not str(rc) == '0' :
        print "launch command <%s> failed" % cmd
    
    cmd = "iptables -t nat -D POSTROUTING -o %s -j SNAT --to %s" % (lan_active_if, lan_active_ip)
    rc, output = subproc(cmd)
    if not str(rc) == '0' :
        print "launch command <%s> failed" % cmd
    
def per_action() :
    """
    """
    add_iptables_rule()

def post_action() :
    """
    """
    del_iptables_rule()

def multiprocess_mode(product_type, product_version, case_id, browser_list, base_url, grid_server_url, local, debug, logout):
    """
    """
    idx = 1
    jobs = {}
    name = None
    for browser in browser_list :
        name = '_'.join(browser) + '_' + str(idx)
        jobs[name] = Timed_Job(target=gui_check ,
                               args=(product_type, product_version,
                                     case_id, browser, base_url,
                                     grid_server_url, local,
                                     logout, debug)
                               )
        jobs[name].name = name
        jobs[name].setup(interval=1, max_count=1)
        idx += 1
    return do_GUI_validator_test(jobs)
    
def singleprocess_mode(product_type, product_version, case_id, browser_list, base_url, grid_server_url, local, debug, logout):
    """
    """
    rc = 0
    idx = 1
    name = None
    for browser in browser_list :
        name = ' '.join(browser) + ' ' + str(idx)
        if gui_check(product_type, product_version, case_id, browser, base_url, grid_server_url, local, debug, logout) :
            print "AT_INFO : <%s> check GUI is Successful" % name
        else :
            print "AT_ERROR : <%s> check GUI is failed" % name
            rc = 1
    return rc
        
def gui_check(product_type, product_version, case_id, browser, base_url, grid_server_url, local, debug, logout) :
    """
    """
    browserInfo = parseBrowser(browser)
    gv = GUI_validator(product_type, product_version, case_id, browserInfo, base_url, grid_server_url, local, debug)
    if gv.login() :
        if gv.gui_check() :
            if logout :
                gv.logout()
                return True
        else :
            if logout :
                gv.logout()
    gv.quit_drv()
    return False

def main() :
    args, opts = parseCommandLine()

    exportCurrentPath()
    
    browser_list = []
    if opts.browsers :
        browser_list = parseBrowsers(opts.browsers)
    elif os.getenv('U_CUSTOM_GUI_CHECK_BROWSERS_INFO') :
        browser_list = parseBrowsers(os.getenv('U_CUSTOM_GUI_CHECK_BROWSERS_INFO'))
    else :
        browser_list.append(['browserName=firefox', 'platform=WINDOWS'])
        browser_list.append(['browserName=chrome', 'platform=WINDOWS'])
        browser_list.append(['browserName=internet explorer', 'platform=WINDOWS'])
#        browser_list.append(['browserName=safari', 'platform=MAC'])
        
    if opts.product_type :
        product_type = opts.product_type
    else :
        product_type = os.getenv('U_DUT_TYPE')
        
    if opts.product_version :
        product_version = opts.product_version
    else :
        product_version = os.getenv('U_CUSTOM_CURRENT_FW_VER')
        
    if not opts.check_element :
        if opts.case_id :
            case_id = opts.case_id
        else :
            case_id = os.getenv('U_CUSTOM_CURRENT_CASE_ID')[:8]
    else :
        case_id = None
        
    if opts.base_url : 
        base_url = opts.base_url
    else :
        if opts.local :
            base_url = 'http://' + os.getenv('G_PROD_IP_BR0_0_0', '') + '/'
        else :
            base_url = 'http://%s:%s/' % (os.getenv('U_TESTBED_SSH_IP', ''), os.getenv('U_SELENIUM_SERVER_DPORT', ''))
        
    if opts.grid_server_url : 
        grid_server_url = opts.grid_server_url
    else :
        grid_server_url = os.getenv('G_SELENIUM_SERVER_URL',
                             'http://192.168.20.106:4444/wd/hub')
    
    per_action()
    
    rc = 0
    try:
        if opts.check_element :
            rc = check_element(product_type, product_version,
                               case_id, browser_list, base_url,
                               grid_server_url, opts.local,
                               opts.debug, opts.logout)
            pass
        else :
            if opts.multi :
                rc = multiprocess_mode(product_type, product_version,
                                       case_id, browser_list, base_url,
                                       grid_server_url, opts.local,
                                       opts.debug, opts.logout)
            else :
                rc = singleprocess_mode(product_type, product_version,
                                        case_id, browser_list, base_url,
                                        grid_server_url, opts.local,
                                        opts.debug, opts.logout)
    except Exception,e:
        post_action()
        
    post_action()
    

    exit(rc)

if __name__ == '__main__' :
    """
    """
    main()


具体的case调用命令

<testcase>
    <name>00203693_Primary_DNS_server_is_available.xml</name>
    <emaildesc>
        00203693_Primary DNS server is available
    </emaildesc>
    <description>
        00203693_Primary DNS server is available
    </description>
    <id>
        <manual></manual>
        <auto></auto>
        <code>
        </code>
    </id>
    <stage>
        <step>
            <name>0</name>
            <desc>verify DUT LAN connection</desc>
            <script>
                bash $U_PATH_TBIN/verifyDutLanConnected.sh -t 120;
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed>1000</failed>
        </step>
        <step>
            <name>1</name>
            <desc>Disconnect the link between SWB and DUT</desc>
            <script>
                python $U_PATH_TOOLS/common/SwapWANLink_s.py -d;
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed>1000</failed>
        </step>
        <step>
            <name>2</name>
            <desc>Reboot DUT</desc>
            <script>
                python $U_PATH_TOOLS/common/SwapWANLink_s.py -u;
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed>1000</failed>
        </step>
        <step>
            <name>3</name>
            <desc>Set WAN Server to IPoE mode</desc>
            <script>
                python $U_PATH_TOOLS/common/SwapWANLink_s.py -w -p IPoE -l ETH;
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed>1000</failed>
        </step>
        <step>
            <name>4</name>
            <desc>Resume connection between SWB and DUT</desc>
            <script>
                python $U_PATH_TOOLS/common/SwapWANLink_s.py -j -l ETH;
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed>1000</failed>
        </step>
        <step>
            <name>5</name>
            <desc>Set ISP protocol to IPoE and dynamic dns.</desc>
            <script>
                python $U_PATH_TOOLS/common/SwapWANLink_s.py -g -p IPoE -l ETH --dns $U_DUT_CUSTOM_LAN_DNS_1,$U_DUT_CUSTOM_LAN_DNS_2;
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed>1000</failed>
        </step>
        <step>
            <name>6</name>
            <desc>FUNC check</desc>
            <script>
                bash $U_PATH_TBIN/verifyDutWanConnected.sh -t 180;
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed>1000</failed>
        </step>
		<step>
			<name>7</name>
			<desc>
				Start capture packets on WAN PC
			</desc>
			<script>
				bash $U_PATH_TBIN/raw_capture.sh -r -i $G_HOST_IF1_2_0 -o Test_DNS_WAN_1.cap --begin
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
		<step>
			<name>8</name>
			<desc>
				Start capture packets on LAN PC
			</desc>
			<script>
				bash $U_PATH_TBIN/raw_capture.sh --local -i $G_HOST_IF0_1_0 -o Test_DNS_LAN_1.cap --begin
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
        <step>
			<name>9</name>
			<desc>
				Initiate a DNS query on the LAN device.
			</desc>
			<script>
				ping $U_CUSTOM_INTERNET_HOST_NAME1 -c 20
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
        <step>
			<name>10</name>
			<desc>
				Stop capture packets on WAN PC
			</desc>
			<script>
                bash $U_PATH_TBIN/raw_capture.sh -r -i $G_HOST_IF1_2_0 -o Test_DNS_WAN_1.cap --stop
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
        <step>
			<name>11</name>
			<desc>
				Stop capture packets on LAN PC
			</desc>
			<script>
                bash $U_PATH_TBIN/raw_capture.sh --local -i $G_HOST_IF0_1_0 -o Test_DNS_LAN_1.cap --stop
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
		<step>
			<name>12</name>
			<desc>
				Parse WAN PC capture packets to verify DUT packets
			</desc>
			<script>
                bash $U_PATH_TBIN/tshark_capture.sh -r $G_CURRENTLOG/Test_DNS_WAN_1.cap -R "dns.qry.type==a and ip.dst==${U_DUT_CUSTOM_LAN_DNS_1} and ip.src==${TMP_DUT_WAN_IP}" -V -o $G_CURRENTLOG/ResultCaptureWan_1.log
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
		<step>
			<name>13</name>
			<desc>
				Parse WAN PC capture packets to verify DUT packets
			</desc>
			<script>
                bash $U_PATH_TBIN/tshark_capture.sh -r $G_CURRENTLOG/Test_DNS_WAN_1.cap -R "dns.qry.type==a and ip.dst==${U_DUT_CUSTOM_LAN_DNS_2} and ip.src==${TMP_DUT_WAN_IP}" -V -n -o $G_CURRENTLOG/ResultCaptureWan_2.log
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
		<step>
			<name>14</name>
			<desc>
				Parse WAN PC capture packets to verify DUT packets
			</desc>
			<script>
                bash $U_PATH_TBIN/tshark_capture.sh -r $G_CURRENTLOG/Test_DNS_LAN_1.cap -R "dns.resp.type==a and ip.src==${G_PROD_BR0_0_0}" -V -o $G_CURRENTLOG/ResultCaptureLan_1.log
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
		<step>
			<name>15</name>
			<desc>Check result after capture packets of WAN</desc>
			<script>
                perl $U_PATH_TBIN/searchoperation.pl -e "${U_CUSTOM_INTERNET_HOST_NAME1}: type A, class IN"  -f $G_CURRENTLOG/ResultCaptureWan_1.log
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
		<step>
			<name>16</name>
			<desc>Check result after capture packets of LAN</desc>
			<script>
                perl $U_PATH_TBIN/searchoperation.pl -e "${U_CUSTOM_INTERNET_HOST_NAME1}: type A, class IN, addr "  -f $G_CURRENTLOG/ResultCaptureLan_1.log
			</script>
			<noerrorcheck></noerrorcheck>
			<passed></passed>
			<failed>1000</failed>
        </step>
        <step>
            <name>17</name>
            <desc>GUI Check</desc>
            <script>
                python $U_PATH_TOOLS/GUI_validator/GUI_validator.py;
            </script>
            <noerrorcheck>1</noerrorcheck>
            <passed>10000</passed>
            <failed>1000</failed>
        </step>
        <step>
            <name>1000</name>
            <desc>ERROR</desc>
            <script>
                echo "ERROR";
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed></failed>
        </step>
        <step>
            <name>10000</name>
            <desc>END</desc>
            <script>
                echo "THE END";
            </script>
            <noerrorcheck></noerrorcheck>
            <passed></passed>
            <failed></failed>
        </step>
    </stage>
</testcase>


看这些代码需要 加上 异常控制,try/catch

转载于:https://my.oschina.net/xxjbs001/blog/223479

这是当前版本的结构图这是当前的结构图lufei@fedora:~/文档/聚核助手2.0$ tree -L 4 . ├── aicr.txt ├── assets │   ├── __init__.py │   └── response.mp3 ├── assistant.log ├── audio_test.py ├── backup_20250715_0859.zip ├── batch_update_entry_points.py ├── certs │   ├── ca-bundle.crt -> /etc/ssl/certs/ca-bundle.crt │   ├── eventbus.crt │   ├── eventbus.key │   └── __init__.py ├── chatgpt_importer.py ├── check_syntax.py ├── cleanup_legacy.py ├── code_metrics.csv ├── config │   ├── config.py │   ├── global_config.yaml │   ├── __init__.py │   ├── __pycache__ │   │   └── __init__.cpython-313.pyc │   └── user_config.yaml ├── config.py ├── config.yaml ├── conversations.json ├── core │   ├── aicore │   │   ├── action_manager.py │   │   ├── actions │   │   │   ├── action_dispatcher.py │   │   │   ├── action_handlers.py │   │   │   ├── actions.yaml │   │   │   ├── __init__.py │   │   │   └── __pycache__ │   │   ├── aicore.py │   │   ├── circuit_breaker.py │   │   ├── command_router.py │   │   ├── context_manager.py │   │   ├── health_monitor.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── memory │   │   │   ├── __init__.py │   │   │   ├── memory_data.json │   │   │   ├── memory_engine.py │   │   │   ├── memory.json │   │   │   ├── memory_log.txt │   │   │   ├── memory_manager.py │   │   │   └── __pycache__ │   │   ├── model_engine.py │   │   └── __pycache__ │   │   ├── action_manager.cpython-313.pyc │   │   ├── aicore.cpython-313.pyc │   │   ├── circuit_breaker.cpython-313.pyc │   │   ├── command_router.cpython-313.pyc │   │   ├── context_manager.cpython-313.pyc │   │   ├── health_monitor.cpython-313.pyc │   │   ├── __init__.cpython-313.pyc │   │   └── model_engine.cpython-313.pyc │   ├── core2_0 │   │   ├── action_dispatcher.py │   │   ├── aicore -> ../../core/aicore │   │   ├── certs │   │   │   └── __init__.py │   │   ├── cli.py │   │   ├── config_manager.py │   │   ├── config.py │   │   ├── error_logger.py │   │   ├── event_bus.py │   │   ├── framework │   │   │   └── __init__.py │   │   ├── __init__.py │   │   ├── jujue_module_generator.py │   │   ├── jumo_core.py │   │   ├── logger.py │   │   ├── module_loader.py │   │   ├── module_manager.py │   │   ├── module_watcher.py │   │   ├── __pycache__ │   │   │   ├── event_bus.cpython-313.pyc │   │   │   ├── __init__.cpython-313.pyc │   │   │   ├── reply_dispatcher.cpython-313.pyc │   │   │   └── trace_logger.cpython-313.pyc │   │   ├── query_handler.py │   │   ├── register_action.py │   │   ├── reply_dispatcher.py │   │   ├── sanhuatongyu │   │   │   ├── config.py │   │   │   ├── context.py │   │   │   ├── emergency_cli.py │   │   │   ├── entry_dispatcher.py │   │   │   ├── events │   │   │   ├── events.py │   │   │   ├── __init__.py │   │   │   ├── logger.py │   │   │   ├── master.py │   │   │   ├── metrics.py │   │   │   ├── module │   │   │   ├── monitoring │   │   │   ├── __pycache__ │   │   │   ├── run_sanhuatongyu.py │   │   │   ├── security │   │   │   ├── system_module.py │   │   │   ├── tests │   │   │   └── utils.py │   │   ├── security_manager.py │   │   ├── self_heal │   │   │   ├── __init__.py │   │   │   ├── log_analyzer.py │   │   │   ├── rollback_manager.py │   │   │   └── self_healing_scheduler.py │   │   ├── trace_logger.py │   │   └── utils.py │   ├── __init__.py │   ├── __pycache__ │   │   └── __init__.cpython-313.pyc │   └── system │   ├── __init__.py │   ├── __pycache__ │   │   ├── __init__.cpython-313.pyc │   │   ├── system_control.cpython-313.pyc │   │   └── system_sense.cpython-313.pyc │   ├── system_control.py │   └── system_sense.py ├── create_memory_module.py ├── data │   ├── __init__.py │   ├── logbook.jsonl │   ├── memory_data.json │   └── memory_log.txt ├── default_config.yaml ├── dependencies │   ├── audio_env │   │   ├── bin │   │   │   ├── activate │   │   │   ├── activate.csh │   │   │   ├── activate.fish │   │   │   ├── Activate.ps1 │   │   │   ├── autopep8 │   │   │   ├── bandit │   │   │   ├── bandit-baseline │   │   │   ├── bandit-config-generator │   │   │   ├── distro │   │   │   ├── edge-playback │   │   │   ├── edge-tts │   │   │   ├── f2py │   │   │   ├── find-corrupt-whisper-files.py │   │   │   ├── flake8 │   │   │   ├── huggingface-cli │   │   │   ├── __init__.py │   │   │   ├── isympy │   │   │   ├── jsonschema │   │   │   ├── markdown-it │   │   │   ├── normalizer │   │   │   ├── numpy-config │   │   │   ├── pbr │   │   │   ├── pip │   │   │   ├── pip3 │   │   │   ├── pip3.13 │   │   │   ├── proton │   │   │   ├── proton-viewer │   │   │   ├── pycodestyle │   │   │   ├── pyflakes │   │   │   ├── pygmentize │   │   │   ├── pylupdate6 │   │   │   ├── python -> /usr/bin/python │   │   │   ├── python3 -> python │   │   │   ├── python3.13 -> python │   │   │   ├── pyuic6 │   │   │   ├── radon │   │   │   ├── rrd2whisper.py │   │   │   ├── srt │   │   │   ├── srt-deduplicate │   │   │   ├── srt-fixed-timeshift │   │   │   ├── srt-linear-timeshift │   │   │   ├── srt-lines-matching │   │   │   ├── srt-mux │   │   │   ├── srt-normalise │   │   │   ├── srt-play │   │   │   ├── srt-process │   │   │   ├── tabulate │   │   │   ├── tiny-agents │   │   │   ├── torchfrtrace │   │   │   ├── torchrun │   │   │   ├── tqdm │   │   │   ├── transformers │   │   │   ├── transformers-cli │   │   │   ├── update-storage-times.py │   │   │   ├── watchmedo │   │   │   ├── whisper-auto-resize.py │   │   │   ├── whisper-auto-update.py │   │   │   ├── whisper-create.py │   │   │   ├── whisper-diff.py │   │   │   ├── whisper-dump.py │   │   │   ├── whisper-fetch.py │   │   │   ├── whisper-fill.py │   │   │   ├── whisper-info.py │   │   │   ├── whisper-merge.py │   │   │   ├── whisper-resize.py │   │   │   ├── whisper-set-aggregation-method.py │   │   │   ├── whisper-set-xfilesfactor.py │   │   │   └── whisper-update.py │   │   ├── include │   │   │   ├── __init__.py │   │   │   └── python3.13 │   │   ├── __init__.py │   │   ├── lib │   │   │   ├── __init__.py │   │   │   └── python3.13 │   │   ├── lib64 -> lib │   │   ├── pyvenv.cfg │   │   └── share │   │   ├── __init__.py │   │   └── man │   ├── __init__.py │   └── requirements.txt ├── dingtalk_install.sh ├── docs │   └── __init__.py ├── entry │   ├── add_entry_func.py │   ├── cli_entry │   │   ├── cli_entry.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── module.py │   │   └── __pycache__ │   │   ├── cli_entry.cpython-313.pyc │   │   └── __init__.cpython-313.pyc │   ├── gui_entry │   │   ├── gui_main.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   └── module.py │   ├── __init__.py │   ├── __pycache__ │   │   └── __init__.cpython-313.pyc │   ├── register_and_run_entries.py │   ├── voice_entry │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── module.py │   │   └── voice_entry.py │   └── voice_input │   ├── 2 │   │   └── __init__.py │   ├── __init__.py │   ├── manifest.json │   ├── manifest.json.bak │   ├── module.py │   └── voice_input.py ├── external │   ├── cpp_example │   │   └── __init__.py │   ├── go_example │   │   └── __init__.py │   ├── __init__.py │   └── rust_example │   └── __init__.py ├── fix_ai_config.py ├── fix_all_issues.py ├── fix_and_run.sh ├── fix_cli_entry_path_and_dispatcher.py ├── fix_entry_import_paths.py ├── fix_errors.sh ├── fix_imports.py ├── fix_log_calls.py ├── fix_logger_calls.py ├── fix_logger_extra_parens.py ├── fix_logger_extra.py ├── fix_log_path.py ├── fix_package_exports.sh ├── fix_percent_format.py ├── fix_relative_imports.py ├── fix_systemmonitor.py ├── gui │   ├── aicore_gui.py │   ├── __init__.py │   ├── main_gui.py │   ├── main_gui.py.bak │   ├── utils │   │   ├── __init__.py │   │   ├── system_monitor.py │   │   ├── typing_effect.py │   │   └── voice_queue.py │   └── widgets │   ├── background_manager.py │   ├── chat_box.py │   ├── __init__.py │   ├── input_bar.py │   ├── menu_bar.py │   ├── status_panel.py │   └── voice_mode_overlay.py ├── health_checker.py ├── health_checker.py.bak ├── health_report.md ├── init_packages.py ├── init_project.sh ├── __init__.py ├── install_missing_dependencies.py ├── jindouyun_particles.py ├── juhe-main.py ├── juhe-main.py.bak ├── juhe_system.log ├── license ├── logbook.jsonl ├── logs │   ├── audit_20250719.log │   ├── audit_20250720.log │   ├── audit_20250721.log │   ├── cli_20250719_183927.log │   ├── cli_20250719_191337.log │   ├── cli_20250719.log │   ├── cli_20250720.log │   ├── cli_20250721.log │   ├── code_inserter │   │   ├── code_inserter.log │   │   └── __init__.py │   ├── code_reader │   │   ├── code_reader.log │   │   └── __init__.py │   ├── code_reviewer.log │   ├── format_manager │   │   └── format_manager.log │   ├── __init__.py │   ├── logbook │   │   ├── audit.log │   │   └── logbook.jsonl │   └── voice_input │   ├── __init__.py │   └── voice_input.log ├── main_controller.py ├── memory_data.json ├── memory.pkl ├── memory_retrieval.py ├── migrate_log.txt ├── migrate_structure.py ├── models.txt ├── module_loader.py ├── modules │   ├── audio_capture │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── capture.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   └── module.py │   ├── audio_consumer │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── audio_consumer.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   └── module.py │   ├── cli_entry │   │   ├── cli_main.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   └── module.py │   ├── code_executor │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── code_executor.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── __pycache__ │   │   ├── code_executor.cpython-313.pyc │   │   └── __init__.cpython-313.pyc │   ├── code_inserter │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── code_inserter.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── __pycache__ │   │   ├── code_inserter.cpython-313.pyc │   │   └── __init__.cpython-313.pyc │   ├── code_reader │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── code_reader.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── __pycache__ │   │   ├── code_reader.cpython-313.pyc │   │   └── __init__.cpython-313.pyc │   ├── code_reviewer │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── code_reviewer.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── __pycache__ │   │   ├── code_reviewer.cpython-313.pyc │   │   └── __init__.cpython-313.pyc │   ├── format_manager │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── format_manager.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── __pycache__ │   │   ├── format_manager.cpython-313.pyc │   │   └── __init__.cpython-313.pyc │   ├── gui_entry │   │   ├── gui_main.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   └── module.py │   ├── hello_module │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── hello_module.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   └── module.py │   ├── __init__.py │   ├── ju_wu │   │   ├── action_mapper.py │   │   ├── __init__.py │   │   ├── intent_router.py │   │   ├── juwu.py │   │   ├── manifest.json │   │   ├── rules │   │   │   ├── __init__.py │   │   │   ├── intent_rules.json │   │   │   └── manifest.json │   │   ├── rule_trainer_gui.py │   │   ├── rule_trainer_interactive.py │   │   └── rule_trainer_widget.py │   ├── juzi │   │   ├── icons │   │   │   ├── file_000000007978620ab9d2f348a2d62a7f-9db60022-3243-4e66-acda-80f38ba5248b.png │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── juzi_engine.py │   │   ├── juzi.py │   │   ├── juzi.xml │   │   └── manifest.json │   ├── language_bridge │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── language_bridge.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   └── module.py │   ├── logbook │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── logbook_manager.py │   │   ├── logbook.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── __pycache__ │   │   ├── __init__.cpython-313.pyc │   │   └── logbook.cpython-313.pyc │   ├── model_engine │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── model_engine.py │   │   ├── module.py │   │   └── __pycache__ │   │   ├── __init__.cpython-313.pyc │   │   ├── model_engine.cpython-313.pyc │   │   └── module.cpython-313.pyc │   ├── music_manager │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   ├── music_manager.py │   │   └── __pycache__ │   │   ├── __init__.cpython-313.pyc │   │   ├── module.cpython-313.pyc │   │   └── music_manager.cpython-313.pyc │   ├── __pycache__ │   │   └── __init__.cpython-313.pyc │   ├── reply_dispatcher │   │   ├── dispatcher.py │   │   ├── __init__.py │   │   ├── manifest.json │   │   └── register_actions.py │   ├── self_learning_module │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── self_learning_module.py │   ├── smart_demo_module │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── smart_demo_module.py │   ├── speech_manager │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   ├── __pycache__ │   │   │   ├── __init__.cpython-313.pyc │   │   │   └── module.cpython-313.pyc │   │   └── speech_manager.py │   ├── stt_module │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── stt_module.py │   ├── system_control │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   ├── __pycache__ │   │   │   ├── __init__.cpython-313.pyc │   │   │   └── module.cpython-313.pyc │   │   └── system_control.py │   ├── system_monitor │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   ├── __pycache__ │   │   │   ├── __init__.cpython-313.pyc │   │   │   ├── module.cpython-313.pyc │   │   │   └── system_monitor.cpython-313.pyc │   │   └── system_monitor.py │   ├── test_module │   │   ├── 1 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── test_module.py │   ├── voice_entry │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── module.py │   │   └── voice_entry.py │   ├── voice_input │   │   ├── 2 │   │   │   ├── __init__.py │   │   │   └── manifest.json │   │   ├── __init__.py │   │   ├── manifest.json │   │   ├── manifest.json.bak │   │   ├── module.py │   │   └── voice_input.py │   └── wake_word_detector │   ├── 1 │   │   ├── __init__.py │   │   └── manifest.json │   ├── download_whisper_model.py │   ├── __init__.py │   ├── manifest.json │   ├── manifest.json.bak │   ├── module.py │   └── wake_word_detector.py ├── module_standardizer.py ├── mysterious_entry_tool.py ├── ollama_bin │   ├── __init__.py │   └── ollama ├── ollama_data │   ├── history │   ├── id_ed25519 │   ├── id_ed25519.pub │   └── __init__.py ├── ollama_models │   ├── blobs │   │   ├── __init__.py │   │   ├── sha256-3f8eb4da87fa7a3c9da615036b0dc418d31fef2a30b115ff33562588b32c691d │   │   ├── sha256-4fa551d4f938f68b8c1e6afa9d28befb70e3f33f75d0753248d530364aeea40f │   │   ├── sha256-577073ffcc6ce95b9981eacc77d1039568639e5638e83044994560d9ef82ce1b │   │   ├── sha256-6a0746a1ec1aef3e7ec53868f220ff6e389f6f8ef87a01d77c96807de94ca2aa │   │   └── sha256-8ab4849b038cf0abc5b1c9b8ee1443dca6b93a045c2272180d985126eb40bf6f │   ├── __init__.py │   └── manifests │   ├── __init__.py │   └── registry.ollama.ai │   ├── __init__.py │   └── library ├── README.md ├── recordings │   └── __init__.py ├── register_entries.sh ├── replace_imports.py ├── replace_logging.py ├── restructure_dirs.sh ├── rollback_snapshots │   ├── backup │   │   ├── backup_20250629_215816_94cd3d │   │   │   ├── code_executor.py │   │   │   ├── code_inserter.py │   │   │   ├── code_reader.py │   │   │   ├── code_reviewer.py │   │   │   ├── format_manager.py │   │   │   ├── hello_module.py │   │   │   ├── __init__.py │   │   │   ├── logbook.py │   │   │   ├── model_engine.py │   │   │   ├── music_manager.py │   │   │   ├── speech_manager.py │   │   │   ├── system_control.py │   │   │   └── system_monitor.py │   │   └── __init__.py │   ├── __init__.py │   ├── rollback_history.json │   ├── snapshot_20250629_215118_795f59e1 │   │   ├── code_executor.py │   │   ├── code_inserter.py │   │   ├── code_reader.py │   │   ├── code_reviewer.py │   │   ├── format_manager.py │   │   ├── hello_module.py │   │   ├── __init__.py │   │   ├── logbook.py │   │   ├── model_engine.py │   │   ├── music_manager.py │   │   ├── speech_manager.py │   │   ├── system_control.py │   │   └── system_monitor.py │   ├── snapshot_20250629_215407_65d62c0e │   │   ├── code_executor.py │   │   ├── code_inserter.py │   │   ├── code_reader.py │   │   ├── code_reviewer.py │   │   ├── format_manager.py │   │   ├── hello_module.py │   │   ├── __init__.py │   │   ├── logbook.py │   │   ├── model_engine.py │   │   ├── music_manager.py │   │   ├── speech_manager.py │   │   ├── system_control.py │   │   └── system_monitor.py │   └── snapshot_20250629_215816_c3d93551 │   ├── code_executor.py │   ├── code_inserter.py │   ├── code_reader.py │   ├── code_reviewer.py │   ├── format_manager.py │   ├── hello_module.py │   ├── __init__.py │   ├── logbook.py │   ├── model_engine.py │   ├── music_manager.py │   ├── speech_manager.py │   ├── system_control.py │   └── system_monitor.py ├── rule_trainer.py ├── run_all_entries.py ├── runtime │   ├── __init__.py │   ├── logs │   │   └── __init__.py │   ├── recordings │   │   └── __init__.py │   └── temp │   └── __init__.py ├── sanhua_self_healer.py ├── scaffold │   ├── fix_all_imports.py │   ├── health_checker.py │   ├── __init__.py │   └── standardize_modules.py ├── scan_old_imports.py ├── start_ollama.sh ├── startup_fix.py ├── system.log ├── test_mic.py ├── tests │   ├── __init__.py │   ├── test_aicore.py │   ├── test_entry_dispatcher.py │   └── test_modules_loading.py ├── test.wav ├── third_party │   ├── __init__.py │   └── ollama │   └── __init__.py ├── tools │   ├── cert_fix_tool.py │   ├── config_validator.py │   ├── import_checker.py │   └── path_dependency_checker.py ├── trace_memory_summary_calls.py ├── update_entry_points.py ├── utils │   └── __init__.py └── venv ├── bin │   ├── activate │   ├── activate.csh │   ├── activate.fish │   ├── Activate.ps1 │   ├── bandit │   ├── bandit-baseline │   ├── bandit-config-generator │   ├── flake8 │   ├── futurize │   ├── markdown-it │   ├── pasteurize │   ├── pbr │   ├── pip │   ├── pip3 │   ├── pip3.11 │   ├── pycodestyle │   ├── pyflakes │   ├── pygmentize │   ├── python -> python3.11 │   ├── python3 -> python3.11 │   ├── python3.11 -> /usr/bin/python3.11 │   ├── radon │   └── watchmedo ├── include │   └── python3.11 ├── lib │   └── python3.11 │   └── site-packages ├── lib64 -> lib ├── pyvenv.cfg └── share └── man └── man1 159 directories, 624 files lufei@fedora:~/文档/聚核助手2.0$
07-22
. ├── cliff_distance_measurement │ ├── CMakeLists.txt │ ├── include │ │ └── cliff_distance_measurement │ ├── package.xml │ └── src │ ├── core │ ├── ir_ranging.cpp │ └── platform ├── robot_cartographer │ ├── config │ │ └── fishbot_2d.lua │ ├── map │ │ ├── fishbot_map.pgm │ │ └── fishbot_map.yaml │ ├── package.xml │ ├── readme.md │ ├── resource │ │ └── robot_cartographer │ ├── robot_cartographer │ │ ├── __init__.py │ │ └── robot_cartographer.py │ ├── rviz │ ├── setup.cfg │ └── setup.py ├── robot_control_service │ ├── bash │ │ └── pwm_control_setup.sh │ ├── CMakeLists.txt │ ├── config │ │ └── control_params.yaml │ ├── include │ │ └── robot_control_service │ ├── package.xml │ ├── readme.md │ └── src │ ├── control_client_camera.cpp │ ├── control_client_cliff.cpp │ ├── control_client_ir.cpp │ ├── control_client_ir_four.cpp │ ├── control_client_master.cpp │ ├── control_client_ros.cpp │ ├── control_client_ultrasonic.cpp │ ├── control_service.cpp │ ├── DirectMotorControl.cpp │ ├── PIDControl.cpp │ ├── publisher_control_view.cpp │ └── publisher_human_realized.cpp ├── robot_control_view │ ├── config │ │ └── icare_robot.rviz │ ├── __init__.py │ ├── launch │ │ └── start_init_view.launch.py │ ├── package.xml │ ├── resource │ │ └── robot_control_view │ ├── robot_control_view │ │ ├── app │ │ ├── blood_oxygen_pulse │ │ ├── __init__.py │ │ ├── __pycache__ │ │ ├── robot_automatic_cruise_server.py │ │ ├── robot_automatic_recharge_server.py │ │ ├── robot_automatic_slam_server.py │ │ ├── robot_blood_oxygen_pulse.py │ │ ├── robot_city_locator_node.py │ │ ├── robot_control_policy_server.py │ │ ├── robot_local_websocket.py │ │ ├── robot_log_clear_node.py │ │ ├── robot_main_back_server.py │ │ ├── robot_network_publisher.py │ │ ├── robot_network_server.py │ │ ├── robot_odom_publisher.py │ │ ├── robot_speech_server.py │ │ ├── robot_system_info_node.py │ │ ├── robot_ultrasonic_policy_node.py │ │ ├── robot_view_manager_node.py │ │ ├── robot_websockets_client.py │ │ ├── robot_websockets_server.py │ │ ├── robot_wifi_server_node.py │ │ ├── start_account_view.py │ │ ├── start_bluetooth_view.py │ │ ├── start_chat_view.py │ │ ├── start_clock_view.py │ │ ├── start_feedback_view.py │ │ ├── start_health_view.py │ │ ├── start_init_view.py │ │ ├── start_lifecycle_view.py │ │ ├── start_main_view.py │ │ ├── start_member_view.py │ │ ├── start_movie_view.py │ │ ├── start_music_view.py │ │ ├── start_radio_view.py │ │ ├── start_schedule_view.py │ │ ├── start_setting_view.py │ │ ├── start_test_view.py │ │ ├── start_view_manager.py │ │ ├── start_weather_view.py │ │ └── start_wifi_view.py │ ├── setup.cfg │ ├── setup.py │ ├── test │ │ ├── my_test.py │ │ ├── test_copyright.py │ │ ├── test_flake8.py │ │ └── test_pep257.py │ └── urdf │ ├── first_robot.urdf.xacro │ ├── fishbot.urdf │ ├── fishbot.urdf.xacro │ ├── fist_robot.urdf │ ├── icare_robot.urdf │ ├── icare_robot.urdf.xacro │ ├── ramand.md │ └── xacro_template.xacro ├── robot_costmap_filters │ ├── CMakeLists.txt │ ├── include │ │ └── robot_costmap_filters │ ├── launch │ │ ├── start_costmap_filter_info_keepout.launch.py │ │ ├── start_costmap_filter_info.launch.py │ │ └── start_costmap_filter_info_speedlimit.launch.py │ ├── package.xml │ ├── params │ │ ├── filter_info.yaml │ │ ├── filter_masks.yaml │ │ ├── keepout_mask.pgm │ │ ├── keepout_mask.yaml │ │ ├── keepout_params.yaml │ │ ├── speedlimit_params.yaml │ │ ├── speed_mask.pgm │ │ └── speed_mask.yaml │ ├── readme.md │ └── src ├── robot_description │ ├── launch │ │ └── gazebo.launch.py │ ├── package.xml │ ├── readme.md │ ├── resource │ │ └── robot_description │ ├── robot_description │ │ └── __init__.py │ ├── rviz │ │ └── urdf_config.rviz │ ├── setup.cfg │ ├── setup.py │ ├── urdf │ │ ├── fishbot_gazebo.urdf │ │ ├── fishbot_v0.0.urdf │ │ ├── fishbot_v1.0.0.urdf │ │ ├── test.urdf │ │ └── three_wheeled_car_model.urdf │ └── worlds │ └── empty_world.world ├── robot_interfaces │ ├── CMakeLists.txt │ ├── include │ │ └── robot_interfaces │ ├── msg │ │ ├── AlarmClockMsg.msg │ │ ├── CameraMark.msg │ │ ├── DualRange.msg │ │ ├── HuoerSpeed.msg │ │ ├── IrSensorArray.msg │ │ ├── IrSignal.msg │ │ ├── NavigatorResult.msg │ │ ├── NavigatorStatus.msg │ │ ├── NetworkDataMsg.msg │ │ ├── PoseData.msg │ │ ├── RobotSpeed.msg │ │ ├── SensorStatus.msg │ │ ├── TodayWeather.msg │ │ └── WifiDataMsg.msg │ ├── package.xml │ ├── readme.md │ ├── src │ └── srv │ ├── LightingControl.srv │ ├── MotorControl.srv │ ├── NewMotorControl.srv │ ├── SetGoal.srv │ ├── StringPair.srv │ ├── String.srv │ └── VoicePlayer.srv ├── robot_launch │ ├── config │ │ └── odom_imu_ekf.yaml │ ├── launch │ │ ├── start_all_base_sensor.launch.py │ │ ├── start_cartographer.launch.py │ │ ├── start_control_service.launch.py │ │ ├── start_navigation.launch.py │ │ ├── start_navigation_service.launch.py │ │ ├── start_navigation_speed_mask.launch.py │ │ ├── start_navigation_with_speed_and_keepout.launch.py │ │ ├── start_ros2.launch.py │ │ ├── test_camera_2.launch.py │ │ ├── test_camera.launch.py │ │ ├── test_car_model.launch.py │ │ ├── test_cliff.launch.py │ │ ├── test_ir.launch.py │ │ ├── test_self_checking.launch.py │ │ ├── test_video_multiplesing.launch.py │ │ └── test_visualization.launch.py │ ├── package.xml │ ├── readme.md │ ├── resource │ │ └── robot_launch │ ├── robot_launch │ │ └── __init__.py │ ├── setup.cfg │ └── setup.py ├── robot_navigation │ ├── config │ │ ├── nav2_filter.yaml │ │ ├── nav2_params.yaml │ │ └── nav2_speed_filter.yaml │ ├── maps │ │ ├── fishbot_map.pgm │ │ └── fishbot_map.yaml │ ├── package.xml │ ├── readme.md │ ├── resource │ │ └── robot_navigation │ ├── robot_navigation │ │ ├── __init__.py │ │ └── robot_navigation.py │ ├── setup.cfg │ └── setup.py ├── robot_navigation2_service │ ├── package.xml │ ├── readme.md │ ├── resource │ │ └── robot_navigation2_service │ ├── robot_navigation2_service │ │ ├── camera_follower_client.py │ │ ├── go_to_pose_service.py │ │ ├── __init__.py │ │ ├── leave_no_parking_zone_client_test_2.py │ │ ├── pose_init.py │ │ ├── real_time_point_client.py │ │ ├── recharge_point_client.py │ │ ├── repub_speed_filter_mask.py │ │ └── save_pose.py │ ├── setup.cfg │ └── setup.py ├── robot_sensor │ ├── bash │ │ └── isr_brushless.sh │ ├── CMakeLists.txt │ ├── config │ │ └── sensor_params.yaml │ ├── include │ │ └── robot_sensor │ ├── package.xml │ ├── readme.md │ └── src │ ├── robot_battery_state_publisher.cpp │ ├── robot_battery_voltage_publisher.cpp │ ├── robot_charging_status_publisher.cpp │ ├── robot_cliff_distance_publisher.cpp │ ├── robot_encode_speed_publisher.cpp │ ├── robot_imu_publisher.cpp │ ├── robot_ir_four_signal_publisher.cpp │ ├── robot_ir_signal_publisher.cpp │ ├── robot_keyboard_control_publisher.cpp │ ├── robot_lighting_control_server.cpp │ ├── robot_map_publisher.cpp │ ├── robot_odom_publisher.cpp │ ├── robot_smoke_alarm_publisher.cpp │ ├── robot_ultrasonic_publisher.cpp │ └── robot_wireless_alarm_publisher.cpp ├── robot_sensor_self_check │ ├── check_report │ │ ├── sensor_diagnostic_report_20250226_144435.json │ │ ├── sensor_diagnostic_report_20250226_144435.txt │ │ ├── sensor_diagnostic_report_20250226_144850.json │ │ ├── sensor_diagnostic_report_20250226_144850.txt │ │ ├── sensor_diagnostic_report_20250226_144927.json │ │ ├── sensor_diagnostic_report_20250226_144927.txt │ │ ├── sensor_diagnostic_report_20250226_144958.json │ │ └── sensor_diagnostic_report_20250226_144958.txt │ ├── config │ │ └── sensors_config.yaml │ ├── package.xml │ ├── resource │ │ └── robot_sensor_self_check │ ├── robot_sensor_self_check │ │ ├── __init__.py │ │ ├── robot_sensor_self_check.py │ │ └── test_topic.py │ ├── setup.cfg │ ├── setup.py │ └── test │ ├── test_copyright.py │ ├── test_flake8.py │ └── test_pep257.py ├── robot_visual_identity │ ├── cfg │ │ ├── nanotrack.yaml │ │ ├── rknnconfig.yaml │ │ └── stgcnpose.yaml │ ├── face_feature │ │ ├── mss_face_encoding.npy │ │ ├── wd_face_encoding.npy │ │ └── yls_face_encoding.npy │ ├── package.xml │ ├── resource │ │ ├── robot_visual_identity │ │ └── ros_rknn_infer │ ├── rknn_model │ │ ├── blood_detect.rknn │ │ ├── blood-seg-last-cbam.rknn │ │ ├── face_detect.rknn │ │ ├── face_emotion.rknn │ │ ├── face_keypoint.rknn │ │ ├── face_verify.rknn │ │ ├── head_detect.rknn │ │ ├── nanotrack_backbone127.rknn │ │ ├── nanotrack_backbone255.rknn │ │ ├── nanotrack_head.rknn │ │ ├── people_detect.rknn │ │ ├── stgcn_pose.rknn │ │ ├── yolo_kpt.rknn │ │ └── yolov8s-pose.rknn │ ├── robot_visual_identity │ │ ├── 人体跟随与避障控制系统文档.md │ │ ├── __init__.py │ │ ├── rknn_infer │ │ ├── robot_behavior_recognition.py │ │ ├── robot_emotion_recognition.py │ │ ├── robot_people_rgb_follow.py │ │ ├── robot_people_scan_follow.py │ │ └── robot_people_track.py │ ├── setup.cfg │ ├── setup.py │ └── test │ ├── test_copyright.py │ ├── test_flake8.py │ └── test_pep257.py ├── video_multiplexing │ ├── bash │ │ ├── test_config.linphonerc │ │ ├── test_video_stream.sh │ │ └── video_stream.pcap │ ├── COLCON_IGNORE │ ├── package.xml │ ├── resource │ │ └── video_multiplexing │ ├── setup.cfg │ ├── setup.py │ ├── test │ │ ├── test_copyright.py │ │ ├── test_flake8.py │ │ └── test_pep257.py │ └── video_multiplexing │ ├── __init__.py │ ├── __pycache__ │ ├── rtp_utils.py │ ├── video_freeswitch.py │ ├── video_linphone_bridge.py │ ├── video_publisher.py │ └── video_test_freeswitch.py └── ydlidar_ros2_driver-humble ├── CMakeLists.txt ├── config │ └── ydlidar.rviz ├── details.md ├── images │ ├── cmake_error.png │ ├── EAI.png │ ├── finished.png │ ├── rviz.png │ ├── view.png │ └── YDLidar.jpg ├── launch │ ├── ydlidar_launch.py │ ├── ydlidar_launch_view.py │ └── ydlidar.py ├── LICENSE.txt ├── package.xml ├── params │ └── TminiPro.yaml ├── README.md ├── src │ ├── ydlidar_ros2_driver_client.cpp │ └── ydlidar_ros2_driver_node.cpp └── startup └── initenv.sh 93 directories, 299 files 我的机器人ros2系统是有显示和主控页面的居家服务型移动机器人,用户点击下载更新就开始执行更新流程,整个系统更新功能应该怎么设计,在开发者应该编写哪些代码和做哪些准备,如何设计流程
07-21
import sys from PyQt5.QtWidgets import QScrollArea, QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QLineEdit, QLabel, QFileDialog, QRadioButton, QComboBox, QCheckBox, QGroupBox, QListWidget, QProgressBar from PyQt5.QtCore import QUrl, QRegExp, QTimer, Qt from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from PyQt5.QtGui import QIcon, QIntValidator, QRegExpValidator from PyQt5.QtWebChannel import QWebChannel # 导入 QWebChannel import configparser import os import subprocess from PyQt5.QtWidgets import QMessageBox import json import time import docker import ctypes import logging import tkinter as tk from tkinter import messagebox import threading import psutil import shutil import 文件服务2 from 服务校验 import validate_service # 外部函数 sys.path.append(os.path.dirname(__file__)) # 添加当前文件的目录到路径 # import 连接数据库添加历史倾斜摄影 # 直接 if getattr(sys, 'frozen', False): # 如果是打包后的exe,使用sys.executable base_dir = os.path.dirname(sys.executable) else: # 如果是普通脚本,使用__file__ base_dir = os.path.dirname(os.path.abspath(__file__)) # 将'\'替换为'/' base_dir = base_dir.replace('\\', '/') # 将第一个字母盘符写为大写 base_dir = base_dir[0].upper() + base_dir[1:] print('文件路径',base_dir) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.task_started = True # 标志任务是否开始 self.current_img_index = 0 # 当前处理的图片索引 self.setWindowTitle("实时三维") self.setGeometry(100, 100, 1366, 768) self.setWindowIcon(QIcon('./icon.ico')) self.process = None # 用于存储子进程的引用 check_docker_running() # 检查docker是否运行 self.node_process = None # 用于存储 node 进程的引用 self.start_node_process() # 启动 node 进程 # 初始化变量存储路径和选择值 self.paths = { "project": base_dir, "sensor": "", "1": "", "texture": os.path.join(base_dir, "ModelingScope.kml"), "localtions":'' } self.command_line = "" self.data_source = "影像" self.current_progress_key = None # 主窗口布局 main_layout = QHBoxLayout() # 左侧控件布局 left_layout = QVBoxLayout() # 示例控件 self.start_button = QPushButton("开始任务") # 开始任务按钮点击事件绑定run_command方法 self.start_button.clicked.connect(lambda:self.run_command(self.command_line)) left_layout.addWidget(self.start_button) # 只准点击一次开始任务按钮,点击后状态显示停止任务 # stop_button = QPushButton("停止任务") # stop_button.clicked.connect(lambda:self.stopTask()) # left_layout.addWidget(stop_button) # left_layout.addWidget(QLabel("空闲")) # 项目工程路径 # self.createPathInput(left_layout, "项目工程路径:", "project") stop_button2 = QPushButton("文件传输") stop_button2.clicked.connect(lambda:self.file_transfer()) left_layout.addWidget(stop_button2) # 平台上传 stop_button3 = QPushButton("上传平台") stop_button3.clicked.connect(lambda:self.file_transfer_to_platform()) left_layout.addWidget(stop_button3) # 任务队列路径 self.createPathInput(left_layout, "任务队列路径:", "sensor") # 图片文件路径 self.createPathInput(left_layout, "图片文件路径:", "localtions") # # 相机文件路径 # self.createPathInput_file(left_layout, "相机文件路径(.json):", "1", "json") left_layout.addWidget(QLabel("数据源:")) radiobuttons_layout = QHBoxLayout() radio_image = QRadioButton("影像") # radio_video = QRadioButton("视频") radio_image.setChecked(True) radio_image.toggled.connect(lambda: self.setDataSource("影像", radio_image.isChecked())) # radio_video.toggled.connect(lambda: self.setDataSource("视频", radio_video.isChecked())) radiobuttons_layout.addWidget(radio_image) # radiobuttons_layout.addWidget(radio_video) left_layout.addLayout(radiobuttons_layout) # 建模范围 # self.createPathInput_file(left_layout, "建模范围(.kml):", "texture", "kml") # 经纬度输入部分 # left_layout.addWidget(QLabel("输入建模范围:")) # self.coordinates_layout = QVBoxLayout() # # 添加一个默认的坐标输入 # for _ in range(4): # self.add_coordinate_input() # add_button = QPushButton("+") # add_button.clicked.connect(self.add_coordinate_input) # self.coordinates_layout.addWidget(add_button) # left_layout.addLayout(self.coordinates_layout) # 添加经纬度输入框 # 初始化经纬度输入框布局 self.coordinates_layout = QVBoxLayout() self.input_widgets = [] # 添加四组默认的输入框 for _ in range(4): self.add_coordinate_input() # 添加和删除按钮 button_layout = QHBoxLayout() add_button = QPushButton("+") remove_button = QPushButton("-") add_button.clicked.connect(self.add_coordinate_input) remove_button.clicked.connect(self.remove_coordinate_input) button_layout.addWidget(add_button) button_layout.addWidget(remove_button) # 滚动区域 scroll_area = QScrollArea() scroll_content = QWidget() scroll_content.setLayout(self.coordinates_layout) scroll_area.setWidget(scroll_content) scroll_area.setWidgetResizable(True) scroll_area.setFixedHeight(180) left_layout.addWidget(QLabel("输入建模范围:")) left_layout.addWidget(scroll_area) left_layout.addLayout(button_layout) # 相对航高(米) # left_layout.addWidget(QLabel("相对航高(米):")) # self.elevation_input = QLineEdit("") # left_layout.addWidget(self.elevation_input) # 地面分辨率 # left_layout.addWidget(QLabel("像元大小(um):")) # self.minphotonSize = QLineEdit("") # left_layout.addWidget(self.minphotonSize) # 建模结果文件名 left_layout.addWidget(QLabel("建模结果文件名:")) self.result_file_name = QLineEdit("") left_layout.addWidget(self.result_file_name) self.result_file_name.textChanged.connect(self.update_json) # 建模精度 left_layout.addWidget(QLabel("建模精度:")) self.precision_combo = QComboBox() self.precision_combo.addItem("快速") self.precision_combo.addItem("普通") self.precision_combo.addItem("精细") left_layout.addWidget(self.precision_combo) # 高级设置 advanced_group = QGroupBox("高级设置") advanced_layout = QVBoxLayout() advanced_layout.addWidget(QLabel("图片集大小(张):")) self.tile_size_input = QLineEdit("20") self.tile_size_input.setValidator(QIntValidator()) # 限制输入为整数 advanced_layout.addWidget(self.tile_size_input) advanced_layout.addWidget(QLabel("照片获取等待时间(秒):")) self.interval_input = QLineEdit("10") self.interval_input.setValidator(QIntValidator()) # 限制输入为整数 advanced_layout.addWidget(self.interval_input) advanced_layout.addWidget(QLabel("边飞边建:")) self.checkbox1 = QRadioButton("正射") self.checkbox2 = QRadioButton("倾斜") self.checkbox3 = QRadioButton("全建") self.checkbox1.setChecked(False) self.checkbox2.setChecked(True) self.checkbox3.setChecked(False) # advanced_layout.addWidget(checkbox1) # advanced_layout.addWidget(checkbox2) advanced_layout.addWidget(self.checkbox1) advanced_layout.addWidget(self.checkbox2) advanced_layout.addWidget(self.checkbox3) # 按钮 # Merge_models = QPushButton("合并模型") # connection = 连接数据库添加历史倾斜摄影.create_connection("localhost", "root", "ysxx_0407H123", "product-backsage2") # Merge_models.clicked.connect(lambda:连接数据库添加历史倾斜摄影.query_users(connection)) # advanced_layout.addWidget(Merge_models) advanced_group.setLayout(advanced_layout) left_layout.addWidget(advanced_group) # 实时状态显示 self.status_group = QGroupBox("实时状态") status_layout = QVBoxLayout() self.file_count_label = QLabel("文件数量: 0") self.file_names_list = QListWidget() self.docker_count_label = QLabel("Docker容器数量: 0") # 添加进度条和文字标签 self.progress_stage_label = QLabel("进度阶段: 第一块进度") self.progress_bar = QProgressBar() self.progress_bar.setAlignment(Qt.AlignCenter) self.progress_bar.setRange(0, 100) self.progress_bar.setValue(0) status_layout.addWidget(self.file_count_label) status_layout.addWidget(self.file_names_list) status_layout.addWidget(self.docker_count_label) status_layout.addWidget(self.progress_stage_label) status_layout.addWidget(self.progress_bar) self.status_group.setLayout(status_layout) left_layout.addWidget(self.status_group) # 设置左侧宽度 left_widget = QWidget() left_widget.setLayout(left_layout) left_widget.setFixedWidth(300) # 创建Web引擎视图 self.web_view = QWebEngineView() self.web_view.load(QUrl("http://localhost:3123")) web_channel = QWebChannel(self.web_view.page()) # 创建 WebChannel 实例 self.web_view.page().setWebChannel(web_channel) # 关联 WebChannel self.web_view.page().runJavaScript("setInterval(function(){location.reload()}, 1000);") # 将左侧控件和Web视图添加到主布局 main_layout.addWidget(left_widget) main_layout.addWidget(self.web_view) # 设置主widget central_widget = QWidget() central_widget.setLayout(main_layout) self.setCentralWidget(central_widget) # 定时更新状态 self.timer = QTimer() self.timer.timeout.connect(self.update_status) self.timer.start(1000) # 每秒更新一次状态 # 启动进度条更新循环 self.update_progress_timer = QTimer() self.update_progress_timer.timeout.connect(self.update_progress_bar) self.update_progress_timer.start(1000) # 每秒检查一次进度 def update_status(self): # 更新文件数量和文件名 folder_path = self.paths["sensor"] if os.path.exists(folder_path): files = os.listdir(folder_path) self.file_count_label.setText(f"文件数量: {len(files)}") self.file_names_list.clear() for file_name in files: self.file_names_list.addItem(file_name) else: self.file_count_label.setText("文件数量: 0") self.file_names_list.clear() # 更新Docker容器数量 try: result = subprocess.run( ["docker", "ps", "--filter", "ancestor=opendronemap/odm:gpu", "--format", "{{.ID}}"], capture_output=True, text=True, shell=False, creationflags=subprocess.CREATE_NO_WINDOW # 确保不弹出窗口 ) container_ids = result.stdout.strip().split('\n') if container_ids and container_ids[0] == '': container_ids = [] # 如果结果为空字符串,将其设为空列表 logging.debug(f"Container IDs: {container_ids}") self.docker_count_label.setText(f"Docker容器数量: {len(container_ids)}") except subprocess.CalledProcessError as e: logging.error(f"获取Docker容器信息时出错: {e}") self.docker_count_label.setText("Docker容器数量: 0") except FileNotFoundError as e: logging.error(f"Docker命令未找到: {e}") self.docker_count_label.setText("Docker容器数量: 0") def add_coordinate_input(self): """添加一个新的经纬度输入行""" layout = QHBoxLayout() lng_input = QLineEdit() lat_input = QLineEdit() lng_input.setPlaceholderText("经度") lat_input.setPlaceholderText("纬度") # 使用正则表达式限制输入为正负加小数 validator = QRegExpValidator(QRegExp(r"^-?\d+(\.\d+)?$")) lng_input.setValidator(validator) lat_input.setValidator(validator) layout.addWidget(lng_input) layout.addWidget(lat_input) self.coordinates_layout.addLayout(layout) self.input_widgets.append((lng_input, lat_input)) # 收集经纬度坐标 coordinates = [] for lng_input, lat_input in self.input_widgets: lng = lng_input.text() lat = lat_input.text() print(lng, lat) if lng and lat: coordinates.append(f"{lng},{lat}") # 将所有经纬度坐标合并为一个字符串,用分号分隔 coordinates_str = ";".join(coordinates) print("经纬度",coordinates_str) # 将经纬度加入kml文件 def generate_kml(self): """生成KML内容""" kml_content = f"""<?xml version="1.0" encoding="utf-8"?> <kml xmlns="http://www.opengis.net/kml/2.2"> <Document> <Schema id="Dataset_203sup" name="Dataset_203sup"> <SimpleField name="SmUserID" type="int"/> </Schema> <Folder> <Placemark> <name/> <ExtendedData> <SchemaData schemaUrl="#Dataset_203sup"> <SimpleData name="SmUserID">0</SimpleData> </SchemaData> </ExtendedData> <Polygon> <outerBoundaryIs> <LinearRing> <coordinates> """ coordinates = [] for lng_input, lat_input in self.input_widgets: lng = lng_input.text() lat = lat_input.text() if lng and lat: coordinates.append(f"{lng},{lat}") # 在最后再添加第一个坐标点 if coordinates: coordinates.append(coordinates[0]) kml_content += "\n".join(coordinates) + "\n </coordinates>\n </LinearRing>\n </outerBoundaryIs>\n </Polygon>\n </Placemark>\n </Folder>\n </Document>\n</kml>" # 写入KML文件 kml_path = self.paths["texture"] with open(kml_path, 'w', encoding='utf-8') as kml_file: kml_file.write(kml_content) print("KML文件已写入:", kml_path) def remove_coordinate_input(self): """删除最下面的一个经纬度输入行""" if len(self.input_widgets) > 4: layout = self.coordinates_layout.takeAt(len(self.input_widgets) - 1) for i in range(layout.count()): widget = layout.itemAt(i).widget() if widget: widget.deleteLater() self.input_widgets.pop() else: QMessageBox.warning(self, "提示", "至少需要保留四组坐标。") def start_node_process(self): """启动 node 进程并保存其引用""" self.node_process = subprocess.Popen('node result_server_logs.js', shell=False, creationflags=subprocess.CREATE_NO_WINDOW) def update_json(self): # 获取建模结果文件名 result_filename = self.result_file_name.text() # 创建要写入的 JSON 数据 data = { "name": result_filename } # 写入 JSON 文件 with open('./result/resultName.json', 'w', encoding='utf-8') as json_file: json.dump(data, json_file, ensure_ascii=False, indent=4) def create_config_file(self,): """生成配置文件 config.ini""" config = configparser.ConfigParser() # 裁剪相机文件目录,若裁剪失败,弹窗提示用户重新选择文件 if self.paths["1"] is not None and self.paths["project"] is not None: project_path = self.paths["project"] + "/" if project_path in self.paths["1"]: self.paths["1"] = self.paths["1"].replace(project_path, "") # else: # QMessageBox.warning(self, "提示", "项目路径未包含在相机文件路径中。") else: # QMessageBox.warning(self, "提示", "路径不能为空。") print("路径不能为空。") self.paths["1"] = self.paths["1"].replace(self.paths["project"]+"/", "") config['settings'] = { 'kmlPath': self.paths["texture"], # 建模范围 # 'elevation': self.elevation_input.text(), # 相对航高 'precision': self.precision_combo.currentText(), # 建模精度 'interval': '2.0', # 照片获取等待时间 'imagesnum': self.tile_size_input.text(), # 每多少张图片生成一个文件夹 'imagesFolderPath':self.paths["project"]+'/'+ self.paths["sensor"], # 图像文件夹路径 # 'minphotonSize':self.minphotonSize.text(), # 地面分辨率 'projectPath':self.paths["project"], # 项目工程路径 'taskQueuePath':self.paths["sensor"], # 任务队列路径 'cameraFile':self.paths["1"], # 相机文件路径 'dataSource':self.data_source, # 数据源 'customTileSize':self.tile_size_input.text(), # 自定义瓦片大小 'imagesWaitTime':self.interval_input.text(), # 照片获取等待时间 'DEM':False, # 是否生成DEM 'DSM':False, # 是否生成DSM 'zhengshe':self.checkbox1.isChecked(), # 是否生成正射图 'qingxie':self.checkbox2.isChecked(), # 是否生成倾斜图 'quanjian':self.checkbox3.isChecked(), # 是否生成全建图 'minImagesPerSquare':4 , #建模一块所需数量 'resultNameDir':'result/MODEL/'+self.result_file_name.text(), # 建模结果文件名 "overlapRatio":0.7, #重叠率 "sideOverlapRatio":0.8, #侧重叠率 "localtions":self.paths["localtions"], "centerpointlongitude":114.25, "centerpointlatitude":30.58, "imagesnum":0, "ziplocaltions":self.paths["project"]+'/'+ "ziplocaltions" } with open('config.ini', 'w', encoding='utf-8') as configfile: config.write(configfile) print("配置文件 config.ini 已创建。") def run_command(self, command): for lng_input, lat_input in self.input_widgets: lng = lng_input.text() lat = lat_input.text() if not lng or not lat: QMessageBox.warning(self, "提示", "请填写完整的建模经纬度范围 。") return # 检查输入框是否为空 if not self.paths["sensor"]: QMessageBox.warning(self, "提示", "任务队列路径不能为空。") return self.task_started = True # 设置任务开始标志为 True # # 任务路径不能和之前的相同 # config = configparser.ConfigParser() # try: # with open('config.ini', 'r', encoding='utf-8') as configfile: # config.read_file(configfile) # except Exception as e: # print(f"读取配置文件时出错: {e}") # # return # folder_path = config.get('settings', 'imagesFolderPath', fallback=None) # print(folder_path) # if folder_path == self.paths["project"]+"/"+self.paths["sensor"]: # QMessageBox.warning(self, "提示", "任务队列路径不能重复。") # return # 清空目标文件夹,如果存在 # destination_folder = self.paths["project"]+"/"+self.paths["sensor"] # if os.path.exists(destination_folder): # shutil.rmtree(destination_folder) # if not self.paths["camera_file"]: # QMessageBox.warning(self, "提示", "相机文件路径不能为空。") # return if not self.paths["texture"]: QMessageBox.warning(self, "提示", "建模范围路径不能为空。") return if not self.paths["localtions"]: QMessageBox.warning(self, "提示", "图片文件路径不能为空。") return if not self.result_file_name.text(): QMessageBox.warning(self, "提示", "建模结果文件名不能为空。") return # if not self.elevation_input.text(): # QMessageBox.warning(self, "提示", "相对航高不能为空。") # return # if not self.minphotonSize.text(): # QMessageBox.warning(self, "提示", "像元大小不能为空。") # return if not self.tile_size_input.text(): QMessageBox.warning(self, "提示", "自定义瓦片大小不能为空。") return if not self.interval_input.text(): QMessageBox.warning(self, "提示", "照片获取等待时间不能为空。") return self.create_config_file() # 调用生成配置文件的方法 # 将按钮状态设为不可用 self.start_button.setEnabled(False) # self.start_button.setText("正在处理...") self.start_button.repaint() self.start_button.update() # 调用文件服务每多少张图生成一个文件夹 # 直接调用 main 函数 # 创建一个新的线程来运行文件服务 file_service_thread = threading.Thread(target=文件服务2.main) file_service_thread.start() # 启动线程 self.generate_kml() # 生成KML文件 # 生成配置文件后,调用startBuilding.exe def is_exe_running(exe_name): # 检查是否有指定的exe正在运行 for proc in psutil.process_iter(['pid', 'name']): try: if proc.info['name'] == exe_name: return True except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass return False # 先判断是否有startbuilding.exe在运行,如果有则关闭所有docker容器,然后关闭startbuilding.exe,再启动startbuilding.exe if is_exe_running("startBuilding.exe"): # 关闭所有基于"opendronemap/odm:gpu"的容器 self.stop_docker_container("opendronemap/odm:gpu") # 关闭startBuilding.exe self.kill_exe("startBuilding.exe") self.process =subprocess.Popen("startBuilding.exe", shell=False,creationflags=subprocess.CREATE_NO_WINDOW) time.sleep(3) # 等待1秒,等待startBuilding.exe启动完成 # 刷新页面 self.web_view.reload() # 使用 QWebEngineView 的 reload 方法 # print(command) # import subprocess # subprocess.Popen(command, shell=True) def file_transfer(self): # 调用文件传输模块 self.process =subprocess.Popen("地表建模-三维航测数据同步.exe", shell=False,creationflags=subprocess.CREATE_NO_WINDOW) # 平台上传 def file_transfer_to_platform(self): # 调用文件传输模块 self.process =subprocess.Popen("结果保存.exe", shell=False,creationflags=subprocess.CREATE_NO_WINDOW) def kill_exe(self, exe_name): """ 杀死exe进程 :param exe_name:进程名字 :return:无 """ os.system('taskkill /f /t /im '+exe_name)#MESMTPC.exe程序名字 print("杀死进程{}".format(exe_name)) def stopTask(self): """停止任务并关闭占用端口3123的进程,同时关闭Docker容器""" # 创建进度对话框 # 创建一个新的窗口 # 显示关闭提示框 msg_box = QMessageBox() msg_box.setWindowTitle("关闭提示") msg_box.setText("软件正在关闭中,请稍候...") msg_box.setStandardButtons(QMessageBox.NoButton) msg_box.setModal(True) # 模态对话框 msg_box.show() QApplication.processEvents() # 处理事件,确保对话框显示 # 关闭 node 进程 if self.node_process: # 检查 node 进程是否存在 self.node_process.kill() # 终止 node 进程 print("已关闭 node 进程") # 查找使用3123的进程 result = subprocess.run("netstat -ano | findstr :3123", capture_output=True, text=True, shell=False, creationflags=subprocess.CREATE_NO_WINDOW) lines = result.stdout.splitlines() if lines: for line in lines: if line: # 确保不是空行 parts = line.split() pid = parts[-1] # 假设最后一部分是PID if pid.isdigit(): os.system(f"taskkill /PID {pid} /F") # 强制关闭该进程 print(f"已关闭占用端口3123的进程 {pid}") # 停止 startBuilding.exe exe_name = 'startBuilding.exe' self.kill_exe(exe_name) # 循环关闭Docker容器,直到3秒没有关闭为止 stop_attempts = 0 while stop_attempts < 5: self.stop_docker_container("opendronemap/odm:gpu") # 更新进度对话框 # progress_dialog.setLabelText("正在关闭Docker容器...请稍候...") # 等待1秒 time.sleep(1) # 检查容器是否还在运行 result = subprocess.run( ["docker", "ps", "--filter", "ancestor=opendronemap/odm:gpu", "--format", "{{.ID}}"], capture_output=True, text=True ) if not result.stdout.strip(): # 如果没有运行的容器,退出循环 break stop_attempts += 1 print("任务停止完成。") # 任务停止完成后关闭对话框 msg_box.close() # 停止dockers def stop_docker_container(self, image_name): # 获取所有基于image_name的容器ID result = subprocess.run( ["docker", "ps", "-f", f"ancestor={image_name}", "-q"], capture_output=True, text=True ) container_ids = result.stdout.strip().split('\n') # 停止每个容器 for container_id in container_ids: subprocess.run(["docker", "stop", container_id]) # 所有容器都停止后,返回True,否则返回False return not container_ids # def createPathInput(self, layout, label_text, path_key): # """创建一个路径输入组件,包括标签、输入框和选择按钮""" # layout.addWidget(QLabel(label_text)) # path_layout = QHBoxLayout() # path_edit = QLineEdit(self.paths[path_key]) # path_button = QPushButton("...") # path_button.setFixedWidth(30) # path_button.clicked.connect(lambda: self.selectPath(path_edit, path_key)) # path_edit.textChanged.connect(lambda text: self.savePath(text, path_key)) # path_layout.addWidget(path_edit) # path_layout.addWidget(path_button) # layout.addLayout(path_layout) # # 选择文件路径 # def createPathInput_file(self, layout, label_text, path_key,type): # """创建一个路径输入组件,包括标签、输入框和选择按钮""" # layout.addWidget(QLabel(label_text)) # path_layout = QHBoxLayout() # path_edit = QLineEdit(self.paths[path_key]) # path_button = QPushButton("...") # path_button.setFixedWidth(30) # path_button.clicked.connect(lambda: self.setPath(path_edit, path_key,type)) # path_edit.textChanged.connect(lambda text: self.savePath(text, path_key)) # path_layout.addWidget(path_edit) # path_layout.addWidget(path_button) # layout.addLayout(path_layout) # def selectPath(self, path_edit, path_key): # """打开文件对话框,设置路径到输入框""" # folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹") # if folder_path: # path_edit.setText(folder_path) # self.savePath(folder_path, path_key) # # 打开文件对话框,选择文件路径,设置到输入框中,并保存到变量中 # def setPath(self, path_edit, path_key,type): # # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "All Files (*)") # # 只能选json文件 # if type=="json": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "json Files (*.json)") # elif type=="kml": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "kml Files (*.kml)") # if file_path: # path_edit.setText(file_path) # self.savePath(file_path, path_key) def createPathInput(self, layout, label_text, path_key): """创建一个路径输入组件,包括标签、输入框和选择按钮""" layout.addWidget(QLabel(label_text)) path_layout = QHBoxLayout() path_edit = QLineEdit(self.paths[path_key]) path_edit.setReadOnly(True) # 设置为只读状态 path_button = QPushButton("...") path_button.setFixedWidth(30) path_button.clicked.connect(lambda: self.selectPath(path_edit, path_key)) path_edit.textChanged.connect(lambda text: self.savePath(text, path_key)) path_layout.addWidget(path_edit) path_layout.addWidget(path_button) layout.addLayout(path_layout) def createPathInput_file(self, layout, label_text, path_key, type): """创建一个路径输入组件,包括标签、输入框和选择按钮""" layout.addWidget(QLabel(label_text)) path_layout = QHBoxLayout() path_edit = QLineEdit(self.paths[path_key]) path_button = QPushButton("...") path_button.setFixedWidth(30) path_button.clicked.connect(lambda: self.setPath(path_edit, path_key, type)) path_edit.textChanged.connect(lambda text: self.savePath(text, path_key)) path_layout.addWidget(path_edit) path_layout.addWidget(path_button) layout.addLayout(path_layout) def selectPath(self, path_edit, path_key): """打开文件夹选择对话框,设置路径到输入框""" folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹", base_dir) base_path = base_dir if folder_path: # 假如path_key是localtions,则不需要判断是否在C:/RealTimeModeling下 if path_key == "localtions": path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: print(folder_path,base_dir,folder_path.startswith(base_dir)) # 检查选择的目录是否在 C:/RealTimeModeling 下 if folder_path.startswith(base_dir): path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: QMessageBox.warning(self, "提示", f"只能选择123 {base_path} 目录下的文件夹。") def setPath(self, path_edit, path_key, type): """打开文件对话框,选择文件路径,设置到输入框中,并保存到变量中""" base_path = base_dir if type == "json": file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", base_dir, "JSON Files (*.json)") elif type == "kml": file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", base_dir, "KML Files (*.kml)") # Check if the selected path starts with the allowed directory if file_path and file_path.startswith(base_dir): path_edit.setText(file_path) self.savePath(file_path, path_key) else: QMessageBox.warning(self, "提示", f"只能选择 {base_path} 目录下的文件夹。") def savePath(self, path, path_key): """保存路径到变量""" self.paths[path_key] = path print(self.paths) self.updateCommand() def updateCommand(self): # 原始命令 docker run -ti --rm -v C:\3d\源文件\皂角林:/datasets --gpus all opendronemap/odm:gpu --project-path /datasets 皂角林 --3d-tiles --pc-rectify --pc-ept --pc-quality low --feature-quality medium --boundary "/datasets/皂角林/boundary.json" --orthophoto-png --orthophoto-cutline line1=":/datasets --gpus all opendronemap/odm:gpu --project-path /datasets " line2= " --3d-tiles --pc-rectify --orthophoto-png --orthophoto-cutline --pc-ept --pc-quality low --feature-quality medium --boundary /datasets" # self.paths["sensor"]去掉包含 self.paths["project"] 的字符串 self.paths["sensor"] = self.paths["sensor"].replace(self.paths["project"]+"/", "") # self.paths["texture"]=self.paths["texture"].replace(self.paths["project"], "") """更新命令行""" # self.command_line = "docker run --rm -ti -v " + self.paths["project"] + ":/data/project -v " + self.paths["sensor"] + ":/data/sensor -v " + self.paths["camera_file"] + ":/data/camera_file -v " + self.paths["texture"] + ":/data/texture -p 3123:3123 zyf/3dmodel:latest " + self.data_source + " " + self.elevation_input.text() + " " + self.precision_combo.currentText() + " " + self.tile_size_input.text() + " " + self.interval_input.text() + " " + " ".join([str(checkbox.isChecked()) for checkbox in [checkbox1, checkbox2, checkbox3]]) print("工程目录", self.paths["project"] , "任务队列", self.paths["sensor"], "相机文件", # self.paths["1"], "建模范围", self.paths["texture"], "数据源", self.data_source , # "相对航高", # self.elevation_input.text(), "建模精度", # self.precision_combo.currentText() , "自定义瓦片大小", self.tile_size_input.text(), "照片获取等待时间", self.interval_input.text() , "是否生成正射", self.checkbox3.isChecked() ) # " ".join([str(checkbox.isChecked()) for checkbox in [self.checkbox1, self.checkbox2, self.checkbox3]])) def setDataSource(self, source, checked): """保存数据源选择""" if checked: self.data_source = source print(self.data_source) def closeEvent(self, event): """重写关闭事件以在退出时停止任务""" self.stopTask() # 调用停止任务的方法 event.accept() # 允许窗口关闭 def update_progress_bar(self): """根据Progress.ini文件更新进度条,支持多个 imgs 阶段处理""" if not self.task_started: return # 如果任务未开始,不更新进度条 config = configparser.ConfigParser() config.read('d:\\实时建模\\实时建模V3\\Progress.ini') # 获取当前要处理的 imgsi current_index = getattr(self, 'current_img_index', 0) current_key = f'imgs{current_index + 1}' # 检查是否有新的 imgsi=1 出现 if 'Progress' in config and current_key in config['Progress'] and config['Progress'][current_key] == '1': print(f"检测到 {current_key}=1,立即进入完成阶段") self.current_img_index += 1 self.progress_stage_label.setText(f"进度阶段: 第{self.current_img_index}块进度") self._start_immediate_complete() return # 如果还没开始自动增长,并且没有检测到任何 imgsi=1 if not hasattr(self, '_auto_grow_started'): self._auto_grow_started = True self._auto_grow_start_time = time.time() self._auto_grow_target = 80 self._auto_grow_duration = 120 # 120秒增长到80% self._auto_grow_start_value = 0 self.progress_bar.setValue(0) self.progress_stage_label.setText(f"进度阶段: 第{self.current_img_index}块进度") # 计算当前时间进度 elapsed = time.time() - self._auto_grow_start_time if elapsed >= self._auto_grow_duration: self.progress_bar.setValue(self._auto_grow_target) return # 线性增长 progress = int((elapsed / self._auto_grow_duration) * self._auto_grow_target) self.progress_bar.setValue(min(progress, self._auto_grow_target)) # 每秒检查一次进度 QTimer.singleShot(1000, self.update_progress_bar) def _start_immediate_complete(self): """处理 imgsi=1 的情况:5秒内增长到100%,5秒后归零""" start_value = self.progress_bar.value() target_value = 100 duration = 5000 # 5秒 steps = 50 # 每次更新间隔 delta = (target_value - start_value) / (duration / steps) def update(): current = self.progress_bar.value() if current >= target_value: QTimer.singleShot(5000, self._reset_progress) # 5秒后归零 return # 强制转换为整数 next_value = int(min(current + delta, target_value)) self.progress_bar.setValue(next_value) QTimer.singleShot(steps, update) update() def run_commandss(command): logging.info(f"运行命令: {command}") result = 0 result = subprocess.run(command, shell=True) return result.returncode def check_docker_running(): """检查 Docker 是否正在运行,如果没有运行则显示警告弹窗""" # try: # client = docker.from_env() # 尝试获取 Docker 客户端 # client.ping() # 发送 ping 请求确认 Docker 是否可用 # except docker.errors.DockerException as e: # # 如果发生异常,说明 Docker 没有运行 # show_docker_warning() # 显示 Docker 未运行的警告 # sys.exit() # 退出程序 # except Exception as e: # # 捕获其他类型的异常 # show_docker_warning() # sys.exit() try: # 运行 docker info 命令 result = subprocess.run(['docker', 'info'], capture_output=True, text=True, check=True) # 打印输出信息 print(result.stdout) print("Docker 正常启动") except subprocess.CalledProcessError as e: # 如果命令执行失败,打印错误信息 print("Docker 未正常启动") show_docker_warning() # 显示 Docker 未运行的警告 print(e.stderr) sys.exit() except FileNotFoundError: # 如果 docker 命令未找到,说明 Docker 未安装 print("Docker 未安装或未正确配置") show_docker_warning() # 显示 Docker 未运行的警告 sys.exit() def show_docker_warning(): """显示 Docker 未运行的警告""" QMessageBox.warning(None, "警告", "请打开 Docker 并确保它正常运行!重新开始建模任务!") def start_service(): # 验证许可证 if not validate_service(): return # 如果验证失败,退出服务 # 验证成功,继续运行服务 run_service() def run_service(): # 您的服务主逻辑 # 打开软件时清空结果文件名 # 创建要写入的 JSON 数据 data = { "name": '' } # 写入 JSON 文件 with open('./result/resultName.json', 'w', encoding='utf-8') as json_file: json.dump(data, json_file, ensure_ascii=False, indent=4) ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0) # 运行一个命令node result_server_logs.js # subprocess.Popen('node result_server_logs.js', shell=False,creationflags=subprocess.CREATE_NO_WINDOW) app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": start_service()
08-28
import sys import pymysql from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QDialog, QTableWidgetItem, QMessageBox, QHeaderView, QPushButton, QLineEdit, QComboBox, QLabel, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox, QTableWidget ) from PyQt5.QtCore import Qt from PyQt5.QtGui import QIcon, QIntValidator, QRegExpValidator from PyQt5.QtCore import QRegExp # 数据库配置,与原系统保持一致 DB_CONFIG = { 'host': '127.0.0.1', 'user': 'root', 'password': '123456', 'database': 'studentscore', 'charset': 'utf8mb4' } class LoginDialog(QDialog): """登录窗口""" def __init__(self): super().__init__() self.setWindowTitle("专业管理系统登录") self.setFixedSize(350, 200) # 设置登录窗口的背景颜色为淡蓝色 self.setStyleSheet("background-color: #e0f7fa;") # 创建布局 layout = QVBoxLayout() # 表单组 form_group = QGroupBox("数据库登录信息") form_layout = QFormLayout() self.txt_host = QLineEdit() self.txt_host.setText(DB_CONFIG['host']) self.txt_host.setPlaceholderText("数据库主机地址") self.txt_user = QLineEdit() self.txt_user.setText(DB_CONFIG['user']) self.txt_user.setPlaceholderText("数据库用户名") self.txt_pass = QLineEdit() self.txt_pass.setText(DB_CONFIG['password']) self.txt_pass.setPlaceholderText("数据库密码") self.txt_pass.setEchoMode(QLineEdit.Password) self.txt_db = QLineEdit() self.txt_db.setText(DB_CONFIG['database']) self.txt_db.setPlaceholderText("数据库名称") form_layout.addRow("主机:", self.txt_host) form_layout.addRow("用户名:", self.txt_user) form_layout.addRow("密码:", self.txt_pass) form_layout.addRow("数据库:", self.txt_db) form_group.setLayout(form_layout) # 按钮组 btn_layout = QHBoxLayout() self.btn_login = QPushButton("登录") self.btn_login.clicked.connect(self.attempt_login) self.btn_exit = QPushButton("退出") self.btn_exit.clicked.connect(self.reject) btn_layout.addWidget(self.btn_login) btn_layout.addWidget(self.btn_exit) # 添加到主布局 layout.addWidget(form_group) layout.addLayout(btn_layout) self.setLayout(layout) def attempt_login(self): """尝试登录""" config = { 'host': self.txt_host.text().strip(), 'user': self.txt_user.text().strip(), 'password': self.txt_pass.text().strip(), 'database': self.txt_db.text().strip(), 'charset': 'utf8mb4' } if not all(config.values()): QMessageBox.warning(self, "输入不完整", "请填写所有数据库连接信息") return try: # 测试数据库连接 conn = pymysql.connect(**config) # 检查bmajor表是否存在 with conn.cursor() as cursor: cursor.execute("SHOW TABLES LIKE 'bmajor'") if not cursor.fetchone(): QMessageBox.critical(self, "表不存在", "数据库中未找到bmajor表") return conn.close() # 更新全局配置 global DB_CONFIG DB_CONFIG = config self.accept() # 关闭对话框并返回QDialog.Accepted except pymysql.Error as e: QMessageBox.critical(self, "连接失败", f"数据库连接失败:\n{str(e)}") class MajorManager(QMainWindow): """专业管理主窗口""" def __init__(self): super().__init__() self.setWindowTitle("专业信息管理系统") self.setGeometry(100, 100, 1000, 600) # 创建中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout = QVBoxLayout(central_widget) # 搜索功能区 search_group = QGroupBox("专业搜索") search_layout = QHBoxLayout() self.lbl_search = QLabel("专业名称:") self.txt_search = QLineEdit() self.txt_search.setPlaceholderText("输入专业名称关键字...") self.btn_search = QPushButton("搜索") self.btn_search.clicked.connect(self.search_major) self.btn_refresh = QPushButton("刷新数据") self.btn_refresh.clicked.connect(self.load_data) search_layout.addWidget(self.lbl_search) search_layout.addWidget(self.txt_search) search_layout.addWidget(self.btn_search) search_layout.addWidget(self.btn_refresh) search_group.setLayout(search_layout) # 数据表格 self.table = self.create_table() # 操作按钮区 btn_group = QGroupBox("专业操作") btn_layout = QHBoxLayout() self.btn_add = QPushButton("添加专业") self.btn_add.clicked.connect(self.add_major) self.btn_edit = QPushButton("编辑专业") self.btn_edit.clicked.connect(self.edit_major) self.btn_delete = QPushButton("删除专业") self.btn_delete.clicked.connect(self.delete_major) btn_layout.addWidget(self.btn_add) btn_layout.addWidget(self.btn_edit) btn_layout.addWidget(self.btn_delete) btn_group.setLayout(btn_layout) # 添加到主布局 main_layout.addWidget(search_group) main_layout.addWidget(self.table, 1) # 表格占据更多空间 main_layout.addWidget(btn_group) # 状态栏 self.statusBar().showMessage("就绪") # 加载数据 self.load_data() def create_table(self): """创建专业表格""" table = QTableWidget() table.setColumnCount(4) table.setHorizontalHeaderLabels(["专业ID", "专业名称", "院系ID", "院系名称"]) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.verticalHeader().setVisible(False) table.setSelectionBehavior(QTableWidget.SelectRows) table.setEditTriggers(QTableWidget.NoEditTriggers) table.setSortingEnabled(True) return table def get_connection(self): """获取数据库连接""" return pymysql.connect(**DB_CONFIG) def load_data(self): """从数据库加载专业数据""" try: conn = self.get_connection() with conn.cursor() as cursor: # 查询bmajor表的所有数据 cursor.execute("SELECT major_id, major_name, depart_id, depart_name FROM bmajor") result = cursor.fetchall() self.table.setRowCount(len(result)) for row_idx, row in enumerate(result): for col_idx, col in enumerate(row): item = QTableWidgetItem(str(col) if col is not None else "") self.table.setItem(row_idx, col_idx, item) conn.close() self.statusBar().showMessage(f"成功加载 {len(result)} 条专业记录") except pymysql.Error as e: QMessageBox.critical(self, "数据库错误", f"加载数据失败:\n{str(e)}") self.statusBar().showMessage("数据加载失败") def add_major(self): """添加新专业""" dialog = MajorDialog(self) if dialog.exec_() == QDialog.Accepted: major_data = dialog.get_data() try: conn = self.get_connection() with conn.cursor() as cursor: sql = """INSERT INTO bmajor (major_id, major_name, depart_id, depart_name) VALUES (%s, %s, %s, %s)""" cursor.execute(sql, major_data) conn.commit() self.load_data() self.statusBar().showMessage(f"成功添加专业: {major_data[1]}") except pymysql.IntegrityError as e: if "Duplicate entry" in str(e): QMessageBox.critical(self, "添加失败", "专业ID或专业名称已存在") else: QMessageBox.critical(self, "添加失败", f"添加专业失败:\n{str(e)}") except pymysql.Error as e: QMessageBox.critical(self, "添加失败", f"添加专业失败:\n{str(e)}") def edit_major(self): """编辑选中专业""" selected = self.table.currentRow() if selected < 0: QMessageBox.warning(self, "未选择", "请先选择要编辑的专业") return major_id = self.table.item(selected, 0).text() dialog = MajorDialog(self, major_id) if dialog.exec_() == QDialog.Accepted: major_data = dialog.get_data() try: conn = self.get_connection() with conn.cursor() as cursor: sql = """UPDATE bmajor SET major_name = %s, depart_id = %s, depart_name = %s WHERE major_id = %s""" # 注意顺序: name, depart_id, depart_name, id cursor.execute(sql, ( major_data[1], major_data[2], major_data[3], major_data[0] )) conn.commit() self.load_data() self.statusBar().showMessage(f"成功更新专业: {major_data[1]}") except pymysql.Error as e: QMessageBox.critical(self, "更新失败", f"更新专业失败:\n{str(e)}") def delete_major(self): """删除选中专业""" selected = self.table.currentRow() if selected < 0: QMessageBox.warning(self, "未选择", "请先选择要删除的专业") return major_id = self.table.item(selected, 0).text() major_name = self.table.item(selected, 1).text() reply = QMessageBox.question(self, "确认删除", f"确定要删除专业 '{major_name}' (ID: {major_id}) 吗?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: try: conn = self.get_connection() with conn.cursor() as cursor: cursor.execute("DELETE FROM bmajor WHERE major_id = %s", (major_id,)) conn.commit() self.load_data() self.statusBar().showMessage(f"已删除专业: {major_name}") except pymysql.Error as e: # 检查是否有外键约束 if "foreign key constraint" in str(e).lower(): QMessageBox.critical(self, "删除失败", "该专业可能被其他表引用,无法删除") else: QMessageBox.critical(self, "删除失败", f"删除专业失败:\n{str(e)}") def search_major(self): """搜索专业""" keyword = self.txt_search.text().strip() if not keyword: self.load_data() return try: conn = self.get_connection() with conn.cursor() as cursor: cursor.execute("SELECT major_id, major_name, depart_id, depart_name " "FROM bmajor WHERE major_name LIKE %s", (f"%{keyword}%",)) result = cursor.fetchall() self.table.setRowCount(len(result)) for row_idx, row in enumerate(result): for col_idx, col in enumerate(row): item = QTableWidgetItem(str(col) if col is not None else "") self.table.setItem(row_idx, col_idx, item) conn.close() self.statusBar().showMessage(f"找到 {len(result)} 条匹配专业") except pymysql.Error as e: QMessageBox.critical(self, "搜索失败", f"搜索专业失败:\n{str(e)}") class MajorDialog(QDialog): """专业编辑对话框""" def __init__(self, parent=None, major_id=None): super().__init__(parent) self.setWindowTitle("添加专业" if not major_id else "编辑专业") self.setFixedSize(400, 350) # 主布局 layout = QVBoxLayout() # 表单布局 form_layout = QFormLayout() self.txt_id = QLineEdit() self.txt_id.setPlaceholderText("2位专业代码") # 设置专业ID验证器,必须是2位字母或数字 id_validator = QRegExpValidator(QRegExp(r'^[a-zA-Z0-9]{2}$')) self.txt_id.setValidator(id_validator) self.txt_name = QLineEdit() self.txt_name.setPlaceholderText("输入专业名称") self.txt_depart_id = QLineEdit() self.txt_depart_id.setPlaceholderText("2位院系代码") # 设置院系ID验证器,必须是2位字母或数字 depart_id_validator = QRegExpValidator(QRegExp(r'^[a-zA-Z0-9]{2}$')) self.txt_depart_id.setValidator(depart_id_validator) self.txt_depart_name = QLineEdit() self.txt_depart_name.setPlaceholderText("输入院系名称") form_layout.addRow("专业ID:", self.txt_id) form_layout.addRow("专业名称:", self.txt_name) form_layout.addRow("院系ID:", self.txt_depart_id) form_layout.addRow("院系名称:", self.txt_depart_name) # 按钮布局 btn_layout = QHBoxLayout() self.btn_save = QPushButton("保存") self.btn_save.clicked.connect(self.validate_and_accept) self.btn_cancel = QPushButton("取消") self.btn_cancel.clicked.connect(self.reject) btn_layout.addStretch() btn_layout.addWidget(self.btn_save) btn_layout.addWidget(self.btn_cancel) # 添加到主布局 layout.addLayout(form_layout) layout.addLayout(btn_layout) self.setLayout(layout) # 如果是编辑模式,加载现有数据 self.major_id = major_id if major_id: self.load_major_data() self.txt_id.setEnabled(False) # 禁止编辑专业ID def load_major_data(self): """加载专业数据到表单""" try: conn = pymysql.connect(**DB_CONFIG) with conn.cursor() as cursor: cursor.execute("SELECT * FROM bmajor WHERE major_id = %s", (self.major_id,)) result = cursor.fetchone() if result: self.txt_id.setText(result[0]) self.txt_name.setText(result[1]) self.txt_depart_id.setText(result[2] if result[2] is not None else "") self.txt_depart_name.setText(result[3] if result[3] is not None else "") conn.close() except pymysql.Error as e: QMessageBox.critical(self, "加载失败", f"加载专业数据失败:\n{str(e)}") def get_data(self): """从表单获取数据""" # 处理可能为空的值 def get_value_or_none(text): return text.strip() if text.strip() else None return ( self.txt_id.text().strip(), self.txt_name.text().strip(), get_value_or_none(self.txt_depart_id.text()), get_value_or_none(self.txt_depart_name.text()) ) def validate_and_accept(self): """验证表单数据并接受""" # 验证专业ID格式 major_id = self.txt_id.text().strip() if len(major_id) != 2: QMessageBox.warning(self, "格式错误", "专业ID必须是2位字符") return # 验证必填字段 if not self.txt_name.text().strip(): QMessageBox.warning(self, "输入不完整", "专业名称不能为空") return # 验证院系ID depart_id = self.txt_depart_id.text().strip() if depart_id and len(depart_id) != 2: QMessageBox.warning(self, "格式错误", "院系ID必须是2位字符") return super().accept() if __name__ == "__main__": app = QApplication(sys.argv) # 显示登录窗口 login = LoginDialog() if login.exec_() != QDialog.Accepted: sys.exit() # 显示主窗口 window = MajorManager() window.show() sys.exit(app.exec_())
06-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值