<commit_before>import os
import sys
import time
from collections import OrderedDict, deque

import exitutils
import alerttasks
import config
import debug
import hw
import logsupport
import screens.__screens as screens
import threadmanager
from controlevents import *
from eventlist import AlertEventItem, ProcEventItem
from eventlist import EventList
from logsupport import ConsoleWarning, ConsoleError, ConsoleDetail, ReportStatus
import historybuffer
import maintscreen


class DisplayScreen(object):
	def __init__(self):

		debug.debugPrint("Main", "Screensize: ", hw.screenwidth, hw.screenheight)
		logsupport.Logs.Log("Screensize: " + str(hw.screenwidth) + " x " + str(hw.screenheight))
		logsupport.Logs.Log(
			"Scaling ratio: " + "{0:.2f} W ".format(hw.dispratioW) + "{0:.2f} H".format(hw.dispratioH))

		self.dim = 'Bright'  # either Bright or Dim (or '' for don't change when a parameter
		self.state = 'Home'  # one of Home, NonHome, Maint, Cover, Alert

		# Central Task List
		self.Tasks = EventList()
		self.Deferrals = []
		self.WatchVarVals = {}  # most recent reported watched variable values

		self.AS = None  # Active Screen
		self.ScreensDict = {}  # all the sceens by name for setting navigation keys
		self.Chain = 0  # which screen chain is active 0: Main chain 1: Secondary Chain
		self.HBScreens = historybuffer.HistoryBuffer(20, 'Screens')
		self.HBEvents = historybuffer.HistoryBuffer(80, 'Events')

	def Dim(self):
		self.dim = 'Dim'
		hw.GoDim(int(config.sysStore.DimLevel))

	def Brighten(self):
		self.dim = 'Bright'
		hw.GoBright(int(config.sysStore.BrightLevel))

	@staticmethod
	def SetActivityTimer(timeinsecs, dbgmsg):
		pygame.time.set_timer(ACTIVITYTIMER, timeinsecs * 1000)
		debug.debugPrint('Dispatch', 'Set activity timer: ', timeinsecs, ' ', dbgmsg)

	def SwitchScreen(self, NS, newdim, newstate, reason, NavKeys=True):
		ASname = '*None*' if self.AS is None else self.AS.name
		self.HBScreens.Entry(
			NS.name + ' was ' + ASname + ' dim: ' + str(newdim) + ' state: ' + str(newstate) + ' reason: ' + str(
				reason))
		# config.sysStore.SetVal(['CurrentScreen'],NS.name) #todo temp
		config.sysStore.CurrentScreen = NS.name
		oldstate = self.state
		olddim = self.dim
		if NS == screens.HomeScreen:  # always force home state on move to actual home screen
			newstate = 'Home'
		if NS == self.AS:
			debug.debugPrint('Dispatch', 'Null SwitchScreen: ', reason)
			logsupport.Logs.Log('Null switchscreen: ' + reason, hb=True)
		if self.AS is not None and self.AS != NS:
			debug.debugPrint('Dispatch', "Switch from: ", self.AS.name, " to ", NS.name, "Nav=", NavKeys, ' State=',
					   oldstate + '/' + newstate + ':' + olddim + '/' + newdim, ' ', reason)
			self.AS.ExitScreen()
		OS = self.AS
		self.AS = NS
		if newdim == 'Dim':
			self.Dim()
			if olddim == 'Dim':
				if newstate == 'Cover':
					# special case persist
					self.SetActivityTimer(screens.DimIdleTimes[0], reason + ' using cover time')
				else:
					self.SetActivityTimer(self.AS.PersistTO, reason)
			else:
				self.SetActivityTimer(self.AS.DimTO, reason)

		elif newdim == 'Bright':
			self.Brighten()
			self.SetActivityTimer(self.AS.DimTO, reason)
		else:
			pass  # leave dim as it

		if NavKeys:
			nav = OrderedDict(
				{'prevkey': self.ScreensDict[self.AS.name].prevkey, 'nextkey': self.ScreensDict[self.AS.name].nextkey})
		else:
			nav = {}

		self.state = newstate

		debug.debugPrint('Dispatch', "New watchlist(Main): " + str(self.AS.HubInterestList))

		if OS != self.AS:
			try:
				self.AS.InitDisplay(nav)
			except Exception as e:
				logsupport.Logs.Log('Screen display error: ', self.AS.name, ' ', repr(e), severity=ConsoleError)

	# noinspection PyUnusedLocal
	def NavPress(self, NS, press):
		debug.debugPrint('Dispatch', 'Navkey: ', NS.name, self.state + '/' + self.dim)
		self.SwitchScreen(NS, 'Bright', 'NonHome', 'Nav Press')

	def MainControlLoop(self, InitScreen):

		config.sysStore.ErrorNotice = -1  # don't pester for errors during startup

		threadmanager.StartThreads()
		config.sysStore.LogStartTime = time.time()  # MQTT will start tracking other console errors now
		# so we can start broadcasting our errors
		logsupport.LocalOnly = False

		self.ScreensDict = screens.SecondaryDict.copy()
		self.ScreensDict.update(screens.MainDict)

		for a in alerttasks.AlertItems.AlertsList.values():
			a.state = 'Armed'
			logsupport.Logs.Log("Arming " + a.type + " alert " + a.name)
			logsupport.Logs.Log("->" + str(a), severity=ConsoleDetail)

			if a.type == 'Periodic':
				self.Tasks.AddTask(AlertEventItem(id(a), a.name, a), a.trigger.NextInterval())
			elif a.type == 'NodeChange':
				a.trigger.node.Hub.SetAlertWatch(a.trigger.node, a)
				if a.trigger.IsTrue():
					# noinspection PyArgumentList
					PostControl(ISYAlert, hub='DS-NodeChange', alert=a)
			elif a.type == 'VarChange':
				a.state = 'Init'
				# Note: VarChange alerts don't need setup because the store has an alert proc
				pass
			elif a.type == 'Init':
				a.Invoke()
			else:
				logsupport.Logs.Log("Internal error - unknown alert type: ", a.type, ' for ', a.name, severity=ConsoleError, tb=False)

		logsupport.Logs.livelog = False  # turn off logging to the screen

		with open(config.homedir + "/.ConsoleStart", "a") as f:
			f.write(str(time.time()) + '\n')
		if config.Running:  # allow for a very early restart request from things like autoversion
			self.SwitchScreen(InitScreen, 'Bright', 'Home', 'Startup')

		statusperiod = time.time()
		cyclehistory = deque((-1, -1, -1, -1, -1, -1, -1, -1, -1, -1))
		prevstatus = ''

		while config.Running:  # Operational Control Loop
			nowtime = time.time()
			if statusperiod <= nowtime or prevstatus != config.consolestatus:
				ReportStatus(config.consolestatus)
				prevstatus = config.consolestatus
				statusperiod = nowtime + 60

			if not threadmanager.Watcher.is_alive():
				logsupport.Logs.Log("Threadmanager Failure", severity=ConsoleError,tb=False)
				exitutils.Exit(exitutils.ERRORRESTART)

			os.utime(config.homedir + "/.ConsoleStart", None)
			if debug.dbgStore.GetVal('StatesDump'):
				debug.dbgStore.SetVal('StatesDump',False)
				for h, hub in config.Hubs.items():
					print('States dump for hub: ',h)
					hub.StatesDump()

			if self.Deferrals:  # an event was deferred mid screen touches - handle now
				event = self.Deferrals.pop(0)
				debug.debugPrint('EventList', 'Deferred Event Pop', event)
			elif debug.dbgStore.GetVal('QDump'):
				events = pygame.fastevent.get()
				if events:
					debug.debugPrint('QDump', 'Time: ', time.time())
					for e in events:
						self.Deferrals.append(e)
						debug.debugPrint('QDump', e, e.type)
					else:
						debug.debugPrint('QDump', "Empty queue")
						time.sleep(0.01)
				event = pygame.event.Event(NOEVENT, dict={})
			else:
				event = pygame.fastevent.wait()  # wait for the next event: touches, timeouts, ISY changes on note

			cyclehistory.pop()
			cyclehistory.appendleft((nowtime%1000, event.type))

			if event.type == pygame.MOUSEBUTTONDOWN:
				self.HBEvents.Entry('MouseDown' + str(event.pos))
				debug.debugPrint('Touch', 'MouseDown' + str(event.pos) + repr(event))
				# screen touch events; this includes touches to non-sensitive area of screen
				self.SetActivityTimer(self.AS.DimTO, 'Screen touch')
				# refresh non-dimming in all cases including non=sensitive areas
				# this refresh is redundant in some cases where the touch causes other activities

				if self.dim == 'Dim':
					# wake up the screen and if in a cover state go home
					config.consolestatus = 'active'
					if self.state == 'Cover':
						self.SwitchScreen(screens.HomeScreen, 'Bright', 'Home', 'Wake up from cover')
					else:
						self.Brighten()  # if any other screen just brighten
					continue  # wakeup touches are otherwise ignored

				# Screen was not Dim so the touch was meaningful
				pos = event.pos
				tapcount = 1
				pygame.time.delay(config.sysStore.MultiTapTime)
				while True:
					eventx = pygame.fastevent.poll()
					if eventx.type == pygame.MOUSEBUTTONDOWN:
						self.HBEvents.Entry('Follow MouseDown' + str(event.pos))<commit_msg>Code to try to isolate some timing bugs<commit_after>import os
import sys
import time
from collections import OrderedDict, deque

import exitutils
import alerttasks
import config
import debug
import hw
import logsupport
import screens.__screens as screens
import threadmanager
from controlevents import *
from eventlist import AlertEventItem, ProcEventItem
from eventlist import EventList
from logsupport import ConsoleWarning, ConsoleError, ConsoleDetail, ReportStatus
import historybuffer
import maintscreen


class DisplayScreen(object):
	def __init__(self):

		debug.debugPrint("Main", "Screensize: ", hw.screenwidth, hw.screenheight)
		logsupport.Logs.Log("Screensize: " + str(hw.screenwidth) + " x " + str(hw.screenheight))
		logsupport.Logs.Log(
			"Scaling ratio: " + "{0:.2f} W ".format(hw.dispratioW) + "{0:.2f} H".format(hw.dispratioH))

		self.dim = 'Bright'  # either Bright or Dim (or '' for don't change when a parameter
		self.state = 'Home'  # one of Home, NonHome, Maint, Cover, Alert

		# Central Task List
		self.Tasks = EventList()
		self.Deferrals = []
		self.WatchVarVals = {}  # most recent reported watched variable values

		self.AS = None  # Active Screen
		self.ScreensDict = {}  # all the sceens by name for setting navigation keys
		self.Chain = 0  # which screen chain is active 0: Main chain 1: Secondary Chain
		self.HBScreens = historybuffer.HistoryBuffer(20, 'Screens')
		self.HBEvents = historybuffer.HistoryBuffer(80, 'Events')

	def Dim(self):
		self.dim = 'Dim'
		hw.GoDim(int(config.sysStore.DimLevel))

	def Brighten(self):
		self.dim = 'Bright'
		hw.GoBright(int(config.sysStore.BrightLevel))

	@staticmethod
	def SetActivityTimer(timeinsecs, dbgmsg):
		pygame.time.set_timer(ACTIVITYTIMER, timeinsecs * 1000)
		debug.debugPrint('Dispatch', 'Set activity timer: ', timeinsecs, ' ', dbgmsg)

	def SwitchScreen(self, NS, newdim, newstate, reason, NavKeys=True):
		ASname = '*None*' if self.AS is None else self.AS.name
		self.HBScreens.Entry(
			NS.name + ' was ' + ASname + ' dim: ' + str(newdim) + ' state: ' + str(newstate) + ' reason: ' + str(
				reason))
		# config.sysStore.SetVal(['CurrentScreen'],NS.name) #todo temp
		config.sysStore.CurrentScreen = NS.name
		oldstate = self.state
		olddim = self.dim
		if NS == screens.HomeScreen:  # always force home state on move to actual home screen
			newstate = 'Home'
		if NS == self.AS:
			debug.debugPrint('Dispatch', 'Null SwitchScreen: ', reason)
			logsupport.Logs.Log('Null switchscreen: ' + reason, hb=True)
		if self.AS is not None and self.AS != NS:
			debug.debugPrint('Dispatch', "Switch from: ", self.AS.name, " to ", NS.name, "Nav=", NavKeys, ' State=',
					   oldstate + '/' + newstate + ':' + olddim + '/' + newdim, ' ', reason)
			self.AS.ExitScreen()
		OS = self.AS
		self.AS = NS
		if newdim == 'Dim':
			self.Dim()
			if olddim == 'Dim':
				if newstate == 'Cover':
					# special case persist
					self.SetActivityTimer(screens.DimIdleTimes[0], reason + ' using cover time')
				else:
					self.SetActivityTimer(self.AS.PersistTO, reason)
			else:
				self.SetActivityTimer(self.AS.DimTO, reason)

		elif newdim == 'Bright':
			self.Brighten()
			self.SetActivityTimer(self.AS.DimTO, reason)
		else:
			pass  # leave dim as it

		if NavKeys:
			nav = OrderedDict(
				{'prevkey': self.ScreensDict[self.AS.name].prevkey, 'nextkey': self.ScreensDict[self.AS.name].nextkey})
		else:
			nav = {}

		self.state = newstate

		debug.debugPrint('Dispatch', "New watchlist(Main): " + str(self.AS.HubInterestList))

		if OS != self.AS:
			try:
				self.AS.InitDisplay(nav)
			except Exception as e:
				logsupport.Logs.Log('Screen display error: ', self.AS.name, ' ', repr(e), severity=ConsoleError)

	# noinspection PyUnusedLocal
	def NavPress(self, NS, press):
		debug.debugPrint('Dispatch', 'Navkey: ', NS.name, self.state + '/' + self.dim)
		self.SwitchScreen(NS, 'Bright', 'NonHome', 'Nav Press')

	def MainControlLoop(self, InitScreen):

		config.sysStore.ErrorNotice = -1  # don't pester for errors during startup

		threadmanager.StartThreads()
		config.sysStore.LogStartTime = time.time()  # MQTT will start tracking other console errors now
		# so we can start broadcasting our errors
		logsupport.LocalOnly = False

		self.ScreensDict = screens.SecondaryDict.copy()
		self.ScreensDict.update(screens.MainDict)

		for a in alerttasks.AlertItems.AlertsList.values():
			a.state = 'Armed'
			logsupport.Logs.Log("Arming " + a.type + " alert " + a.name)
			logsupport.Logs.Log("->" + str(a), severity=ConsoleDetail)

			if a.type == 'Periodic':
				self.Tasks.AddTask(AlertEventItem(id(a), a.name, a), a.trigger.NextInterval())
			elif a.type == 'NodeChange':
				a.trigger.node.Hub.SetAlertWatch(a.trigger.node, a)
				if a.trigger.IsTrue():
					# noinspection PyArgumentList
					PostControl(ISYAlert, hub='DS-NodeChange', alert=a)
			elif a.type == 'VarChange':
				a.state = 'Init'
				# Note: VarChange alerts don't need setup because the store has an alert proc
				pass
			elif a.type == 'Init':
				a.Invoke()
			else:
				logsupport.Logs.Log("Internal error - unknown alert type: ", a.type, ' for ', a.name, severity=ConsoleError, tb=False)

		logsupport.Logs.livelog = False  # turn off logging to the screen

		with open(config.homedir + "/.ConsoleStart", "a") as f:
			f.write(str(time.time()) + '\n')
		if config.Running:  # allow for a very early restart request from things like autoversion
			self.SwitchScreen(InitScreen, 'Bright', 'Home', 'Startup')

		statusperiod = time.time()
		cyclehistory = deque((-1, -1, -1, -1, -1, -1, -1, -1, -1, -1))
		prevstatus = ''

		while config.Running:  # Operational Control Loop
			nowtime = time.time()
			if statusperiod <= nowtime or prevstatus != config.consolestatus:
				ReportStatus(config.consolestatus)
				prevstatus = config.consolestatus
				statusperiod = nowtime + 60
			nowtime2 = time.time()

			if not threadmanager.Watcher.is_alive():
				logsupport.Logs.Log("Threadmanager Failure", severity=ConsoleError,tb=False)
				exitutils.Exit(exitutils.ERRORRESTART)

			os.utime(config.homedir + "/.ConsoleStart", None)
			if debug.dbgStore.GetVal('StatesDump'):
				debug.dbgStore.SetVal('StatesDump',False)
				for h, hub in config.Hubs.items():
					print('States dump for hub: ',h)
					hub.StatesDump()

			if self.Deferrals:  # an event was deferred mid screen touches - handle now
				event = self.Deferrals.pop(0)
				debug.debugPrint('EventList', 'Deferred Event Pop', event)
			elif debug.dbgStore.GetVal('QDump'):
				events = pygame.fastevent.get()
				if events:
					debug.debugPrint('QDump', 'Time: ', time.time())
					for e in events:
						self.Deferrals.append(e)
						debug.debugPrint('QDump', e, e.type)
					else:
						debug.debugPrint('QDump', "Empty queue")
						time.sleep(0.01)
				event = pygame.event.Event(NOEVENT, dict={})
			else:
				event = pygame.fastevent.wait()  # wait for the next event: touches, timeouts, ISY changes on note

			nowtime3 = time.time()
			cyclehistory.pop()
			cyclehistory.appendleft((nowtime%1000, nowtime2%1000, nowtime3%1000, event.type))

			if event.type == pygame.MOUSEBUTTONDOWN:
				self.HBEvents.Entry('MouseDown' + str(event.pos))
				debug.debugPrint('Touch', 'MouseDown' + str(event.pos) + repr(event))
				# screen touch events; this includes touches to non-sensitive area of screen
				self.SetActivityTimer(self.AS.DimTO, 'Screen touch')
				# refresh non-dimming in all cases including non=sensitive areas
				# this refresh is redundant in some cases where the touch causes other activities

				if self.dim == 'Dim':
					# wake up the screen and if in a cover state go home
					config.consolestatus = 'active'
					if self.state == 'Cover':
						self.SwitchScreen(screens.HomeScreen, 'Bright', 'Home', 'Wake up from cover')
					else:
						self.Brighten()  # if any other screen just brighten
					continue  # wakeup touches are otherwise ignored

				# Screen was not Dim so the touch was meaningful
				pos = event.pos
				tapcount = 1
				pygame.time.delay(config.sysStore.MultiTapTime)
				while True:
					eventx = pygame.fastevent.poll()
					if eventx.type == pygame.MOUSEBUTTONDOWN:
						self.HBEvents.Entry('Follow MouseDown' + str(event.pos))
{'fraction_non_alphanumeric': 0.12905993381541855, 'fraction_numerical': 0.00539281774727295, 'mean_word_length': 3.74941792782305, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1151218', 'n_tokens_mistral': 6261, 'n_tokens_neox': 5327, 'n_words': 1398}