Display-o-Tron Menu

i have modifed the example script for menu.py
i have successfully added menu options and able to use the options to reboot or shutdown the pi via a Display-o-Tron HAT, now im trying to add a menu option to control a gpio output
anyone out there who can help?

#!/usr/bin/env python

import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep 

# Add the root examples dir so Python can find the plugins
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

print("""
This advanced example uses the menu framework.
It gives you a basic menu setup with plugins. You should be able to view system info and adjust settings!

Press CTRL+C to exit.
""")
def Lights_on(MenuOption):
    import RPi.GPIO as GPIO
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(13,GPIO.OUT)
    for x in range (0,3):
        GPIO.output(13,True)
        print ("on")
        time.sleep(2)
        GPIO.output(13,False)
        print("off")
        time.sleep(2)
    
"""
Using a set of nested lists you can describe
the menu you want to display on dot3k.

Instances of classes derived from MenuOption can
be used as menu items to show information or change settings.

See GraphTemp, GraphCPU, Contrast and Backlight for examples.
"""

menu = Menu(
    structure={
        'Power Options': {
           'Reboot':GraphSysReboot(),
        'Shutdown':GraphSysShutdown(),
            },
        'Aquarium': {
            'Lighting': {
                'ON': Lights_on(MenuOption),
                }
            },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_timeout=30,
    input_handler=Text())

"""
You can use anything to control dot3k.menu,
but you'll probably want to use dot3k.touch
"""
nav.bind_defaults(menu)

while 1:
    menu.redraw()
    time.sleep(0.05)

You can use three backticks to format your code and preserve the indentation, like so:

```
code goes here
```

At the moment you’re creating a function with a single parameter of “MenuOption”, rather than a class that inherits MenuOption.

There’s a step-by-step guide to writing plugins, in the form of commented code examples here that might help get you on the right track: https://github.com/pimoroni/displayotron/tree/master/examples/plugins/writing_your_own

This is what i have so far but i get,
Traceback (most recent call last) :
File “/home/pi/Scripts/temp.py”, line 121 in
‘Control’:Lightcontrol(MenuOption),
TypeError: init() takes 1 positional argument but 2 were given

#!/usr/bin/env python

import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep 

# Add the root examples dir so Python can find the plugins
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

print("""
This advanced example uses the menu framework.
It gives you a basic menu setup with plugins. You should be able to view system info and adjust settings!

Press CTRL+C to exit.
""")

class Lightcontrol(MenuOption):
    """Control; Lights"""
    
def __init__(self):
    self.selected_option = 0
    self.options = [
        'On',
        'Off',
        ]
    self.actions = [
        self.handle_On,
        self.handle_Off,
]
    MenuOption.__init__(self)

def handle_On(self):
    print('Eeek! Doing monkey stuff!')
    time.sleep(2)
    print('Done monkey stuff!')

def handle_Off(self):
    print('Oook! Doing donkey stuff!')
    time.sleep(2)
    print('Done donkey stuff!')
def select_option(self):
    self.actions[self.selected_option]()

def next_option(self):
    self.selected_option = (self.selected_option + 1) % len(self.options)

def prev_option(self):
    self.selected_option = (self.selected_option - 1) % len(self.options)

def up(self):
    self.prev_option()

def down(self):
    self.next_option()



    def right(self):
        self.select_option()

    def get_current_option(self):
        return self.options[self.selected_option]

    def get_next_option(self):
        return self.options[(self.selected_option + 1) % len(self.options)]

    def get_prev_option(self):
        return self.options[(self.selected_option - 1) % len(self.options)]

    def redraw(self, menu):
        menu.write_option(
            row=0,
            margin=1,
            icon='',
            text=self.get_prev_option()
        )

        menu.write_option(
            row=1,
            margin=1,
            icon='>',  # Let's use a > to denote the currently selected option
            text=self.get_current_option()
        )

        menu.write_option(
            row=2,
            margin=1,
            icon='',
            text=self.get_next_option()
        )
    
    
"""
Using a set of nested lists you can describe
the menu you want to display on dot3k.

Instances of classes derived from MenuOption can
be used as menu items to show information or change settings.

See GraphTemp, GraphCPU, Contrast and Backlight for examples.
"""


menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': Lightcontrol(MenuOption),
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_timeout=30,
    input_handler=Text())

"""
You can use anything to control dot3k.menu,
but you'll probably want to use dot3k.touch
"""
nav.bind_defaults(menu)

while 1:
    menu.redraw()
    time.sleep(0.05)

You’re passing the “MenuOption” class as an argument into the init function:

'Control': Lightcontrol(MenuOption),

sorry i forgot to update here, i removed the (MenuOption) so it says Lightcontrol()
but now the on and off options arent showing under the control submenu


import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep 

# Add the root examples dir so Python can find the plugins
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

print("""
This advanced example uses the menu framework.
It gives you a basic menu setup with plugins. You should be able to view system info and adjust settings!

Press CTRL+C to exit.
""")

class Lightcontrol():
    """Control; Lights"""
    
def __init__(self):
    self.selected_option = 0
    self.options = [
        'On',
        'Off',
        ]
    self.actions = [
        self.handle_On,
        self.handle_Off,
]
    MenuOption.__init__(self)

def handle_On(self):
    print('Eeek! Doing monkey stuff!')
    time.sleep(2)
    print('Done monkey stuff!')

def handle_Off(self):
    print('Oook! Doing donkey stuff!')
    time.sleep(2)
    print('Done donkey stuff!')
def select_option(self):
    self.actions[self.selected_option]()

def next_option(self):
    self.selected_option = (self.selected_option + 1) % len(self.options)

def prev_option(self):
    self.selected_option = (self.selected_option - 1) % len(self.options)

def up(self):
    self.prev_option()

def down(self):
    self.next_option()



    def right(self):
        self.select_option()

    def get_current_option(self):
        return self.options[self.selected_option]

    def get_next_option(self):
        return self.options[(self.selected_option + 1) % len(self.options)]

    def get_prev_option(self):
        return self.options[(self.selected_option - 1) % len(self.options)]

    def redraw(self, menu):
        menu.write_option(
            row=0,
            margin=1,
            icon='',
            text=self.get_prev_option()
        )

        menu.write_option(
            row=1,
            margin=1,
            icon='>',  # Let's use a > to denote the currently selected option
            text=self.get_current_option()
        )

        menu.write_option(
            row=2,
            margin=1,
            icon='',
            text=self.get_next_option()
        )
    
    
"""
Using a set of nested lists you can describe
the menu you want to display on dot3k.

Instances of classes derived from MenuOption can
be used as menu items to show information or change settings.

See GraphTemp, GraphCPU, Contrast and Backlight for examples.
"""


menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': Lightcontrol(),
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_timeout=30,
    input_handler=Text())

"""
You can use anything to control dot3k.menu,
but you'll probably want to use dot3k.touch
"""
nav.bind_defaults(menu)

while 1:
    menu.redraw()
    time.sleep(0.05)```

If the above is a direct copy and paste of your code- it looks like you’ve got some indentation issues in some of the class methods of LightControl

i got it working, but when i select on the programs loops printing “lights on” so it appears to flash until i press select again and it takes it back to on or off

#!/usr/bin/env python
 
import sys
import time
 
import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep 
 
# Add the root examples dir so Python can find the plugins
sys.path.append('/home/pi/Pimoroni/displayotron/examples')
 
from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast
 
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)


 
print("""
Script Running:
Press CTRL+C to exit.
""")
 
class LightOn(MenuOption):
    """Control; Lights"""
    def __init__(self):
        self.last = self.millis()
        MenuOption.__init__(self)

    def redraw(self, menu):
        menu.clear_row(0)
        menu.clear_row(1)
        menu.clear_row(2)
        GPIO.output(13,0)
        GPIO.output(5,0)
        GPIO.output(13,1)
        menu.write_row(1,"Lights On")
        return
        
class LightOff(MenuOption):
    """Control; Lights"""
    def __init__(self):
        self.last = self.millis()
        MenuOption.__init__(self)

    def redraw(self, menu):
        menu.clear_row(0)
        menu.clear_row(1)
        menu.clear_row(2)
        GPIO.output(5,0)
        GPIO.output(13,0)
        GPIO.output(5,1)
        menu.write_row(1,"Lights Off")
        return       

LY = LightOn()
LN = LightOff()
 
menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': {
                        'On': LY,
                        'Off':LN,
                        }
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_handler=(LightOn,LightOff),
    idle_timeout=30,
    input_handler=Text())
 
nav.bind_defaults(menu)
 
while 1:
    menu.redraw()
    time.sleep(0.05)

Here’s the version with a light control class as mentioned on Discord:

import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep

# Add the root examples dir so Python can find the plugins
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)



print("""
Script Running:
Press CTRL+C to exit.
""")


suspend_draw = False

class Lights(MenuOption):
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        MenuOption.__init__(self)

    def redraw(self, menu):
        if self._mode == 0:
            menu.clear_row(0)
            menu.write_row(1, "Toggle Lights")
            menu.write_row(2, "Currently: {}".format("On" if self._status else "Off"))
        elif self._mode == 1:
            if self._timeout <= time.time():
                self._mode = 0
            menu.clear_row(0)
            menu.write_row(1, self._message)
            menu.clear_row(2)

    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1

    def lights_on(self):
        GPIO.output(13,0)
        GPIO.output(5,0)
        GPIO.output(13,1)
        self._status = True
        self.display_message("Lights: On", 1)

    def lights_off(self):
        GPIO.output(5,0)
        GPIO.output(13,0)
        GPIO.output(5,1)
        self._status = False
        self.display_message("Lights: Off", 1)

    def up(self):
        self.lights_on()

    def down(self):
        self.lights_off()

menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': Lights(),
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_handler=None,
    idle_timeout=30,
    input_handler=Text())

nav.bind_defaults(menu)

while 1:
    if not suspend_draw:
        menu.redraw()
    time.sleep(0.05)

this is where we’re at so far

import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime

sys.path.append ('/home/pi/.local/lib/python2.7/site-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast
from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)

a = Astral()
a.solar_depression = 'civil'
city = a['London']

class Lights(MenuOption):
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        self._auto = True
        self._sun_rise = None
        self._sun_set = None
        MenuOption.__init__(self)

    def get_status(self):
        return self._status

    def is_auto(self):
        return self._auto

    def set_times(self, sun_rise, sun_set):
        self._sun_rise = sun_rise
        self._sun_set = sun_set

    def redraw(self, menu):
        if self._mode == 0:
            menu.write_row(0, "Lights {}".format(str(datetime.datetime.now().time())[:8]))
            menu.write_row(1, "{} {}".format("[LIGHTS]" if self._status else " LIGHTS ", "[AUTO]" if self._auto else " AUTO "))
            menu.write_row(2, "R:{}  S:{}".format(str(self._sun_rise)[:-3], str(self._sun_set)[:-3]))
        elif self._mode == 1:
            if self._timeout <= time.time():
                self._mode = 0
            menu.clear_row(0)
            menu.write_row(1, self._message)
            menu.clear_row(2)

    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1

    def lights_on(self):
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("Lights: On", 1)

    def lights_off(self):
        GPIO.output(5,1)
        GPIO.output(13,0)
        time.sleep(1)
        GPIO.output(13,1)
        time.sleep(1)
        GPIO.output(13,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def auto_on(self):
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self):
        self._auto = False
        self.display_message("Auto: Off", 1)

    def right(self):
        self.auto_on()

    def left(self):
        self.auto_off()
        return True

    def up(self):
        self._auto = False
        self.lights_on()

    def down(self):
        self._auto = False
        self.lights_off()

lights = Lights()


menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights,
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_handler=lights,
    idle_time=2,
    input_handler=Text())

nav.bind_defaults(menu)

while True:
    menu.redraw()

    now = datetime.datetime.now()
    sun = city.sun(date=now.date(), local=True)
    sun_rise = sun['sunrise'].time()
    sun_set = sun['sunset'].time()
    
    lights.set_times(sun_rise, sun_set)

    if lights.is_auto() == True:
        if now >= sun_set or now<= sun_rise and lights.get_status() == False:
            lights.lights_off()
        elif now.time() >= sun_rise or now.time() <= sun_set and lights.get_status() == True:
            lights.lights_on()

    time.sleep(1.0 / 20)
import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime

sys.path.append ('/home/pi/.local/lib/python2.7/site-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import itertools

a = Astral()
a.solar_depression = 'civil'
city = a['London']

class Lights(MenuOption):
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        self._auto = True
        self._sun_rise = None
        self._sun_set = None
        MenuOption.__init__(self)

    def get_status(self):
        return self._status

    def is_auto(self):
        return self._auto

    def set_times(self, sun_rise, sun_set):
        self._sun_rise = sun_rise
        self._sun_set = sun_set

    def redraw(self, menu):
        if self._mode == 0:
            menu.write_row(0, "Lights {}".format(str(datetime.datetime.now().time())[:8]))
            menu.write_row(1, "{} {}".format("[LIGHTS]" if self._status else " LIGHTS ", "[AUTO]" if self._auto else " AUTO "))
            menu.write_row(2, "R:{}  S:{}".format(str(self._sun_rise)[:-3], str(self._sun_set)[:-3]))
        elif self._mode == 1:
            if self._timeout <= time.time():
                self._mode = 0
            menu.clear_row(0)
            menu.write_row(1, self._message)
            menu.clear_row(2)

    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1

    def daylights_on(self):
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self):
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self):
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self):
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_on(self):
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self):
        self._auto = False
        self.display_message("Auto: Off", 1)

    def right(self):
        self.auto_on()

    def left(self):
        self.auto_off()
        return True

    def up(self):
        self._auto = False
        self.light_toggle()

    def down(self):
        self._auto = False
        self.lights_off()

lights = Lights()


menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights,
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_handler=lights,
    idle_time=5,
    input_handler=Text())

nav.bind_defaults(menu)

while True:
    menu.redraw()

    now = datetime.datetime.now()
    sun = city.sun(date=now.date(), local=True)
    sun_rise = sun['sunrise'].time()
    sun_set = sun['sunset'].time()
    
    lights.set_times(sun_rise, sun_set)

    if lights.is_auto() == True:
        if now.time() >= sun_set or now.time()<= sun_rise and lights.get_status() == False:
            lights.lights_off()
        elif now.time() >= sun_rise or now.time() <= sun_set and lights.get_status() == True:
            lights.daylights_on()

    time.sleep(1.0 / 20)




import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime

sys.path.append (’/home/pi/.local/lib/python2.7/site-packages’)
sys.path.append(’/home/pi/Pimoroni/displayotron/examples’)

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule

a = Astral()
a.solar_depression = ‘civil’
city = a[‘London’]

class Lights(MenuOption):
def init(self):
self._mode = 0
self._message = ‘’
self._timeout = 0
self._status = False
self._auto = True
self._sun_rise = None
self._sun_set = None
MenuOption.init(self)

def get_status(self):
    return self._status

def is_auto(self):
    return self._auto

def set_times(self, sun_rise, sun_set):
    self._sun_rise = sun_rise
    self._sun_set = sun_set

def redraw(self, menu):
    if self._mode == 0:
        menu.write_row(0, "Lights {}".format(str(datetime.datetime.now().time())[:8]))
        menu.write_row(1, "{} {}".format("[LIGHTS]" if self._status else " LIGHTS ", "[AUTO]" if self._auto else " AUTO "))
        menu.write_row(2, "R:{}  S:{}".format(str(self._sun_rise)[:-3], str(self._sun_set)[:-3]))
    elif self._mode == 1:
        if self._timeout <= time.time():
            self._mode = 0
        menu.clear_row(0)
        menu.write_row(1, self._message)
        menu.clear_row(2)

def display_message(self, message, timeout=1):
    self._message = message
    self._timeout = time.time() + timeout
    self._mode = 1

def daylights_on(self):
    GPIO.output(13,0)
    GPIO.output(5,1)
    GPIO.output(13,1)
    self._status = True
    self.display_message("DayLights: On", 1)

def nightlights_on(self):
    GPIO.output(13,0)
    GPIO.output(5,1)
    GPIO.output(13,0)
    self._status = True
    self.display_message("NightLights: On", 1)


def lights_off(self):
    GPIO.output(5,0)
    self._status = False
    self.display_message("Lights: Off", 1)

def light_toggle(self):
    if (GPIO.input(13) == True):
        self.nightlights_on() 
    else:
        self.daylights_on()

def auto_on(self):
    self._auto = True
    self.display_message("Auto: On", 1)

def auto_off(self):
    self._auto = False
    self.display_message("Auto: Off", 1)

def right(self):
    self.auto_on()

def left(self):
    self.auto_off()
    return True

def up(self):
    self._auto = False
    self.light_toggle()

def down(self):
    self._auto = False
    self.lights_off()

lights = Lights()

menu = Menu(
structure={
‘Power Options’: {
‘Reboot’:GraphSysReboot(),
‘Shutdown’:GraphSysShutdown(),
},
‘Aquarium’: {
‘Lighting’: {
‘Control’: lights,
}
},
‘Clock’: Clock(backlight),
‘Status’: {
‘IP’: IPAddress(),
‘CPU’: GraphCPU(backlight),
‘Temp’: GraphTemp()
},
‘Settings’: {
‘Display’: {
‘Contrast’: Contrast(lcd),
‘Backlight’: Backlight(backlight)
}
}
},
lcd=lcd,
idle_handler=lights,
idle_time=5,
input_handler=Text())

nav.bind_defaults(menu)

while True:
menu.redraw()

now = datetime.datetime.now()
sun = city.sun(date=now.date(), local=True)
sun_rise = sun['sunrise'].time()
sun_set = sun['sunset'].time()
dateSTR = datetime.datetime.now().strftime("%H:%M:%S" )

lights.set_times(sun_rise, sun_set)


if lights.is_auto() == True:
    if dateSTR == ("23:18:00") == True:
        nightlights_on()
    if dateSTR == ("23:18:30") == True:
        daylights_on()

time.sleep(1.0 / 20)
import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime

sys.path.append ('/home/pi/.local/lib/python2.7/site-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule

a = Astral()
a.solar_depression = 'civil'
city = a['London']



class Lights(MenuOption):
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        self._auto = True
        self._sun_rise = None
        self._sun_set = None
        MenuOption.__init__(self)

    def get_status(self):
        return self._status

    def is_auto(self):
        return self._auto

    def set_times(self, sun_rise, sun_set):
        self._sun_rise = sun_rise
        self._sun_set = sun_set

    def redraw(self, menu):
        if self._mode == 0:
            menu.write_row(0, "Lights {}".format(str(datetime.datetime.now().time())[:8]))
            menu.write_row(1, "{} {}".format("[LIGHTS]" if self._status else " LIGHTS ", "[AUTO]" if self._auto else " AUTO "))
            menu.write_row(2, "R:{}  S:{}".format(str(self._sun_rise)[:-3], str(self._sun_set)[:-3]))
        elif self._mode == 1:
            if self._timeout <= time.time():
                self._mode = 0
            menu.clear_row(0)
            menu.write_row(1, self._message)
            menu.clear_row(2)

    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1

    def job(day):
        self.daylights_on

    def job(night):
        self.nightlights_on

    def job(off):
        self.lights_off

    def daylights_on(self): #Activates Daylights
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self): #Activates Nightlights
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self): # Deactivates any light mode
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self): # Toggles Between Day/Night Modes
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_on(self): # Sets an automatic timer to activate lights
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self): # Sets an automatic timer to dectivate lights
        self._auto = False
        self.display_message("Auto: Off", 1)

    def right(self):
        self.auto_on()

    def left(self):
        self.auto_off()
        return True

    def up(self):
        self._auto = False
        self.light_toggle()

    def down(self):
        self._auto = False
        self.lights_off()

lights = Lights()

#Unordered menu
menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights,
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_handler=lights,
    idle_time=5,
    input_handler=Text())

nav.bind_defaults(menu)

while True:
    menu.redraw()

    now = datetime.datetime.now()
    sun = city.sun(date=now.date(), local=True)
    sun_rise = sun['sunrise'].time()
    sun_set = sun['sunset'].time()
    dateSTR = datetime.datetime.now().strftime("%H:%M:%S" )
    
    lights.set_times(sun_rise, sun_set)
    

    if lights.is_auto() == True: # timer for setting for nightlights
        schedule.run_pending()
        schedule.every().day.at("23:06").do(lights.nightlights_on)
        schedule.every().day.at("23:07").do(lights.daylights_on)

    time.sleep(1.0 / 20)

I have no way to test this right now, but:

import sys
import time

import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime

sys.path.append ('/home/pi/.local/lib/python2.7/site-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')

from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast

from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule

a = Astral()
a.solar_depression = 'civil'
city = a['London']


class Lights(MenuOption):
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        self._auto = True
        self._sun_rise = None
        self._sun_set = None
        self._current_status = 'off'
        MenuOption.__init__(self)

    def get_status(self):
        return self._current_status

    def is_on(self):
        return self._status

    def is_auto(self):
        return self._auto

    def set_times(self, sun_rise, sun_set):
        self._sun_rise = sun_rise
        self._sun_set = sun_set

    def redraw(self, menu):
        if self._mode == 0:
            menu.write_row(0, "Lights {}".format(str(datetime.datetime.now().time())[:8]))
            menu.write_row(1, "{} {}".format("[LIGHTS]" if self._status else " LIGHTS ", "[AUTO]" if self._auto else " AUTO "))
            menu.write_row(2, "R:{}  S:{}".format(str(self._sun_rise)[:-3], str(self._sun_set)[:-3]))
        elif self._mode == 1:
            if self._timeout <= time.time():
                self._mode = 0
            menu.clear_row(0)
            menu.write_row(1, self._message)
            menu.clear_row(2)

    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1

    def job(day):
        self.daylights_on

    def job(night):
        self.nightlights_on

    def job(off):
        self.lights_off

    def daylights_on(self): #Activates Daylights
        self._current_status = 'day'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self): #Activates Nightlights
        self._current_status = 'night'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self): # Deactivates any light mode
        self._current_status = 'off'
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self): # Toggles Between Day/Night Modes
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_on(self): # Sets an automatic timer to activate lights
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self): # Sets an automatic timer to dectivate lights
        self._auto = False
        self.display_message("Auto: Off", 1)

    def right(self):
        self.auto_on()

    def left(self):
        self.auto_off()
        return True

    def up(self):
        self._auto = False
        self.light_toggle()

    def down(self):
        self._auto = False
        self.lights_off()

lights = Lights()

#Unordered menu
menu = Menu(
    structure={
            'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights,
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
            }
        }
    },
    lcd=lcd,
    idle_handler=lights,
    idle_time=5,
    input_handler=Text())

nav.bind_defaults(menu)

required_status = 'off'

def schedule_nightlights():
    global required_status
    required_status = 'night'
    return schedule.CancelJob

def schedule_daylights():
    global required_status
    required_status = 'day'
    return schedule.CancelJob

def schedule_off():
    global required_status
    required_status = 'off'
    return schedule.CancelJob

def schedule_update():
    now = datetime.datetime.now()
    sun = city.sun(date=now.date(), local=True)
    sun_rise = sun['sunrise'].time()
    sun_set = sun['sunset'].time()
    dateSTR = datetime.datetime.now().strftime("%H:%M:%S" )
    
    lights.set_times(sun_rise, sun_set)

    schedule.clear('lights') # Cancel all pending sunrise/sunset jobs
    
    # With our newly calculated times, schedule the jobs
    schedule.every().day.at("23:06").do(schedule_nightlights).tag('lights')
    schedule.every().day.at("23:07").do(schedule_daylights).tag('lights')
    schedule.every().day.at("00:00").do(schedule_off).tag('lights')


# Update the schedule with the latest sunrise/sunset times
schedule.every().day.at("00:00").do(schedule_update).tag('update')

# Call schedule update once at startup to force set the initial rise/set times
schedule_update()

while True:
    menu.redraw()
    
    schedule.run_pending()

    if lights.is_auto():
        if lights.get_status() != required_status:
            if required_status == 'day':
                lights.daylights_on()
            if required_status == 'night':
                lights.nightlights_on()
            if required_status == 'off':
                lights.off()

    time.sleep(1.0 / 20)


trying to edit this example but i cant work out how to make an adjustable time setting, for example i want to navigate to dayset menu and press up or down to change the time setting and left or right to change from hours to minutes etc.

import sys
sys.path.append ('/usr/local/lib/python2.7/dist-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')
sys.path.append('/home/pi/.local/lib/python2.7/site-packages')
import time
import subprocess
import threading
import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime
from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast
from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule

class lights(MenuOption):
    
    def __init__(self):
        self.selected_option = 0

        self.options = [
          {'title': 'DayLights', 'action': self.daylights_on,   'icon': ' '},
          {'title': 'NightLights', 'action': self.nightlights_on,   'icon': ' '},
          {'title': 'LightsAuto', 'action': self.auto_on,    'icon': ' '},
          {'title': 'Dayset', 'action': self.dayset,    'icon': ' '},
          {'title': 'Nightset', 'action': self.nightset,  'icon': ' '},
          {'title': 'Offset', 'action': format(str(datetime.datetime.now().time())),  'icon': ' '},

        ]

        MenuOption.__init__(self)

    def dayset(self):
        print("nothing")

    def nightset(self):
        print("nothing")
        
    def offset(self):
        print("nothing")
        
    def daylights_on(self): #Activates Daylights
        self._current_status = 'day'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self): #Activates Nightlights
        self._current_status = 'night'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self): # Deactivates any light mode
        self._current_status = 'off'
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self): # Toggles Between Day/Night Modes
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_on(self): # Sets an automatic timer to activate lights
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self): # Sets an automatic timer to dectivate lights
        self._auto = False
        self.display_message("Auto: Off", 1)

    def select_option(self):
        self.actions[ self.selected_option ]['action']()

    def next_option(self):
        self.selected_option = (self.selected_option + 1) % len(self.options)

    def prev_option(self):
        self.selected_option = (self.selected_option - 1) % len(self.options)

    def up(self):
        self.prev_option()

    def down(self):
        self.next_option()

    def get_current_icon(self):
        return self.options[ self.selected_option ]['icon']
        
    def get_next_icon(self):
        return self.options[ (self.selected_option + 1) % len(self.options) ]['icon']

    def get_prev_icon(self):
        return self.options[ (self.selected_option - 1) % len(self.options) ]['icon']

    def redraw(self, menu):
        menu.write_option(
            row=0,
            margin=1,
            icon=self.get_prev_icon(),
            text=self.get_prev_option()
        )
      
        menu.write_option( 
            row=1,
            margin=1,
            icon=self.get_current_icon() or '>',
            text=self.get_current_option()
        )
      
        menu.write_option( 
            row=2,
            margin=1,
            icon=self.get_next_icon(),
            text=self.get_next_option()
        )

 
menu = Menu(
 structure={
    'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights,
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
                }
            }
    },
  lcd=lcd,
)

@nav.on(nav.UP)
def handle_up(pin):
  menu.up()

@nav.on(nav.DOWN)
def handle_down(pin):
  menu.down()


"""
We need to handle right, to let us select options
"""
@nav.on(nav.RIGHT)
def handle_right(pin):
  menu.right()

@nav.on(nav.LEFT)
def handle_LEFT(pin):
  menu.left()


"""
You can decide when the menu is redrawn, but
you'll usually want to do this:
"""
while 1:
  menu.redraw()
time.sleep(0.01)
==================== RESTART: /home/pi/Scripts/New/1.8.py ====================
Traceback (most recent call last):
  File "/home/pi/Scripts/New/1.8.py", line 200, in <module>
    menu.redraw()
  File "/usr/local/lib/python3.5/dist-packages/dot3k/menu.py", line 476, in redraw
    self.current_value().redraw(self)
  File "/home/pi/Scripts/New/1.8.py", line 126, in redraw
    text=self.get_prev_option()
  File "/usr/local/lib/python3.5/dist-packages/dot3k/menu.py", line 431, in write_option
    current_row += text
TypeError: Can't convert 'dict' object to str implicitly
>>> 
import sys
sys.path.append ('/usr/local/lib/python2.7/dist-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')
sys.path.append('/home/pi/.local/lib/python2.7/site-packages')
import time
import subprocess
import threading
import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime
from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast
from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule

class lights(MenuOption):
    
    def __init__(self):
        self.selected_option = 0

        self.options = [
          {'title': 'DayLights', 'action': self.daylights_on,   'icon': ' '},
          {'title': 'NightLights', 'action': self.nightlights_on,   'icon': ' '},
          {'title': 'LightsAuto', 'action': self.auto_on,    'icon': ' '},
          {'title': 'Dayset', 'action': self.dayset,    'icon': ' '},
          {'title': 'Nightset', 'action': self.nightset,  'icon': ' '},
          {'title': 'Offset', 'action': format(str(datetime.datetime.now().time())),  'icon': ' '},

        ]

        MenuOption.__init__(self)

    def dayset(self):
        print("nothing")

    def nightset(self):
        print("nothing")
        
    def offset(self):
        print("nothing")
        
    def daylights_on(self): #Activates Daylights
        self._current_status = 'day'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self): #Activates Nightlights
        self._current_status = 'night'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self): # Deactivates any light mode
        self._current_status = 'off'
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self): # Toggles Between Day/Night Modes
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_on(self): # Sets an automatic timer to activate lights
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self): # Sets an automatic timer to dectivate lights
        self._auto = False
        self.display_message("Auto: Off", 1)

    def select_option(self):
        self.actions[ self.selected_option ]['action']()

    def next_option(self):
        self.selected_option = (self.selected_option + 1) % len(self.options)

    def prev_option(self):
        self.selected_option = (self.selected_option - 1) % len(self.options)

    def up(self):
        self.prev_option()

    def down(self):
        self.next_option()

    def get_current_option(self):
        return self.options[self.selected_option]

    def get_next_option(self):
        return self.options[(self.selected_option + 1) % len(self.options)]

    def get_prev_option(self):
        return self.options[(self.selected_option - 1) % len(self.options)]

    def get_current_icon(self):
        return self.options[ self.selected_option ]['icon']
        
    def get_next_icon(self):
        return self.options[ (self.selected_option + 1) % len(self.options) ]['icon']

    def get_prev_icon(self):
        return self.options[ (self.selected_option - 1) % len(self.options) ]['icon']

    def redraw(self, menu):
        menu.write_option(
            row=0,
            margin=1,
            icon=self.get_prev_icon(),
            text=self.get_prev_option()
        )
      
        menu.write_option( 
            row=1,
            margin=1,
            icon=self.get_current_icon() or '>',
            text=self.get_current_option()
        )
      
        menu.write_option( 
            row=2,
            margin=1,
            icon=self.get_next_icon(),
            text=self.get_next_option()
        )

 
menu = Menu(
 structure={
    'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights(),
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
                }
            }
    },
  lcd=lcd,
)

@nav.on(nav.UP)
def handle_up(pin):
  menu.up()

@nav.on(nav.DOWN)
def handle_down(pin):
  menu.down()


"""
We need to handle right, to let us select options
"""
@nav.on(nav.RIGHT)
def handle_right(pin):
    menu.right()

@nav.on(nav.LEFT)
def handle_LEFT(pin):
    menu.left()

@nav.on(nav.BUTTON)
def handle_BUTTON(pin):
    menu.select()

"""
You can decide when the menu is redrawn, but
you'll usually want to do this:
"""
while 1:
  menu.redraw()
time.sleep(0.01)
==================== RESTART: /home/pi/Scripts/New/1.9.py ====================
Traceback (most recent call last):
  File "/home/pi/Scripts/New/1.9.py", line 196, in <module>
    menu.redraw()
  File "/usr/local/lib/python3.5/dist-packages/dot3k/menu.py", line 476, in redraw
    self.current_value().redraw(self)
  File "/home/pi/Scripts/New/1.9.py", line 145, in redraw
    text=self.get_next_option()
  File "/usr/local/lib/python3.5/dist-packages/dot3k/menu.py", line 431, in write_option
    current_row += text
TypeError: Can't convert 'NoneType' object to str implicitly
import sys
sys.path.append ('/usr/local/lib/python2.7/dist-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')
sys.path.append('/home/pi/.local/lib/python2.7/site-packages')
import time
import subprocess
import threading
import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime
from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast
from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule



class lights(MenuOption):
    
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        self._auto = True
        self._sun_rise = None
        self._sun_set = None
        self._current_status = 'off'
        self.selected_option = 0

        self.options = [
            'Daylights',self.daylights_on(),
            'Nightlights',self.nightlights_on(),
            'LightsAuto',self.auto_on(),
            'Dayset',
            'Offset',
            'Nightset'
            ]
        MenuOption.__init__(self)

    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1

    def next_option(self):
        self.selected_option = (self.selected_option + 1) % len(self.options)

    def prev_option(self):
        self.selected_option = (self.selected_option - 1) % len(self.options)

    def daylights_on(self): #Activates Daylights
        self._current_status = 'day'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self): #Activates Nightlights
        self._current_status = 'night'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self): # Deactivates any light mode
        self._current_status = 'off'
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self): # Toggles Between Day/Night Modes
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_on(self): # Sets an automatic timer to activate lights
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self): # Sets an automatic timer to dectivate lights
        self._auto = False
        self.display_message("Auto: Off", 1)

    """
    We'll need to override the up/down control methods
    to call our next/prev methods
    """

    def up(self):
        self.prev_option()

    def down(self):
        self.next_option()

    """
    And these are some handy functions for getting the prev/next/current
    option text for display on the menu.
    """

    def get_current_option(self):
        return self.options[self.selected_option]

    def get_next_option(self):
        return self.options[(self.selected_option + 1) % len(self.options)]

    def get_prev_option(self):
        return self.options[(self.selected_option - 1) % len(self.options)]

    """
    When the menu is redrawn, it calls your plugins
    redraw method and passes an instance of itself.
    """

    def redraw(self, menu):
        menu.write_option(
            row=0,
            margin=1,
            text=self.get_prev_option()
        )

        menu.write_option(
            row=1,
            margin=1,
            icon='>',
            text=self.get_current_option()
        )

        menu.write_option(
            row=2,
            margin=1,
            text=self.get_next_option()
            return
        )

menu = Menu(
 structure={
    'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights(),
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
                }
            }
    },
  lcd=lcd,
)

@nav.on(nav.UP)
def handle_up(pin):
  menu.up()

@nav.on(nav.DOWN)
def handle_down(pin):
  menu.down()

@nav.on(nav.RIGHT)
def handle_right(pin):
    menu.right()

@nav.on(nav.LEFT)
def handle_LEFT(pin):
    menu.left()

@nav.on(nav.BUTTON)
def handle_BUTTON(pin):
    menu.select()

while 1:
    menu.redraw()
    time.sleep(0.01)

heres the menu package

import time, os, atexit, sys
from collections import OrderedDict
import threading

if sys.version_info[0] >= 3:
    import configparser as ConfigParser
else:
    import ConfigParser

_MODE_NAV = 'navigate'
_MODE_ADJ = 'adjust'
_MODE_TXT = 'entry'

class MenuIcon():
  arrow_left       = [0,0,8,24,8,0,0,0]
  arrow_right      = [0,0,2,3,2,0,0,0]
  arrow_up         = [0,4,14,0,0,0,0,0]
  arrow_down       = [0,0,0,0,0,14,4,0]
  arrow_left_right = [0,0,10,27,10,0,0,0]
  arrow_up_down    = [0,4,14,0,0,14,4,0]
  play             = [0,24,30,31,30,24,0,0]
  pause            = [0,27,27,27,27,27,0,0]
  back             = [0,8,30,9,1,1,14,0]
  bar_left         = [0,3,2,2,2,2,3,0]
  bar_right        = [0,24,8,8,8,8,24,0]
  bar_full         = [0,31,0,31,31,0,31,0]
  bar_empty        = [0,32,0,0,0,0,32,0]
  
class StoppableThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.stop_event = threading.Event()
        self.daemon = True         

    def start(self):
        if self.isAlive() == False:
            self.stop_event.clear()
            threading.Thread.start(self)

    def stop(self):
        if self.isAlive() == True:
            # set event to signal thread to terminate
            self.stop_event.set()
            # block calling thread until thread really has terminated
            self.join()

class AsyncWorker(StoppableThread):
    def __init__(self, todo):
        StoppableThread.__init__(self)
        self.todo = todo

    def run(self):
        while self.stop_event.is_set() == False:
            if self.todo() == False:
                self.stop_event.set()
                break

class Menu():
  """
  This class accepts a list of menu items,
  Each key corresponds to a text item displayed on the menu
  Each value can either be:
  * A nested list, for a sub-menu
  * A function, which is called immediately on select
  * A class derived from MenuOption, providing interactive functionality
  """

  def __init__(self, *args, **kwargs):
    """
    structure, lcd, idle_handler = None, idle_time = 60
    """
    self.menu_options = OrderedDict()
    self.lcd = None
    self.idle_handler = None
    self.input_handler = None
    self.idle_time = 60*1000
    self.config_file = 'dot3k.cfg'

    # Track displayed text for auto-scroll
    self.last_text = ['','','']
    self.last_change = [0,0,0]

    if len(args) > 0 and not args[0] == None:
      self.menu_options = args[0]
    if len(args) > 1:
      self.lcd = args[1]
    if len(args) > 2:
      self.idle_handler = args[2]
    if len(args) > 3:
      self.idle_time = args[3]*1000

    if 'structure' in kwargs.keys() and not kwargs['structure'] == None:
      self.menu_options = kwargs['structure']
    if 'lcd' in kwargs.keys():
      self.lcd = kwargs['lcd']
    if 'idle_handler' in kwargs.keys():
      self.idle_handler = kwargs['idle_handler']
    if 'idle_time' in kwargs.keys():
      self.idle_time = kwargs['idle_time']*1000
    if 'input_handler' in kwargs.keys():
      self.input_handler = kwargs['input_handler']
    if 'config_file' in kwargs.keys():
      self.config_file = kwargs['config_file']

    self.list_location = []
    self.current_position = 0
    self.idle = False
    self.mode = _MODE_NAV

    self.config = ConfigParser.ConfigParser()
    self.config.read([self.config_file, os.path.expanduser('~/.' + self.config_file)])

    if type(self.menu_options) is dict or type(self.menu_options) is OrderedDict:
      self._setup_menu(self.menu_options)
   
    self.last_action = self.millis()
    self._thread = AsyncWorker(self._update)
    atexit.register(self.save)

  def run(self):
    self._thread.start()
    atexit.register(self.stop)

  def stop(self):
    self._thread.stop()

  def _update(self):
    self.redraw()
    time.sleep(0.05)

  def millis(self):
    return int(round(time.time() * 1000))

  def add_item(self, path, handler):
    if not type(path) is list:
      path = path.split('/')
    loc = self.menu_options
    last = path.pop()
    
    while len(path) > 0:
      key = path.pop()
      if not key in loc:
        loc[key] = OrderedDict()
      loc = loc[key]

    loc[last] = handler 

    if isinstance(loc[last],MenuOption):
      loc[last].setup(self.config)

  def save(self):
    if sys.version_info[0] >= 3:
      with open('dot3k.cfg', 'wt', encoding='utf-8') as configfile:
        self.config.write(configfile)
    else:
      with open('dot3k.cfg', 'wb') as configfile:
        self.config.write(configfile)
    print('Config saved to dot3k.cfg')

  def _setup_menu(self, menu):
    for key in menu:
      value = menu[key]
      if type(value) is dict or type(value) is OrderedDict:
        self._setup_menu(value)
      elif isinstance(value,MenuOption):
        value.setup(self.config)

  def current_submenu(self):
    """
    Traverse the list of indexes in list_location
    and find the relevant nested listionary
    """
    menu = self.menu_options
    for location in self.list_location:
      menu = menu[list(menu.keys())[location]]
    return menu

  def current_value(self):
    return self.current_submenu()[self.current_key()]
    
  def current_key(self):
    """
    Convert the integer current_position into
    a valid key for the currently selected listionary
    """
    return list(self.current_submenu().keys())[self.current_position]

  def next_position(self):
    position = self.current_position + 1
    position %= len(self.current_submenu())
    return position

  def previous_position(self):
    position = self.current_position - 1
    position %= len(self.current_submenu())
    return position

  def select_option(self):
    """
    Navigate into, or handle selected menu option accordingly
    """
    if type(self.current_value()) is dict or type(self.current_value()) is OrderedDict:
      self.list_location.append( self.current_position )
      self.current_position = 0
    elif isinstance(self.current_value(),MenuOption):
      self.mode = _MODE_ADJ
      self.current_value().begin()
    elif callable(self.current_submenu()[self.current_key()]):
      self.current_submenu()[self.current_key()]()

  def prev_option(self):
    """
    Decrement the option pointer,
    select previous menu item
    """
    self.current_position = self.previous_position()

  def next_option(self):
    """
    Increment the option pointer,
    select next menu item
    """
    self.current_position = self.next_position()

  def exit_option(self):
    """
    Exit current submenu and restore position
    in previous menu
    """
    if len(self.list_location) > 0:
      self.current_position = self.list_location.pop()

  def start_input(self):
    if self.input_handler == None:
      return False

    self.current_value().text_entry = False
    self.input_handler.begin()
    self.input_handler.set_value(self.current_value().initial_value())
    self.input_handler.set_prompt(self.current_value().input_prompt())
    self.mode = _MODE_TXT

  def finish_input(self):
    if self.input_handler.cancel_input:
      self.current_value().cancel_input()
      self.input_handler.cancel_input = False
      self.input_handler.cleanup()
      self.mode = _MODE_ADJ
    else:
      self.current_value().receive_input(self.input_handler.get_value())
      self.input_handler.cleanup() 
      self.mode = _MODE_ADJ
 
  def select(self):
    """
    Handle "select" action
    """
    self.last_action = self.millis()
    if self.idle:
      self.idle = False
      self.idle_handler.cleanup()
      self.idle_handler.idling = False
      return True

    if self.mode == _MODE_NAV:
      self.select_option()
    elif self.mode == _MODE_ADJ:
      # The "select" call must return true to exit the adjust
      if self.current_value().select():
        self.mode = _MODE_NAV
    elif self.mode == _MODE_TXT:
      if self.input_handler.select():
        self.finish_input()

  def cancel(self):
    self.last_action = self.millis()
    if self.idle:
      self.idle = False
      self.idle_handler.cleanup()
      self.idle_handler.idling = False
      return True
    
    if self.mode == _MODE_NAV:
      self.exit_option()
    if self.mode == _MODE_ADJ:
      self.current_value().cleanup()
      self.mode = _MODE_NAV

  def up(self):
    self.last_action = self.millis()
    if self.idle:
      self.idle = False
      self.idle_handler.cleanup()
      self.idle_handler.idling = False
      return True

    if self.mode == _MODE_NAV:
      self.prev_option()
    elif self.mode == _MODE_ADJ:
      self.current_value().up()
    elif self.mode == _MODE_TXT:
      self.input_handler.up()

  def down(self):
    self.last_action = self.millis()
    if self.idle:
      self.idle = False
      self.idle_handler.cleanup()
      self.idle_handler.idling = False
      return True

    if self.mode == _MODE_NAV:
      self.next_option()
    elif self.mode == _MODE_ADJ:
      self.current_value().down()
    elif self.mode == _MODE_TXT:
      self.input_handler.down()

  def left(self):
    self.last_action = self.millis()
    if self.idle:
      self.idle = False
      self.idle_handler.cleanup()
      self.idle_handler.idling = False
      return True

    if self.mode == _MODE_NAV:
      self.exit_option()
    elif self.mode == _MODE_ADJ:
      if not self.current_value().left():
        self.current_value().cleanup()
        self.mode = _MODE_NAV
    elif self.mode == _MODE_TXT:
      self.input_handler.left()
      

  def right(self):
    self.last_action = self.millis()
    if self.idle:
      self.idle = False
      self.idle_handler.cleanup()
      self.idle_handler.idling = False
      return True

    if self.mode == _MODE_NAV:
      self.select_option()
    elif self.mode == _MODE_ADJ:
      self.current_value().right()
    elif self.mode == _MODE_TXT:
      self.input_handler.right()

  def clear_row(self,row):
    self.lcd.set_cursor_position(0,row)
    self.lcd.write(' '*self.lcd.COLS)
  
  def write_row(self,row,text):
    self.lcd.set_cursor_position(0,row)
    while len(text) < self.lcd.COLS:
      text += ' '
    self.lcd.write(text[0:self.lcd.COLS])

  #def write_option(self,row,text,icon=' ',margin=1):
  def write_option(self, *args, **kwargs):

    row = 0
    text = ''
    icon = ''
    margin = 0
    scroll = False
   
    scroll_padding = '  '
    scroll_delay = 2000
    scroll_repeat = 10000
    scroll_speed = 200

    if len(args) > 0:
      row = args[0]
    if len(args) > 1:
      text = args[1]
    if len(args) > 2:
      icon = args[2]
    if len(args) > 3:
      margin = args[3]

    if 'row' in kwargs.keys():
      row = kwargs['row']
    if 'text' in kwargs.keys():
      text = kwargs['text']
    if 'icon' in kwargs.keys():
      icon = kwargs['icon']
    if 'margin' in kwargs.keys():
      margin = kwargs['margin']

    if 'scroll' in kwargs.keys() and kwargs['scroll'] == True:
      scroll = True
    if 'scroll_speed' in kwargs.keys():
      scroll_speed = kwargs['scroll_speed']
    if 'scroll_repeat' in kwargs.keys():
      scroll_repeat = kwargs['scroll_repeat']
    if 'scroll_delay' in kwargs.keys():
      scroll_delay = kwargs['scroll_delay']
    if 'scroll_padding' in kwargs.keys():
      scroll_padding = kwargs['scroll_padding']

    if icon == None:
      icon = ''

    if margin == None:
      margin = 0

    current_row = ''

    if( self.last_text[row] != text ):
      self.last_text[row] = text
      self.last_change[row] = self.millis()

    if scroll:
      text += scroll_padding

    if scroll and self.millis() - self.last_change[row] > scroll_delay:
      pos = int(((self.millis() - self.last_change[row] - scroll_delay) / scroll_speed) % len(text))
      text = text[pos:]+text[:pos]
      if pos == len(text)-1:
        self.last_change[row] = self.millis() + scroll_repeat

    current_row += icon

    while len(current_row) < margin:
      current_row += ' '

    current_row += text
    
    self.write_row(row,current_row)

  def get_menu_item(self, index):
    return list(self.current_submenu().keys())[index]

  def redraw(self): 
    if self.can_idle() and isinstance(self.idle_handler,MenuOption):
      if self.idle == False:
        self.idle_handler.idling = True
        self.idle_handler.begin()
      self.idle = True
      self.idle_handler.redraw(self)
      return False

    if self.mode == _MODE_NAV:
      self.write_option(
        row=1,
        text=self.get_menu_item(self.current_position),
        icon=chr(252),
        margin=1
      )

      if len(self.current_submenu()) > 2:
        self.write_option(
          row=0,
          text=self.get_menu_item(self.previous_position()),
          margin=1
        )
      else:
        self.clear_row(0)

      if len(self.current_submenu()) > 1:
        self.write_option(
          row=2,
          text=self.get_menu_item(self.next_position()),
          margin=1
        )
      else:
        self.clear_row(2)
      

    # Call the redraw function of the endpoint Class
    elif self.mode == _MODE_ADJ:
      self.current_value().redraw(self)
      if self.current_value().text_entry:
        self.start_input()

    elif self.mode == _MODE_TXT:
      self.input_handler.redraw(self)

  def can_idle(self):
    if self.millis() - self.last_action >= self.idle_time:
      if self.mode == _MODE_NAV:
        return True
      if self.mode == _MODE_ADJ and self.current_value().can_idle:
        return True
    return False
 

class MenuOption():
  def __init__(self):
    self.idling = False
    self.can_idle = False
    self.config = None
    self.text_entry = False

  """
  These helper functions let you start
  the input_handler of the parent menu
  and receive the text that the user inputs
  """
  def initial_value(self):
    # Return a value to start with
    return ''
  def receive_input(self, value):
    # Return false to reject input
    return True
  def request_input(self):
    self.text_entry = True
  def cancel_input(self):
    # Called if input is cancelled by handler
    pass
  def input_prompt(self):
    return 'Input text:'

  """
  These helper functions are for
  input handler plugins
  """
  def set_value(self, value):
    pass

  def get_value(self):
    return ''
  def set_prompt(self, value):
    pass

  def millis(self):
    return int(round(time.time() * 1000))
  def up(self):
    pass
  def down(self):
    pass
  def left(self):
    pass
  def right(self):
    pass
  def select(self):
    # Must return true to allow exit
    return True
  def begin(self):
    pass
  def redraw(self, menu):
    pass
  def setup(self, config):
    self.config = config
  def cleanup(self):
    # Undo any backlight or other changes
    pass

  def set_option(self, section, option, value):
    if self.config != None:
      if not section in self.config.sections():
        self.config.add_section(section)
      self.config.set(section, option, value)

  def get_option(self, section, option, default = None):
    if not section in self.config.sections():
      self.config.add_section(section)
    if option in self.config.options(section):
      return self.config.get(section, option)
    elif default == None:
      return False
    else:
      self.config.set(section, option, str(default))
      return default

this works but doesnt show messages on screen which im not to bothered about right now, next thing is to make the dayset options adjustable somehow using:

if lights.is_auto() == True: # timer for setting for nightlights
        schedule.run_pending()
        schedule.every().day.at("23:06").do(lights.nightlights_on)
        schedule.every().day.at("23:07").do(lights.daylights_on)
import sys
sys.path.append ('/usr/local/lib/python2.7/dist-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')
sys.path.append('/home/pi/.local/lib/python2.7/site-packages')
import time
import subprocess
import threading
import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime
from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast
from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule



class lights(MenuOption):
    
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        self._auto = True
        self._sun_rise = None
        self._sun_set = None
        self._current_status = 'off'
        self.selected_option = 0

        self.options = [
            'Togglelights',
            'LightsAuto',
            'Off',
            'Dayset',
            'Offset',
            'Nightset'
            ]

        self.actions = [
            self.light_toggle,
            self.auto_on,
            self.lights_off,
]
        MenuOption.__init__(self)

    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1



    def daylights_on(self): #Activates Daylights
        self._current_status = 'day'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self): #Activates Nightlights
        self._current_status = 'night'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self): # Deactivates any light mode
        self._current_status = 'off'
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self): # Toggles Between Day/Night Modes
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_on(self): # Activates an automatic timer to activate lights
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self): # Deactivates an automatic timer to dectivate lights
        self._auto = False
        self.display_message("Auto: Off", 1)

    def select_option(self):
        self.actions[self.selected_option]()

    def next_option(self):
        self.selected_option = (self.selected_option + 1) % len(self.options)

    def prev_option(self):
        self.selected_option = (self.selected_option - 1) % len(self.options)

    def up(self):
        self.prev_option()

    def down(self):
        self.next_option()

    def right(self):
        self.select_option()

    def button(self):
        self.select_option()

    def get_current_option(self):
        return self.options[self.selected_option]

    def get_next_option(self):
        return self.options[(self.selected_option + 1) % len(self.options)]

    def get_prev_option(self):
        return self.options[(self.selected_option - 1) % len(self.options)]

    def redraw(self, menu):
        menu.write_option(
            row=0,
            margin=1,
            text=self.get_prev_option()
        )
        menu.write_option(
            row=1,
            margin=1,
            icon='>',
            text=self.get_current_option()          
        )
        menu.write_option(
            row=2,
            margin=1,
            text=self.get_next_option()
        )
        return
menu = Menu(
 structure={
    'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights(),
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
                }
            }
    },
  lcd=lcd,
)

@nav.on(nav.UP)
def handle_up(pin):
  menu.up()

@nav.on(nav.DOWN)
def handle_down(pin):
  menu.down()

@nav.on(nav.RIGHT)
def handle_right(pin):
    menu.right()

@nav.on(nav.LEFT)
def handle_LEFT(pin):
    menu.left()

@nav.on(nav.BUTTON)
def handle_BUTTON(pin):
    menu.select()

while 1:
    menu.redraw()
    time.sleep(0.01)

==================== RESTART: /home/pi/Scripts/New/1.9.py ====================
Traceback (most recent call last):
  File "/home/pi/Scripts/New/1.9.py", line 259, in <module>
    schedule_update()
  File "/home/pi/Scripts/New/1.9.py", line 245, in schedule_update
    lights.set_times(sun_rise, sun_set)
TypeError: set_times() missing 1 required positional argument: 'sun_set'
import sys
sys.path.append ('/usr/local/lib/python2.7/dist-packages')
sys.path.append('/home/pi/Pimoroni/displayotron/examples')
sys.path.append('/home/pi/.local/lib/python2.7/site-packages')
import time
import subprocess
import threading
import dothat.backlight as backlight
import dothat.lcd as lcd
import dothat.touch as nav
from dot3k.menu import Menu, MenuOption
from time import sleep
import datetime
from plugins.clock import Clock
from plugins.graph import IPAddress, GraphTemp, GraphCPU, GraphNetSpeed, GraphSysReboot, GraphSysShutdown
from plugins.text import Text
from plugins.utils import Backlight, Contrast
from astral import Astral
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(5,GPIO.OUT)
import schedule

a = Astral()
a.solar_depression = 'civil'
city = a['London']

class lights(MenuOption):
    
    def __init__(self):
        self._mode = 0
        self._message = ''
        self._timeout = 0
        self._status = False
        self._auto = True
        self._sun_rise = None
        self._sun_set = None
        self._current_status = 'off'
        self.selected_option = 0

        self.options = [
            'Togglelights',
            'ToggleAuto',
            'Off',
            'Dayset',
            'Offset',
            'Nightset'
            ]

        self.actions = [
            self.light_toggle,
            self.auto_toggle,
            self.lights_off,
            ]
        MenuOption.__init__(self)

    def get_status(self):
        return self._current_status

    def is_on(self):
        return self._status

    def is_auto(self):
        return self._auto

    def set_times(self, sun_rise, sun_set):
        self._sun_rise = sun_rise
        self._sun_set = sun_set

    def select_option(self):
        self.actions[self.selected_option]()

    def next_option(self):
        self.selected_option = (self.selected_option + 1) % len(self.options)

    def prev_option(self):
        self.selected_option = (self.selected_option - 1) % len(self.options)

    def up(self):
        self.prev_option()

    def down(self):
        self.next_option()

    def right(self):
        self.select_option()

    def button(self):
        self.select_option()

    def get_current_option(self):
        return self.options[self.selected_option]

    def get_next_option(self):
        return self.options[(self.selected_option + 1) % len(self.options)]

    def get_prev_option(self):
        return self.options[(self.selected_option - 1) % len(self.options)]

    def redraw(self, menu):        
        menu.write_option(
            row=0,
            margin=1,
            text=self.get_prev_option()
        )
        menu.write_option(
            row=1,
            margin=1,
            icon='>',
            text=self.get_current_option()          
        )
        menu.write_option(
            row=2,
            margin=1,
            text=self.get_next_option()
        )
        return
    def display_message(self, message, timeout=1):
        self._message = message
        self._timeout = time.time() + timeout
        self._mode = 1

    def job(day):
        self.daylights_on

    def job(night):
        self.nightlights_on

    def job(off):
        self.lights_off

    def daylights_on(self): #Activates Daylights
        self._current_status = 'day'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,1)
        self._status = True
        self.display_message("DayLights: On", 1)

    def nightlights_on(self): #Activates Nightlights
        self._current_status = 'night'
        GPIO.output(13,0)
        GPIO.output(5,1)
        GPIO.output(13,0)
        self._status = True
        self.display_message("NightLights: On", 1)


    def lights_off(self): # Deactivates any light mode
        self._current_status = 'off'
        GPIO.output(5,0)
        self._status = False
        self.display_message("Lights: Off", 1)

    def light_toggle(self): # Toggles Between Day/Night Modes
        if (GPIO.input(13) == True):
            self.nightlights_on() 
        else:
            self.daylights_on()

    def auto_toggle(self):
        if (self._auto == True):
            self._auto = False

    def auto_on(self): # Activates an automatic timer to activate lights
        self._auto = True
        self.display_message("Auto: On", 1)

    def auto_off(self): # Deactivates an automatic timer to dectivate lights
        self._auto = False
        self.display_message("Auto: Off", 1)
        
menu = Menu(
 structure={
    'Power Options': {
                'Reboot':GraphSysReboot(),
                'Shutdown':GraphSysShutdown(),
                },
            'Aquarium': {
                'Lighting': {
                    'Control': lights(),
                    }
                },
        'Clock': Clock(backlight),
        'Status': {
            'IP': IPAddress(),
            'CPU': GraphCPU(backlight),
            'Temp': GraphTemp()
        },
        'Settings': {
            'Display': {
                'Contrast': Contrast(lcd),
                'Backlight': Backlight(backlight)
                }
            }
    },
  lcd=lcd,
)

@nav.on(nav.UP)
def handle_up(pin):
  menu.up()

@nav.on(nav.DOWN)
def handle_down(pin):
  menu.down()

@nav.on(nav.RIGHT)
def handle_right(pin):
    menu.right()

@nav.on(nav.LEFT)
def handle_LEFT(pin):
    menu.left()

@nav.on(nav.BUTTON)
def handle_BUTTON(pin):
    menu.select()

required_status = 'off'

def schedule_nightlights():
    global required_status
    required_status = 'night'
    return schedule.CancelJob

def schedule_daylights():
    global required_status
    required_status = 'day'
    return schedule.CancelJob

def schedule_off():
    global required_status
    required_status = 'off'
    return schedule.CancelJob

def schedule_update():
    now = datetime.datetime.now()
    sun = city.sun(date=now.date(), local=True)
    sun_rise = sun['sunrise'].time()
    sun_set = sun['sunset'].time()
    dateSTR = datetime.datetime.now().strftime("%H:%M:%S" )
    
    lights.set_times(sun_rise, sun_set)

    schedule.clear('lights') # Cancel all pending sunrise/sunset jobs
    
    # With our newly calculated times, schedule the jobs
    schedule.every().day.at("17:47").do(schedule_nightlights).tag('lights')
    schedule.every().day.at("17:45").do(schedule_daylights).tag('lights')
    schedule.every().day.at("17:46").do(schedule_off).tag('lights')


# Update the schedule with the latest sunrise/sunset times
schedule.every().day.at("00:00").do(schedule_update).tag('update')

# Call schedule update once at startup to force set the initial rise/set times
schedule_update()

while True:
    menu.redraw()
    
    schedule.run_pending()

    if lights.is_auto():
        if lights.get_status() != required_status:
            if required_status == 'day':
                lights.daylights_on()
            if required_status == 'night':
                lights.nightlights_on()
            if required_status == 'off':
                lights.lights_off()

    time.sleep(1.0 / 20)