More Clever Night Light in Kivy
FleetingFollowing more clever night light, but using my a python runtime on android, so that I can use it when travelling. I also needed it a day when my sd card burned and I did not have a fiber connection to build a new raspberry pi image easily.
Using the click sound from click.ogg
import os
from datetime import datetime, timedelta
import math
import time
import requests
from subprocess import call
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
import requests
from kivy.properties import BooleanProperty, NumericProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from plyer import wifi
from android.runnable import run_on_ui_thread
import subprocess
from kivy.uix.spinner import Spinner
from plyer import orientation
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.core.audio import SoundLoader
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
from kivy.uix.screenmanager import Screen, ScreenManager, ScreenManagerException
from kivy.core.audio import SoundLoader
from jnius import autoclass
from helpers.osc import oschandler, to_service
from helpers.wakelock import WakeLock
from helpers.notify import notify, show_toast
from android.runnable import run_on_ui_thread
from logging import getLogger
from kivy.lang import Builder
from kivy.uix.togglebutton import ToggleButton
Context = autoclass('android.content.Context')
AlarmManager = autoclass('android.app.AlarmManager')
PendingIntent = autoclass('android.app.PendingIntent')
Intent = autoclass('android.content.Intent')
SystemClock = autoclass('android.os.SystemClock')
PythonActivity = autoclass('org.kivy.android.PythonActivity')
Build_VERSION = autoclass('android.os.Build$VERSION')
api_level = Build_VERSION.SDK_INT
logger = getLogger(__name__)
pink = (1, 0.75, 0.8)
orange = (1, 0.5, 0)
cyan = (0, 1, 1)
white = (1, 1, 1)
red = (1, 0, 0)
black = (0, 0, 0)
green = (0, 1, 0)
school_schedule = {
"almost_hour": 6,
"almost_minute": 30,
"getup_hour": 7,
"getup_minute": 0,
"day_hour": 7,
"day_minute": 40,
"evening_hour": 20,
"evening_minute": 0,
"night_hour": 20,
"night_minute": 30,
}
holiday_schedule = {
"almost_hour": 7,
"almost_minute": 30,
"getup_hour": 8,
"getup_minute": 0,
"day_hour": 8,
"day_minute": 40,
"evening_hour": 20,
"evening_minute": 0,
"night_hour": 20,
"night_minute": 30,
}
force_today = None
def ease_debugging(schedule, today=None, duration=30, start_time="night"):
now = datetime.now()
global force_today
force_today = today
start_assoc = {"night": 0, "almost": 1, "getup": 2, "day": 3, "evening": 4,
"tomorrow": 5,}
schedule["almost_hour"] = (now + timedelta(minutes=duration*(1 - start_assoc[start_time]))).hour
schedule["almost_minute"] = (now + timedelta(minutes=duration*(1 - start_assoc[start_time]))).minute
schedule["getup_hour"] = (now + timedelta(minutes=duration*(2 - start_assoc[start_time]))).hour
schedule["getup_minute"] = (now + timedelta(minutes=duration*(2 - start_assoc[start_time]))).minute
schedule["day_hour"] = (now + timedelta(minutes=duration*(3 - start_assoc[start_time]))).hour
schedule["day_minute"] = (now + timedelta(minutes=duration*(3 - start_assoc[start_time]))).minute
schedule["evening_hour"] = (now + timedelta(minutes=duration*(4 - start_assoc[start_time]))).hour
schedule["evening_minute"] = (now + timedelta(minutes=duration*(4 - start_assoc[start_time]))).minute
schedule["night_hour"] = (now + timedelta(minutes=duration*(5 - start_assoc[start_time]))).hour
schedule["night_minute"] = (now + timedelta(minutes=duration*(5 - start_assoc[start_time]))).minute
#ease_debugging(holiday_schedule, duration=60)
#ease_debugging(school_schedule, duration=60)
@run_on_ui_thread
def fs2():
from jnius import autoclass, cast
# Get the current Android activity and window
PythonActivity = autoclass('org.kivy.android.PythonActivity')
activity = PythonActivity.mActivity
window = activity.getWindow()
# Get decor view
decorView = window.getDecorView()
# View constants for immersive mode
View = autoclass('android.view.View')
flags = (
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
)
# Apply flags
decorView.setSystemUiVisibility(flags)
# Also set window flag to keep fullscreen
LayoutParams = autoclass('android.view.WindowManager$LayoutParams')
window.addFlags(LayoutParams.FLAG_FULLSCREEN)
def su(cmd):
logger.debug("su: {}".format(cmd))
call(["su", "-c", cmd])
def set_wifi_enabled(enable=True):
if api_level >= 26:
# on recent android, we cannot deal with the wifi anymore, I will
# therefore rely on su and for sake of simplicity enable airplane mode
# all together
su("settings put global airplane_mode_on {}".format("0" if enable else "1"))
su("am broadcast -a android.intent.action.AIRPLANE_MODE --ez state {}".format("false" if enable else "true"))
else:
# this works with android 4 and 5. I only need to play with the wifi
# because all other services are generally already off on such legacy
# devices
from android import mActivity
Context = autoclass('android.content.Context')
WifiManager = autoclass('android.net.wifi.WifiManager')
wifi_service = mActivity.getSystemService(Context.WIFI_SERVICE)
wifi_service.setWifiEnabled(enable)
wifi_service.reconnect()
def wake_n_notify(message, sleep=True):
set_wifi_enabled(True)
now = datetime.now()
message = "{:02}:{:02}:{:02}: {}".format(now.hour, now.minute, now.second, message)
logger.debug(message)
show_toast(message)
for _ in range(3):
if notify(message, channel="nightlight"):
show_toast("notification sent: {}".format(message), long=True)
# also sync the date until I get back internet (and ntp) working
try:
if os.path.exists("/system/bin/toybox"):
resp = requests.get("http://home/dateserver/toybox")
if resp.status_code // 100 != 2:
raise NotImplementedError()
su("toybox date {}".format(resp.text))
elif os.path.exists("/system/bin/toolbox"):
# heuristic. On the device that accepts another format, which is not there
if os.path.exists("/system/xbin/which"):
resp = requests.get("http://home/dateserver/toolbox1")
if resp.status_code // 100 != 2:
raise NotImplementedError()
su("toolbox date {}".format(resp.text))
else:
resp = requests.get("http://home/dateserver/toolbox2")
if resp.status_code // 100 != 2:
raise NotImplementedError()
su("toolbox date -s {}".format(resp.text))
else:
raise NotImplementedError()
except Exception as e:
show_toast("Failed to update the date...: {}".format(e))
break
else:
seconds = 5
show_toast("retrying in {} seconds".format(seconds))
time.sleep(seconds)
if sleep:
pass
set_wifi_enabled(False)
class SettingsScreen(Screen):
steps = NumericProperty(4)
step_time = NumericProperty(20)
rest_time = NumericProperty(10)
total_time = StringProperty("Total: 0s")
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def get_current_schedule(self):
if self.holidays.state == "down":
return holiday_schedule
else:
return school_schedule
def trigger_ease_debugging(self, *_):
"""Trigger ease_debugging on the currently selected schedule"""
if self.debug_button.state == "normal":
logger.info("Debug mode is off, not triggering ease_debugging")
return
ease_debugging(school_schedule, duration=int(self.debug_number.text))
ease_debugging(holiday_schedule, duration=int(self.debug_number.text))
logger.info("Triggered ease_debugging on current schedule")
# Update the UI to reflect the new schedule values
self.setup_times()
def create_time_picker(self, layout, label_text, hour_key, minute_key):
"""Create a time picker layout with hour and minute spinners"""
time_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(time_layout)
time_layout.add_widget(Label(text=label_text, color=(0, 0, 0, 1)))
# Hour spinner
hour_spinner = Spinner(
text="0", # Temporary value, will be set by setup_times
values=[str(i) for i in range(0, 24)],
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(hour_spinner, hour_key)
time_layout.ad
def bind_spinner(self, spinner, key):
def update_schedule(instance, value):
current_schedule = self.get_current_schedule()
current_schedule[key] = int(value)
# logger.info("Updated {} to {} in global schedule".format(key, value))
spinner.bind(text=update_schedule)
def __init__(self, **kwargs):
wake_n_notify("Updating the date", sleep=False)
super(SettingsScreen, self).__init__(**kwargs)
with self.canvas.before:
self.color = Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self._update_rect, pos=self._update_rect)
layout = BoxLayout(orientation='vertical')
self.add_widget(layout)
self.debug_button_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(self.debug_button_layout)
self.debug_button = ToggleButton(
text='Debug Mode',
size_hint=(1, 1),
background_color=(1, 0.5, 0, 1),
pos_hint={'center_x': .5, 'center_y': .5},
color=(1, 1, 1, 1),
)
self.debug_button.bind(on_press=self.trigger_ease_debugging)
self.debug_button_layout.add_widget(self.debug_button)
self.debug_number = Spinner(
text="1",
values=[str(i) for i in range(0, 61)], # numbers 0 to 60
size_hint=(1, 1),
pos_hint={'center_x': .5, 'center_y': .5},
)
self.debug_number.bind(text=self.trigger_ease_debugging)
self.debug_button_layout.add_widget(self.debug_number)
self.holidays = ToggleButton(text='Holidays', size_hint=(1, 0.15))
layout.add_widget(self.holidays)
self.holidays.bind(on_press=self.setup_times)
almost_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(almost_layout)
almost_layout.add_widget(
Label(text="Almost", color=(0, 0, 0, 1))
)
self.almost_hour = Spinner(
text="7",
values=[str(i) for i in range(0, 24)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.almost_hour, "almost_hour")
almost_layout.add_widget(self.almost_hour)
self.almost_minute = Spinner(
text='30',
values=[str(i) for i in range(0, 60)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.almost_minute, "almost_minute")
almost_layout.add_widget(self.almost_minute)
getup_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(getup_layout)
getup_layout.add_widget(
Label(text="getup", color=(0, 0, 0, 1))
)
self.getup_hour = Spinner(
text="8",
values=[str(i) for i in range(0, 24)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.getup_hour, "getup_hour")
getup_layout.add_widget(self.getup_hour)
self.getup_minute = Spinner(
text='0',
values=[str(i) for i in range(0, 60)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.getup_minute, "getup_minute")
getup_layout.add_widget(self.getup_minute)
day_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(day_layout)
day_layout.add_widget(
Label(text="day", color=(0, 0, 0, 1))
)
self.day_hour = Spinner(
text="8",
values=[str(i) for i in range(0, 24)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.day_hour, "day_hour")
day_layout.add_widget(self.day_hour)
self.day_minute = Spinner(
text='40',
values=[str(i) for i in range(0, 60)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.day_minute, "day_minute")
day_layout.add_widget(self.day_minute)
evening_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(evening_layout)
evening_layout.add_widget(
Label(text="evening", color=(0, 0, 0, 1))
)
self.evening_hour = Spinner(
text="20",
values=[str(i) for i in range(0, 24)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.evening_hour, "evening_hour")
evening_layout.add_widget(self.evening_hour)
self.evening_minute = Spinner(
text='0',
values=[str(i) for i in range(0, 60)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.evening_minute, "evening_minute")
evening_layout.add_widget(self.evening_minute)
night_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(night_layout)
night_layout.add_widget(
Label(text="night", color=(0, 0, 0, 1))
)
self.night_hour = Spinner(
text="8",
values=[str(i) for i in range(0, 24)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.night_hour, "night_hour")
night_layout.add_widget(self.night_hour)
self.night_minute = Spinner(
text='0',
values=[str(i) for i in range(0, 60)], # numbers 0 to 100
size_hint=(None, None),
size=(200, 44),
pos_hint={'center_x': .5, 'center_y': .5}
)
self.bind_spinner(self.night_minute, "night_minute")
night_layout.add_widget(self.night_minute)
self.setup_times()
self.go_button = Button(text="Go !!!",
background_color=(0, 0, 1, 1),
color=(1, 1, 1, 1),
size_hint=(1, 0.15), disabled=True)
self.go_button.bind(on_press=self.start)
layout.add_widget(self.go_button)
@oschandler("service:ready")
def _(_):
self.go_button.disabled = False
def setup_times(self, instance=None):
if self.holidays.state == "down":
logger.info("Using holidays schedule {}".format(self.holidays.state))
schedule = holiday_schedule
else:
logger.info("Using school schedule")
schedule = school_schedule
self.almost_hour.text = str(schedule["almost_hour"])
self.almost_minute.text = str(schedule["almost_minute"])
self.getup_hour.text = str(schedule["getup_hour"])
self.getup_minute.text = str(schedule["getup_minute"])
self.day_hour.text = str(schedule["day_hour"])
self.day_minute.text = str(schedule["day_minute"])
self.evening_hour.text = str(schedule["evening_hour"])
self.evening_minute.text = str(schedule["evening_minute"])
self.night_hour.text = str(schedule["night_hour"])
self.night_minute.text = str(schedule["night_minute"])
def start(self, *_):
app = App.get_running_app()
normal = app.sm.get_screen('nightlight:normal')
normal.debug = self.debug_button.state == "down"
normal.debug_number = int(self.debug_number.text)
normal.holidays = self.holidays.state == "down"
app.goto("nightlight:normal")
normal.start()
class NormalScreen(Screen):
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def _get_time(self, value):
now = datetime.now()
today = force_today or now.weekday()
if self.holidays or today in [2, 5, 6]: # 2=Wednesday, 5=Saturday, 6=Sunday
schedule = holiday_schedule
logger.debug("the schedule of today is holiday")
else:
schedule = school_schedule
logger.debug("the schedule of today is school")
if now.replace(hour=schedule["night_hour"],
minute=schedule["night_minute"], second=0,) < now:
logger.debug("it is late, I should consider the schedule of tomorrow")
if self.holidays or today in [2-1, 5-1, 6-1]: # tomorrow is a day off
schedule = holiday_schedule
else:
schedule = school_schedule
if value == "almost_hour" and self.debug:
ease_debugging(schedule, duration=self.debug_number)
logger.debug("Today: {}, schedule[{}]: {}".format(today, value, schedule[value]))
return schedule[value]
@property
def almost_hour(self):
return self._get_time("almost_hour")
@property
def almost_minute(self):
return self._get_time("almost_minute")
@property
def getup_hour(self):
return self._get_time("getup_hour")
@property
def getup_minute(self):
return self._get_time("getup_minute")
@property
def day_hour(self):
return self._get_time("day_hour")
@property
def day_minute(self):
return self._get_time("day_minute")
@property
def evening_hour(self):
return self._get_time("evening_hour")
@property
def evening_minute(self):
return self._get_time("evening_minute")
@property
def night_hour(self):
return self._get_time("night_hour")
@property
def night_minute(self):
return self._get_time("night_minute")
def __init__(self, **kwargs):
super(NormalScreen, self).__init__(**kwargs)
self.click = SoundLoader.load(os.path.join(os.path.dirname(__file__), "click.ogg"))
self.remaining_time = None
with self.canvas.before:
self.color = Color(0, 0, 0, 1)
self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self._update_rect, pos=self._update_rect)
layout = BoxLayout(orientation='vertical')
self.add_widget(layout)
current_layout = BoxLayout(size_hint=(1, 0.15))
layout.add_widget(current_layout)
self.current_hour = Label(
text="7",
font_size="115sp",
)
current_layout.add_widget(self.current_hour)
self.sep = Label(
text=":",
font_size="150sp",
)
current_layout.add_widget(self.sep)
self.current_minute = Label(
text='30',
font_size="115sp",
)
current_layout.add_widget(self.current_minute)
self.keepon = WakeLock("nightlight:keepon", wakeup=False, partial=True)
self.wakeup = WakeLock("nightlight:wakeup", wakeup=True, partial=False)
self.keepon.acquire()
self.update_display()
@oschandler("clockwake:done:nightlight:almost", answer=True)
def _(_):
app = App.get_running_app()
if app.sm.current == self.name:
Clock.schedule_once(self.almost)
else:
wake_n_notify("nightlight:almost, but not in the right screen")
@oschandler("clockwake:done:nightlight:getup", answer=True)
def _(_):
app = App.get_running_app()
if app.sm.current == self.name:
Clock.schedule_once(self.getup)
else:
wake_n_notify("nightlight:getup, but not in the right screen")
@oschandler("clockwake:done:nightlight:day", answer=True)
def _(_):
app = App.get_running_app()
if app.sm.current == self.name:
Clock.schedule_once(self.day)
else:
wake_n_notify("nightlight:day, but not in the right screen")
@oschandler("clockwake:done:nightlight:evening", answer=True)
def _(_):
app = App.get_running_app()
if app.sm.current == self.name:
Clock.schedule_once(self.evening)
else:
wake_n_notify("nightlight:evening, but not in the right screen")
@oschandler("clockwake:done:nightlight:night", answer=True)
def _(_):
app = App.get_running_app()
if app.sm.current == self.name:
Clock.schedule_once(self.night)
else:
wake_n_notify("nightlight:night, but not in the right screen")
self.bind(on_pre_enter=self.pre_enter_handler)
self.bind(on_pre_leave=self.pre_leave_handler)
def pre_leave_handler(self, _):
self.keepon.release()
self.wakeup.release()
from jnius import autoclass
from android import mActivity
View = autoclass('android.view.View')
option = View.SYSTEM_UI_FLAG_VISIBLE
from android.runnable import run_on_ui_thread
@run_on_ui_thread
def fs():
logger.debug("Exit fullscreen mode")
mActivity.getWindow().getDecorView().setSystemUiVisibility(option)
if not self.debug:
fs()
def pre_enter_handler(self, _):
# from plyer import orientation
# orientation.set_landscape()
from jnius import autoclass
from android import mActivity
View = autoclass('android.view.View')
option = View.SYSTEM_UI_FLAG_FULLSCREEN
@run_on_ui_thread
def fs():
logger.debug("Entering fullscreen mode")
mActivity.getWindow().getDecorView().setSystemUiVisibility(option)
if not self.debug:
if api_level >= 26:
fs2()
else:
fs()
def update_display(self, *_):
now = datetime.now()
self.current_hour.text = str(now.hour).zfill(2)
self.current_minute.text = str(now.minute).zfill(2)
Clock.schedule_once(self.update_display, 61 - now.second)
def durationuntil(self, hour, minute):
now = datetime.now()
new_date = now.replace(hour=hour, minute=minute,second=0)
duration = (new_date - now).total_seconds()
if duration < 0:
new_date = new_date + timedelta(days=1)
duration = (new_date - now).total_seconds()
return duration
def start(self):
now = datetime.now()
almost = now.replace(hour=self.almost_hour, minute=self.almost_minute)
getup = now.replace(hour=self.getup_hour, minute=self.getup_minute)
day = now.replace(hour=self.day_hour, minute=self.day_minute)
evening = now.replace(hour=self.evening_hour, minute=self.evening_minute)
night = now.replace(hour=self.night_hour, minute=self.night_minute)
if now < almost:
self.night()
elif now < getup:
self.almost()
elif now < day:
self.getup()
elif now < evening:
self.day()
elif now < night:
self.evening()
else:
self.night()
def almost(self, *_):
self.color.rgb = orange
self.current_hour.color = (0,0,0, 1)
self.current_minute.color = (0,0,0, 1)
self.sep.color = (0,0,0, 1)
self.wakeup.acquire()
wake_n_notify("almost time, next at {:02}:{:02}".format(self.getup_hour, self.getup_minute))
to_service("clockwake", {"duration": self.durationuntil(self.getup_hour, self.getup_minute), "callback": "nightlight:getup"})
def getup(self, *_):
self.color.rgb = green
self.current_hour.color = (0,0,0, 1)
self.sep.color = (0,0,0, 1)
self.current_minute.color = (0,0,0, 1)
self.wakeup.acquire()
wake_n_notify("getup, next at {:02}:{:02}".format(self.day_hour, self.day_minute))
self.click.seek(0)
self.click.play()
to_service("clockwake", {"duration": self.durationuntil(self.day_hour, self.day_minute), "callback": "nightlight:day"})
def day(self, *_):
wake_n_notify("day, next at {:02}:{:02}".format(self.evening_hour, self.evening_minute))
self.wakeup.release()
self.current_hour.color = (0,0,0, 1)
self.sep.color = (0,0,0, 1)
self.current_minute.color = (0,0,0, 1)
self.color.rgb = white
to_service("clockwake", {"duration": self.durationuntil(self.evening_hour, self.evening_minute), "callback": "nightlight:evening"})
def evening(self, *_):
self.color.rgb = red
self.current_hour.color = (1,1,1, 1)
self.sep.color = (1,1,1, 1)
self.current_minute.color = (1,1,1, 1)
self.wakeup.acquire()
wake_n_notify("evening, next at {:02}:{:02}".format(self.night_hour, self.night_minute))
self.click.seek(0)
self.click.play()
to_service("clockwake", {"duration": self.durationuntil(self.night_hour, self.night_minute), "callback": "nightlight:night"})
def night(self, *_):
wake_n_notify("night, next at {:02}:{:02}".format(self.almost_hour, self.almost_minute))
self.wakeup.release()
self.color.rgb = black
to_service("clockwake", {"duration": self.durationuntil(self.almost_hour, self.almost_minute), "callback": "nightlight:almost"})
class NightLightApp(App):
def goto(self, screen):
self.sm.current = screen
def back(self):
self.sm.current = 'nightlight:normal'
@staticmethod
def populate(sm):
try:
sm.get_screen('nightlight:normal')
return
except ScreenManagerException:
pass # not populated yet
sm.add_widget(SettingsScreen(name='nightlight:settings'))
sm.add_widget(NormalScreen(name='nightlight:normal'))
def build(self):
self.sm = ScreenManager()
self.populate(self.sm)
return self.sm
def run():
NightLightApp().run()