def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = thermui.Ui_Form() self.ui.setupUi(self) envconfname = 'THERM_CONFIG' confname = None if envconfname in os.environ: confname = os.environ[envconfname] if not confname: QMessageBox.critical(self, "therm", "No %s in environment" % envconfname) sys.exit(1) conf = conftree.ConfSimple(confname) utils.initlog(conf) self.logger = logging.getLogger() self.datarepo = conf.get('datarepo') self.scratchdir = conf.get('scratchdir') if not self.datarepo or not self.scratchdir: QMessageBox.critical(self, "therm", "No 'datarepo' or 'scratchdir' in config") sys.exit(1) self.remotesettingfile = os.path.join(self.datarepo, 'consigne') self.myscratchact = os.path.join(self.scratchdir, 'ui') self.myscratchdis = os.path.join(self.scratchdir, 'ui-') self.ctlscratch = os.path.join(self.scratchdir, 'ctl') if not os.path.exists(self.ctlscratch): QMessageBox.critical( self, "therm", "%s not found: controller not active" % self.ctlscratch) sys.exit(1) self._refreshTimer = QtCore.QTimer() self._refreshTimer.timeout.connect(self._periodic) self._refreshTimer.start(15 * 1000.0) self._readCtlValues() cf = None self.localactive = False if os.path.exists(self.myscratchact): # Local control self.localactive = True cf = conftree.ConfSimple(self.myscratchact) elif os.path.exists(self.myscratchdis): cf = conftree.ConfSimple(self.myscratchdis) if cf: self.localsetting = float(cf.get("localsetting") or 20.5) else: self.localsetting = 20.5 if self.localactive: self.ui.commandePB.setChecked(True) self.on_commandePB_toggled(True) else: self.ui.commandePB.setChecked(False) self.on_commandePB_toggled(False) self.ui.dial.setWrapping(True) self.lastDialValue = 0
def __init__(self, fn=''): if fn: self.conf = conftree.ConfSimple(fn) uplog("Minim config read: contentDir: %s" % self.conf.get("minimserver.contentDir")) else: self.conf = conftree.ConfSimple('/dev/null') self.quotes = "\"'" self.escape = '' self.whitespace = ', '
def get(self): # Always check for a local setting, it overrides the remote if g_uisettingfile and os.path.exists(g_uisettingfile): try: cf = conftree.ConfSimple(g_uisettingfile) tmp = cf.get('localsetting') if tmp: return float(tmp) except: pass now = time.time() logger.debug("SetpointGetter: get. setpointfromgit %s", self.setpointfromgit) if self.setpointfromgit is None or \ now > self.lasttime + self.fetchinterval: logger.debug("Fetching setpoint") self.setpointfromgit = gitif.fetch_setpoint() if not self.setpointfromgit: self.giterrorcnt += 1 else: self.giterrorcnt = 0 if self.giterrorcnt >= self.maxgiterrors: # Let the watchdog handle this logger.critical("Too many git pull errors, exiting") pioif.turnoff() sys.exit(1) logger.debug("SetpointGetter: got %s", self.setpointfromgit) self.lasttime = now return self.setpointfromgit or self.safetemp
def maybelogin(): msgproc.log("READY") global formatid global httphp global pathprefix global is_logged_in if is_logged_in: return True if "UPMPD_HTTPHOSTPORT" not in os.environ: raise Exception("No UPMPD_HTTPHOSTPORT in environment") httphp = os.environ["UPMPD_HTTPHOSTPORT"] if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) username = upconfig.get('highresaudiouser') password = upconfig.get('highresaudiopass') formatid = 7 setMimeAndSamplerate("application/flac", "44100") if not username or not password: raise Exception( "highresaudiouser and/or highresaudiopass not set in configuration" ) is_logged_in = session.login(username, password)
def getserviceuserpass(upconfig, servicename): username = upconfig.get(servicename + 'user') password = upconfig.get(servicename + 'pass') if not username or not password: altconf = conftree.ConfSimple('/var/cache/upmpdcli/ohcreds/screds') username = altconf.get(servicename + 'user') password = altconf.get(servicename + 'pass') return username, password
def __init__(self): self.tgupfile = None upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) self.minimcnffn = upconfig.get("uprclminimconfig") if self.minimcnffn: conf = conftree.ConfSimple(self.minimcnffn) self.tgupfile = conf.get("minimserver.tagUpdate") #uplog("Minim config read: tagUpdate: %s" % self.tgupfile) self.tgupfile = self._makeabs(self.tgupfile) if not os.path.exists(self.tgupfile): uplog("gettagupdate: %s does not exist" % self.tgupfile) return wlogfn = conf.get("minimserver.writeTagChanges") wlogfn = self._makeabs(wlogfn) #uplog("gettagupdate: log file: %s" % wlogfn) if wlogfn: global _logfp try: _logfp = open(wlogfn, "a") except Exception as ex: uplog("can't open %s : %s" % (wlogfn, ex))
def maybelogin(a={}): # Do this always setidprefix(spotidprefix) global is_logged_in if is_logged_in: return True if "UPMPD_HTTPHOSTPORT" not in os.environ: raise Exception("No UPMPD_HTTPHOSTPORT in environment") global httphp httphp = os.environ["UPMPD_HTTPHOSTPORT"] if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") global pathprefix pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) global cachedir cachedir = upconfig.get('cachedir') if not cachedir: me = pwd.getpwuid(os.getuid()).pw_name uplog("me: %s" % me) if me == 'upmpdcli': cachedir = '/var/cache/upmpdcli/' else: cachedir = os.path.expanduser('~/.cache/upmpdcli/') cachedir = os.path.join(cachedir, servicename) try: os.makedirs(cachedir) except OSError as exc: if exc.errno == errno.EEXIST and os.path.isdir(cachedir): pass else: raise uplog("cachedir: %s" % cachedir) if 'user' in a: username = a['user'] else: username = upconfig.get(servicename + 'user') if not username: raise Exception("spotifyuser not set in configuration") is_logged_in = session.login(username, os.path.join(cachedir, "token")) setMimeAndSamplerate("audio/mpeg", "44100") if not is_logged_in: raise Exception("spotify login failed")
def maybelogin(a={}): global session global quality global httphp global pathprefix global is_logged_in # Do this always setidprefix(tidalidprefix) if is_logged_in: return True if "UPMPD_HTTPHOSTPORT" not in os.environ: raise Exception("No UPMPD_HTTPHOSTPORT in environment") httphp = os.environ["UPMPD_HTTPHOSTPORT"] if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) if 'user' in a: username = a['user'] password = a['password'] else: username, password = getserviceuserpass(upconfig, 'tidal') if not username or not password: raise Exception("tidaluser and/or tidalpass not set in configuration") qalstr = upconfig.get('tidalquality') if qalstr == 'lossless': quality = Quality.lossless elif qalstr == 'high': quality = Quality.high elif qalstr == 'low': quality = Quality.low elif not qalstr: quality = Quality.high else: raise Exception("Bad tidal quality string in config. " + "Must be lossless/high/low") tidalconf = tidalapi.Config(quality) session = tidalapi.Session(config=tidalconf) session.login(username, password) is_logged_in = True if quality == Quality.lossless: setMimeAndSamplerate('audio/flac', "44100") else: setMimeAndSamplerate('audio/mpeg', "44100")
def init(): # Give ntpd a little time to adjust the date. time.sleep(60) envconfname = 'CLIMCAVE_CONFIG' confname = None if envconfname in os.environ: confname = os.environ[envconfname] if not confname: raise Exception("NO %s in environment" % envconfname) conf = conftree.ConfSimple(confname) utils.initlog(conf) global logger logger = logging.getLogger(__name__) global g_templog g_templog = conf.get('templog') pidfile = conf.get('pidfile') if not pidfile: pidfile = os.path.join(os.path.dirname(g_templog), 'climcave.pid') utils.pidw(pidfile) global g_idtempext g_idtempext = conf.get('idtempext') global g_idtempint g_idtempint = conf.get('idtempint') if not g_idtempext or not g_idtempint: logger.critical( "No idtempext or idtempint defined in configuration") sys.exit(1) global g_using_t2ss g_using_t2ss = conf.get('using_t2ss') if g_using_t2ss: idctl1 = conf.get('idctl1') if not idctl1: logger.critical("No idctl1 defined in configuration") sys.exit(1) import t2ssif t2ssif.init(idctl1) else: gpio_pin = int(conf.get("gpio_pin")) if not gpio_pin: logger.critical("No gpio_pin defined in configuration") sys.exit(1) global pioif import pioif pioif.init(gpio_pin)
def runindexer(confdir, topdirs, rebuild=False): global _idxproc, _lastidxstatus if _idxproc is not None: raise Exception("uprclrunindexer: already running") _maybeinitconfdir(confdir, topdirs) cf = conftree.ConfSimple(os.path.join(confdir, "recoll.conf"), readonly=False) td = cf.get("topdirs", '') if td != topdirs: cf.set("topdirs", topdirs) env = copy.deepcopy(os.environ) env["HOME"] = confdir if rebuild: _idxproc = subprocess.Popen(["recollindex", "-c", confdir, "-z"]) else: _idxproc = subprocess.Popen(["recollindex", "-c", confdir])
def runindexer(confdir, topdirs): global _idxproc, _lastidxstatus if _idxproc is not None: raise Exception("uprclrunindexer: already running") if not os.path.isdir(confdir): if os.path.exists(confdir): raise Exception("Exists and not directory: %s" % confdir) _initconfdir(confdir, topdirs) else: cf = conftree.ConfSimple(os.path.join(confdir, "recoll.conf"), readonly=False) td = cf.get("topdirs", '') if td != topdirs: cf.set("topdirs", topdirs) env = copy.deepcopy(os.environ) env["HOME"] = confdir _idxproc = subprocess.Popen(["recollindex", "-c", confdir])
def maybelogin(): global quality global httphp global pathprefix global is_logged_in if is_logged_in: return True if "UPMPD_HTTPHOSTPORT" not in os.environ: raise Exception("No UPMPD_HTTPHOSTPORT in environment") httphp = os.environ["UPMPD_HTTPHOSTPORT"] if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) username = upconfig.get('gmusicuser') password = upconfig.get('gmusicpass') quality = upconfig.get('gmusicquality') if quality not in ('hi', 'med', 'low'): raise Exception("gmusic bad quality value: " + quality) deviceid = upconfig.get('gmusicdeviceid') if not deviceid: deviceid = None if not username or not password: raise Exception( "gmusicuser and/or gmusicpass not set in configuration") is_logged_in = session.login(username, password, deviceid) if not is_logged_in: raise Exception("gmusic login failed") setMimeAndSamplerate('audio/mpeg', "44100")
def maybelogin(): global formatid global httphp global pathprefix global is_logged_in if is_logged_in: return True if "UPMPD_HTTPHOSTPORT" not in os.environ: raise Exception("No UPMPD_HTTPHOSTPORT in environment") httphp = os.environ["UPMPD_HTTPHOSTPORT"] if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) username = upconfig.get('qobuzuser') password = upconfig.get('qobuzpass') formatid = upconfig.get('qobuzformatid') if formatid: formatid = int(formatid) else: formatid = 5 if formatid == 5: setMimeAndSamplerate("audio/mpeg", "44100") else: setMimeAndSamplerate("application/flac", "44100") if not username or not password: raise Exception("qobuzuser and/or qobuzpass not set in configuration") is_logged_in = session.login(username, password)
def _uprcl_init_worker(): ####### # Acquire configuration data. global g_pathprefix # pathprefix would typically be something like "/uprcl". It's used # for dispatching URLs to the right plugin for processing. We # strip it whenever we need a real file path if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") g_pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") global g_friendlyname if "UPMPD_FNAME" in os.environ: g_friendlyname = os.environ["UPMPD_FNAME"] global g_upconfig g_upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) global g_minimconfig minimcfn = g_upconfig.get("uprclminimconfig") g_minimconfig = minimconfig.MinimConfig(minimcfn) global g_httphp g_httphp = g_upconfig.get("uprclhostport") if g_httphp is None: ip = findmyip() g_httphp = ip + ":" + "9090" uplog("uprclhostport not in config, using %s" % g_httphp) global g_rclconfdir g_rclconfdir = g_upconfig.get("uprclconfdir") if g_rclconfdir is None: uplog("uprclconfdir not in config, using /var/cache/upmpdcli/uprcl") g_rclconfdir = "/var/cache/upmpdcli/uprcl" global g_rcltopdirs g_rcltopdirs = g_upconfig.get("uprclmediadirs") if not g_rcltopdirs: g_rcltopdirs = g_minimconfig.getcontentdirs() if g_rcltopdirs: g_rcltopdirs = conftree.stringsToString(g_rcltopdirs) if not g_rcltopdirs: raise Exception("uprclmediadirs not in config") pthstr = g_upconfig.get("uprclpaths") if pthstr is None: uplog("uprclpaths not in config") pthlist = stringToStrings(g_rcltopdirs) pthstr = "" for p in pthlist: pthstr += p + ":" + p + "," pthstr = pthstr.rstrip(",") uplog("Path translation: pthstr: %s" % pthstr) lpth = pthstr.split(',') pathmap = {} for ptt in lpth: l = ptt.split(':') pathmap[l[0]] = l[1] host, port = g_httphp.split(':') # Start the bottle app. Its' both the control/config interface and # the file streamer httpthread = threading.Thread(target=runbottle, kwargs={ 'host': host, 'port': int(port), 'pthstr': pthstr, 'pathprefix': g_pathprefix }) httpthread.daemon = True httpthread.start() _update_index() uplog("Init done")
def uprcl_init(): global httphp, pathprefix, pathmap, rclconfdir, g_rcldocs if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) httphp = upconfig.get("uprclhostport") if httphp is None: raise Exception("uprclhostport not in config") pthstr = upconfig.get("uprclpaths") if pthstr is None: raise Exception("uprclpaths not in config") lpth = pthstr.split(',') pathmap = {} for ptt in lpth: l = ptt.split(':') pathmap[l[0]] = l[1] global rclconfdir rclconfdir = upconfig.get("uprclconfdir") if rclconfdir is None: raise Exception("uprclconfdir not in config") g_rcldocs = uprclfolders.inittree(rclconfdir, httphp, pathprefix) uprcltags.recolltosql(g_rcldocs) uprcluntagged.recoll2untagged(g_rcldocs) host, port = httphp.split(':') if False: # Running the server as a thread. We get into trouble because # something somewhere writes to stdout a bunch of --------. # Could not find where they come from, happens after a sigpipe # when a client closes a stream. The --- seem to happen before # and after the exception strack trace, e.g: # ---------------------------------------- # Exception happened during processing of request from ('192... # Traceback... # [...] # error: [Errno 32] Broken pipe # ---------------------------------------- httpthread = threading.Thread(target=uprclhttp.runHttp, kwargs={ 'host': host, 'port': int(port), 'pthstr': pthstr, 'pathprefix': pathprefix }) httpthread.daemon = True httpthread.start() else: # Running the HTTP server as a separate process cmdpath = os.path.join(os.path.dirname(sys.argv[0]), 'uprclhttp.py') cmd = subprocess.Popen((cmdpath, host, port, pthstr, pathprefix), stdin=open('/dev/null'), stdout=sys.stderr, stderr=sys.stderr, close_fds=True)
if not _try_run_git(g_gitcmd + ['add', '.']): return if not _try_run_git(g_gitcmd + ['commit', '-q', '-m', 'n']): return _try_run_git(g_gitcmd + ['push', '-q']) ########## if __name__ == '__main__': def perr(s): print("%s" % s, file=sys.stderr) logging.basicConfig() import conftree conf = conftree.ConfSimple("therm_config") datarepo = conf.get('datarepo') if not datarepo: perr("No 'datarepo' param in configuration") sys.exit(1) init(datarepo) def usage(): perr("Usage: gitif.py <cmd>") perr("cmd:") perr(" setpoint") sys.exit(1) if len(sys.argv) != 2: usage() cmd = sys.argv[1]
def init(): # Give ntpd a little time to adjust the date. # time.sleep(60) envconfname = 'THERM_CONFIG' confname = None if envconfname in os.environ: confname = os.environ[envconfname] if not confname: raise Exception("NO %s in environment" % envconfname) conf = conftree.ConfSimple(confname) utils.initlog(conf) global logger logger = logging.getLogger(__name__) # Pid here is the process id, nothing to do with the control loop pidfile = conf.get('pidfile') if not pidfile: pidfile = '/tmp/thermostat.pid' utils.pidw(pidfile) global g_housetempids g_housetempids = conf.get('housetempids').split() if not g_housetempids: logger.critical("No housetempids defined in configuration") sys.exit(1) datarepo = conf.get('datarepo') if not datarepo: logger.critical("No 'datarepo' param in configuration") sys.exit(1) global g_uisettingfile, g_tempscratch scratchdir = conf.get('scratchdir') if scratchdir: g_uisettingfile = os.path.join(scratchdir, 'ui') g_tempscratch = os.path.join(scratchdir, 'ctl') else: g_uisettingfile = None g_tempscratch = None gitif.init(datarepo) thermlog.init(datarepo) gpio_pin = int(conf.get("gpio_pin")) if not gpio_pin: logger.critical("No fan_pin defined in configuration") sys.exit(1) pioif.init(gpio_pin) # Are we using a PID controller or an on/off global g_using_pid, g_heatingperiod g_using_pid = int(conf.get("using_pid") or 0) if g_using_pid: # PID coefs global g_kp, g_ki, g_kd # Using 100.0 means that we go 100% for 1 degrees of # error. This makes sense if the heating can gain 1 degree in # a period (1/2 hour). g_kp = float(conf.get('pid_kp') or 100.0) # The Ki needs to be somewhat normalized against the (very # long) sample period. We use the normalized half kp by # default, meaning that the Ki contribution on a single period # will be half the kp one. Of course it will go up over # multiple periods. g_ki = float(conf.get('pid_ki') or g_kp / (2.0 * g_heatingperiod)) g_kd = float(conf.get('pid_kd') or 0.0) else: global g_hysteresis g_hysteresis = float(conf.get('hysteresis') or 0.5)
def _readCtlValues(self): cf = conftree.ConfSimple(self.ctlscratch) self.measuredtemp = float(cf.get("measuredtemp")) self.remotesetting = open(self.remotesettingfile, 'r').read().strip() self.remotesetting = float(self.remotesetting)
def _uprcl_init_worker(): global httphp, pathprefix, rclconfdir, g_rcldocs # pathprefix would typically be something like "/uprcl". It's used # for dispatching URLs to the right plugin for processing. We # strip it whenever we need a real file path if "UPMPD_PATHPREFIX" not in os.environ: raise Exception("No UPMPD_PATHPREFIX in environment") pathprefix = os.environ["UPMPD_PATHPREFIX"] if "UPMPD_CONFIG" not in os.environ: raise Exception("No UPMPD_CONFIG in environment") upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"]) httphp = upconfig.get("uprclhostport") if httphp is None: ip = findmyip() httphp = ip + ":" + "9090" uplog("uprclhostport not in config, using %s" % httphp) rclconfdir = upconfig.get("uprclconfdir") if rclconfdir is None: uplog("uprclconfdir not in config, using /var/cache/upmpdcli/uprcl") rclconfdir = "/var/cache/upmpdcli/uprcl" rcltopdirs = upconfig.get("uprclmediadirs") if rcltopdirs is None: raise Exception("uprclmediadirs not in config") pthstr = upconfig.get("uprclpaths") if pthstr is None: uplog("uprclpaths not in config") pthlist = stringToStrings(rcltopdirs) pthstr = "" for p in pthlist: pthstr += p + ":" + p + "," pthstr = pthstr.rstrip(",") uplog("Path translation: pthstr: %s" % pthstr) lpth = pthstr.split(',') pathmap = {} for ptt in lpth: l = ptt.split(':') pathmap[l[0]] = l[1] # Update or create index. uplog("Creating updating index in %s for %s" % (rclconfdir, rcltopdirs)) start = timer() uprclindex.runindexer(rclconfdir, rcltopdirs) # Wait for indexer while not uprclindex.indexerdone(): time.sleep(.5) fin = timer() uplog("Indexing took %.2f Seconds" % (fin - start)) g_rcldocs = uprclfolders.inittree(rclconfdir, httphp, pathprefix) uprcltags.recolltosql(g_rcldocs) uprcluntagged.recoll2untagged(g_rcldocs) host, port = httphp.split(':') if True: # Running the server as a thread. We get into trouble because # something somewhere writes to stdout a bunch of --------. # Could not find where they come from, happens after a sigpipe # when a client closes a stream. The --- seem to happen before # and after the exception strack trace, e.g: # ---------------------------------------- # Exception happened during processing of request from ('192... # Traceback... # [...] # error: [Errno 32] Broken pipe # ---------------------------------------- # # **Finally**: found it: the handle_error SocketServer method # was writing to stdout. Overriding it should have fixed the # issue. Otoh the separate process approach works, so we kept # it for now httpthread = threading.Thread(target=uprclhttp.runHttp, kwargs={ 'host': host, 'port': int(port), 'pthstr': pthstr, 'pathprefix': pathprefix }) httpthread.daemon = True httpthread.start() else: # Running the HTTP server as a separate process cmdpath = os.path.join(os.path.dirname(sys.argv[0]), 'uprclhttp.py') cmd = subprocess.Popen((cmdpath, host, port, pthstr, pathprefix), stdin=open('/dev/null'), stdout=sys.stderr, stderr=sys.stderr, close_fds=True) global _initrunning _initrunning = False msgproc.log("Init done")
def __init__(self, fname): self.data = conftree.ConfSimple(fname)
########## if __name__ == '__main__': import conftree confname = "/home/dockes/.climcave_config" def perr(s): print("%s" % s, file=sys.stderr) def usage(): perr("t2ssif.py: Usage: t2ssif.py <cmd>") perr("cmd:") perr(" fanstate") sys.exit(1) if len(sys.argv) != 2: usage() cmd = sys.argv[1] conf = conftree.ConfSimple(confname) idctl1 = conf.get('idctl1') if not idctl1: perr("No idctl1 defined in %s" % confname) sys.exit(1) init(idctl1) if cmd == "fanstate": print("fan state %d" % fanstate()) else: usage() sys.exit(0)