Countdown with dynamic or calculated time

Questions, tips, information and discussions about using the SharpCap Sequencer and Sequence Planner tools
mcamilleri
Posts: 50
Joined: Thu Mar 02, 2023 12:02 am

Countdown with dynamic or calculated time

#1

Post by mcamilleri »

Hi Robin,

I am completing a SharpCap addin to manage and run occultations, and complete Excel and XML reporting forms.

The automated recording is being done by generating sequences rather than attempt to control SharpCap via the Python scripts. This is to enable users to set up whatever controls and equipment they need without having to handle it in the Python script (which would basically end up rebuilding SharpCap within the script - the 'inner platform effect'). It also keeps the deployment simple as it is all can be run from a single Sequence, not needing Python scripts. This is important as observers may be running it on multiple telescopes or laptops, or provide the Sequences to other observers that don't have my addin installed. Sequences are also safer than Scripting for multiple runs over an entire night. KISS.

I want to display time or countdown notification from the Sequences. However since the sequences are started at any time by the observer, and they might be late to start some steps (I use the WAIT UNTIL LATER THAN so that it doesn't fail if started late) the time until the step starts (e.g. GOTO or recording state) is dynamic. So I can't use the COUNTDOWN as I can't pre-calculate the interval when generating the sequence.

I have used the SHOW NOTIFICATION, and can loop that with a delay to update the time. But it is a small banner at the top of the screen so is not ideal, and I don't really like creating a delay loop as it could be unreliable.

I have developed a workaround using RUN PYTHON, something like this. It calculates the time interval and creates a temporary sequence with the COUNTDOWN then executes it. It works OK, however I can't have an if statement in the function to veto the countdown if it is past the time, so it always flashes up at least briefly - an annoyance rather than a real problem. I can add an IF statement when calling the function.

SEQUENCE
# Import required modules
RUN PYTHON CODE "import datetime as dt;"
RUN PYTHON CODE "def do_countdown(date_string, message): target_dt = dt.datetime.strptime(date_string,'%Y%m%d %H:%M:%S'); seconds_to_wait = int(max(0, (target_dt - dt.datetime.now()).total_seconds())); seconds_to_wait = int(max(0, (target_dt - dt.datetime.now()).total_seconds())); f = open('C:\\Users\\AstroPC\\Documents\\SharpCap\\temp_countdown.scs', 'w', encoding='utf-8'); f.write(f'COUNTDOWN FOR {seconds_to_wait} SHOWING \"{message} \" '); f.close(); SharpCap.Sequencer.RunSequenceFile('C:\\Users\\AstroPC\\Documents\\SharpCap\\temp_countdown.scs');"

RUN PYTHON CODE "do_countdown('20260113 22:59:00','Waiting')"
RUN PYTHON CODE "do_countdown('20260113 23:59:10','Waiting More')"

END SEQUENCE

So I define the do_countdown() function at the top of the sequence then I can call it later. It seems to work OK.

This is a very hacky workaround. Is there a better way to do it? Could you add a COUNTDOWN to a specific time to the sequencer?

Thanks
Michael
mcamilleri
Posts: 50
Joined: Thu Mar 02, 2023 12:02 am

Re: Countdown with dynamic or calculated time

#2

Post by mcamilleri »

Here is a version using the windows temp paths so that it should run on any PC.

Code: Select all

SEQUENCE
# Import required modules
RUN PYTHON CODE "import datetime as dt; import tempfile; import os;"
RUN PYTHON CODE "def do_countdown(date_string, message): target_dt = dt.datetime.strptime(date_string,'%Y%m%d %H:%M:%S'); seconds_to_wait = int(max(0, (target_dt - dt.datetime.now()).total_seconds())); temp_file = os.path.join(tempfile.gettempdir(), 'temp_countdown.scs'); f = open(temp_file, 'w', encoding='utf-8'); f.write(f'COUNTDOWN FOR {seconds_to_wait} SHOWING \"{message} \" '); f.close(); SharpCap.Sequencer.RunSequenceFile(temp_file);"

RUN PYTHON CODE "do_countdown('20260114 22:59:00','Waiting')"
RUN PYTHON CODE "do_countdown('20260114 23:59:10','Waiting More')"

END SEQUENCE
User avatar
admin
Site Admin
Posts: 17342
Joined: Sat Feb 11, 2017 3:52 pm
Location: Vale of the White Horse, UK
Contact:

Re: Countdown with dynamic or calculated time

#3

Post by admin »

Hi Michael,

thanks for sharing how things are going with your new sequence planning tool. I think you are probably right that a 'countdown until' statement would be a good thing in the sequencer language as it would help in this sort of situation. I'm also considering having a way where you can change the time related statements so that they treat midday as the day change point, which would simplify some situations that are otherwise complex around waiting until after midnight.

By the way, maybe there is a way to sneak an if/else into one line of python - this is legal python code

Code: Select all

print ('hello') if condition else print('bye')
The else seems to be required, but it doesn't need to do anything useful...

cheers,

Robin
mcamilleri
Posts: 50
Joined: Thu Mar 02, 2023 12:02 am

Re: Countdown with dynamic or calculated time

#4

Post by mcamilleri »

Thanks, I'll try the alternative if/else.

I had some difficulties with the timing due to the limitations of the Sequencer. The main limitation is only have a WAIT UNTIL time, with no date. I have a workaround using RUN PYTHON to generate date and times but that is hacky and messy, so I am trying to use WAIT UNTIL exclusively.

Since the sequences are started by the observer they might start late. So for example if the GOTO time is 8 pm if they start at 8:01 pm the sequence will wait another 24 hours and they lose the entire nights observations. So I want to use WAIT UNTIL LATER THAN, but that then risks problems with local times after midnight. I could perhaps add a WAIT UNTIL 11:59:59 PM step. I need to do some more testing. I use WAIT UNTIL LATER THAN SUNSET or WAIT UNTIL LATER THAN 6 PM to try to deal with the day properly.

Moving the day changeover to midday would help me, but would perhaps cause problems for solar observers.

Some observers operate remote telescopes in other time zones, so having UTC and/or a TIMEZONE would be helpful

My priorities for time improvements for the scheduler are:
1. Add DATE or DATETIME to WAIT
2. Add ability to use UTC time
3. Add Timezones

Now that I have a working dynamic countdown function I will try again to make a dynamic WAIT UNTIL that calculates wait times using PYTHON datetime.
User avatar
admin
Site Admin
Posts: 17342
Joined: Sat Feb 11, 2017 3:52 pm
Location: Vale of the White Horse, UK
Contact:

Re: Countdown with dynamic or calculated time

#5

Post by admin »

Hi,

for the day changing at midday, I'm planning to make that an option, so that by default a sequence will run with days changing at midnight (for compatibility with current code), but there will be a step you can put at the start of your sequence to change that behaviour. This ensures compatibility while bringing in the new behaviour too.

I'm not 100% convinced by the need for UTC and even less for time zone options. All of the sequencer things currently work in local time. If you are doing calculations based on time then the best thing to do is convert to/from UTC as required - I guess there are python ways to do this, or alternatively you can access System.DateTime from .NET. One of the problems with UTC and/or other time zones is that the concept of 'today' becomes very unclear, and the behaviour of 'wait until later than' will be very difficult to predict.

cheers,

Robin
mcamilleri
Posts: 50
Joined: Thu Mar 02, 2023 12:02 am

Re: Countdown with dynamic or calculated time

#6

Post by mcamilleri »

Thanks - days changing at midnight will be very helpful and I should be able to use local time without the date to reliably trigger event recordings for a nights observations.

I understand where you are coming from with local vs UTC. The general use of SharpCap is to observe a specified object tonight. So telling the sequencer to record at, say , 10 pm tonight makes sense. If the telescope is in another time zone that will still work and the object will be at the roughly the same angle relative to the meridian regardless of longitude. Well, except in China which has a single +8 Timezone.

For transient events such as occultations, transits etc which only occur once the use case is to record at a specific time and date in UTC, not a local time. The event time may vary a bit with location, e.g. ISS crossing the Sun, lunar occultation of a star, or by minutes to hours for a stellar occultation depending on the location.

So if UTC is available there is no need to allow for the Timezone when generating sequences for transient events that will be run in another Timezone, or risk it running on the wrong day.

For example, there is an occultation observer in the USA that runs a remote telescope in Australia.

Michael
User avatar
admin
Site Admin
Posts: 17342
Joined: Sat Feb 11, 2017 3:52 pm
Location: Vale of the White Horse, UK
Contact:

Re: Countdown with dynamic or calculated time

#7

Post by admin »

Hi Michael,

OK, I can see that it gets very interesting when the PC being used is not in the 'correct' timezone for the telescope, meaning that concepts like midnight, midday, etc end up with complex calculations needed to get them right. Internally, SharpCap mostly works in UTC to avoid having to cope with this complexity more than absolutely necessary, but the sequencer works in the local time zone because the vast majority of users think in their local time. If I am going to do anything on this front I have to work out a way to ensure that I don't make things more complex for the majority...

cheers,

Robin
mcamilleri
Posts: 50
Joined: Thu Mar 02, 2023 12:02 am

Re: Countdown with dynamic or calculated time

#8

Post by mcamilleri »

Hi Robin,

I think I have it working now. It was tricky as the user can cancel the Countdown message box and the sequence will then run before it should and they would miss the event. I tried adding time.sleep() if it gets cancelled too soon but that locks up SharpCap until the sleep time ends, and the user can only use emergency stop or force SharpCap to close to recover.

So I added a countdown restart option for the user, recursively, if it gets cancelled. It seems to work OK. I will need to test it some more with a full event recording sequence.

To explain again why I needed this was to ensure the events would be recorded at the correct time. Situations where it could fail to record at the right time is if the observer starts too late (or a step gets delayed or doesn't complete in the expected time), if the event is not tonight but in the following days, or if the event is after midnight and the sequence was using WAIT UNTIL LATER THAN. Also need it to use UTC so the sequence can be run in any time zone.

Code: Select all

RUN PYTHON CODE "def seconds_to_wait_utc(target_dt): return int(max(0, (target_dt - dt.datetime.utcnow()).total_seconds()));"

RUN PYTHON CODE "def do_countdown_utc(date_string, message): target_dt = dt.datetime.strptime(date_string,'%Y%m%d %H:%M:%S'); seconds_to_wait = seconds_to_wait_utc(target_dt); temp_file = os.path.join(tempfile.gettempdir(), 'temp_countdown.scs'); f = open(temp_file, 'w', encoding='utf-8'); f.write(f'COUNTDOWN FOR {seconds_to_wait} SHOWING \"{message} at {date_string} UTC\" '); f.close(); SharpCap.Sequencer.RunSequenceFile(temp_file) if seconds_to_wait >0 else None; seconds_to_wait = seconds_to_wait_utc(target_dt); result = (str(SharpCap.ShowMessageBox('Select Yes to restart Countdown. \nIf you do not restart this event will not be recorded at the correct time','Warning: User stopped the Countdown',MessageBoxButtons.YesNo)) if seconds_to_wait > 0 else 'pass'); do_countdown_utc(date_string, message) if result == 'Yes' else None;"
mcamilleri
Posts: 50
Joined: Thu Mar 02, 2023 12:02 am

Re: Countdown with dynamic or calculated time

#9

Post by mcamilleri »

Forgot some Windows forms imports, as they are in my Addin already. So added the imports so it should run if my addin is not loaded.

Code: Select all

RUN PYTHON CODE "import datetime as dt; import tempfile; import os; import time;import clr;clr.AddReference('System.Windows.Forms');from System.Windows.Forms import MessageBoxButtons;"

RUN PYTHON CODE "def seconds_to_wait_utc(target_dt): return int(max(0, (target_dt - dt.datetime.utcnow()).total_seconds()));"

RUN PYTHON CODE "def do_countdown_utc(date_string, message): target_dt = dt.datetime.strptime(date_string,'%Y%m%d %H:%M:%S'); seconds_to_wait = seconds_to_wait_utc(target_dt); temp_file = os.path.join(tempfile.gettempdir(), 'temp_countdown.scs'); f = open(temp_file, 'w', encoding='utf-8'); f.write(f'COUNTDOWN FOR {seconds_to_wait} SHOWING \"{message} at {date_string} UTC\" '); f.close(); SharpCap.Sequencer.RunSequenceFile(temp_file) if seconds_to_wait >0 else None; seconds_to_wait = seconds_to_wait_utc(target_dt); result = (str(SharpCap.ShowMessageBox('Select Yes to restart Countdown. \nIf you do not restart this event will not be recorded at the correct time','Warning: User stopped the Countdown',MessageBoxButtons.YesNo)) if seconds_to_wait > 0 else 'pass'); do_countdown_utc(date_string, message) if result == 'Yes' else None;"


RUN PYTHON CODE "do_countdown_utc('20260117 10:58:00','Waiting for GOTO')"
mcamilleri
Posts: 50
Joined: Thu Mar 02, 2023 12:02 am

Re: Countdown with dynamic or calculated time

#10

Post by mcamilleri »

Hi Robin,

After much testing and trials I have concluded that generating and running COUNTDOWN is not viable due to threading and blocking issues.

I have come up with RUN PYTHON CODE statements that do a UTC countdown in the notifications, or a UTC countdown with a custom dialog. Each will run from a sequencer script using RUN PYTHON. Due to recursion limits they countdown at 1 min intervals until within 5 mins then count down at 1 s intervals.

I have also set up asynchronous running from my SharpCap Addin. But running a sequence that calls python code from within a Python addin is potentially troublesome.

So long story short, I have some working UTC countdowns, but it would be much better if they were available as sequencer commands.

I have released my occultation-manager add-in now. Available in Beta here https://github.com/labstercam/occultati ... s/releases

Code: Select all

# Waiting notifications and Countdown Functions for the Sequencer
# These do a 'safe' countdown. If run late they will just pass the time. They are safe for passing through midnight. 
# They can be interrupted or stopped by the user without locking or crashing the sequence
# The normal sequencer functions of WAIT UNTIL LOCALTIME or WAIT UNTIL AFTER LOCALTIME are not fully safe
# as Sharpcap does not know what day it is, and treats midnight as start of day
# So if the sequence is started late when using WAIT UNTIL LOCALTIME it will wait until the next day and miss the event.
# If the sequence is run using WAIT UNTIL AFTER LOCALTIME then if the event local time is after midnight it will start immediately

# To help solve these potential issues some countdown functions are provided.
# Choose which one you want to use and MAKE SURE YOU TEST IT AND ARE HAPPY WITH THE BEHAVIOUR
# The more complex countdowns may be less reliable.

# Note that when defining function using RUN PYTHON CODE they must be a single line of code (no line breaks) and cannot use normal if or other flow control statments
# Hence the rather odd syntax formatting.

# Copy these into your sequencer code
# Include the UTC event tags where appropriate, e.g {goto_time} (see the Help menu)



#######################
# Notification with no countdown
# Display a SharpCap Notification with countdown.
# Safe as uses no Python CODE
# But has risks as it only uses the Sequence WAIT UNTIL
# which doesn't know what day it is or when midnight it
#######################


    SHOW NOTIFICATION "Waiting until {goto_time}_local" COLOUR Green DURATION 10000
    # Note MUST use the local time tags with the WAIT statements!
    WAIT UNTIL LOCALTIME "{goto_time_local}"
    CLEAR NOTIFICATION

#######################
# Countdown Notification using UTC
# Display a SharpCap Notification with countdown.
# More complex, perhaps less reliable
#######################

RUN PYTHON CODE "import datetime as dt; import time; import clr; clr.AddReference('System'); from System import Action"
RUN PYTHON CODE "def format_time(seconds): days = seconds // 86400; hours = (seconds % 86400) // 3600; mins = (seconds % 3600) // 60; secs = seconds % 60; return (str(days) + ' Days ' if days > 0 else '') + str(hours).zfill(2) + ':' + str(mins).zfill(2) + ':' + str(secs).zfill(2)"
RUN PYTHON CODE "def countdown_utc(date_string, message, target_dt=None, is_first=True): target_dt = dt.datetime.strptime(date_string,'%Y-%m-%dT%H:%M:%S') if is_first else target_dt; remaining = int((target_dt - dt.datetime.utcnow()).total_seconds()); status = 0; formatted = format_time(remaining); alert = ' ⚠️ LESS THAN 5 MIN!' if remaining < 300 and remaining >= 60 else (' 🔴 LESS THAN 1 MIN!' if remaining < 60 else ''); (SharpCap.ShowNotification(message + ': ' + formatted + ' remaining' + alert, status, False, 2, None, None, None) if remaining > 0 else None); sleep_time = 1 if remaining <= 300 else 60; (time.sleep(sleep_time) if remaining > 0 and SharpCap.Sequencer.IsRunning else None); (countdown_utc(date_string, message, target_dt, False) if remaining > sleep_time and SharpCap.Sequencer.IsRunning else None)"

# Example
# NOTE: Must use the UTC tags, e.g. {goto_time}, {start_time}. See the Help menu
RUN PYTHON CODE "countdown_utc('{goto_time}','Waiting for GOTO')"

#######################
# Countdown Dialog using UTC
# Display a Windows dialog with count down and stop button
# Most complex, and perhaps the least reliable
#######################
RUN PYTHON CODE "import datetime as dt; import time; import clr; clr.AddReference('System.Windows.Forms'); clr.AddReference('System.Drawing'); from System.Windows.Forms import Form, Label, Button, FormStartPosition, DockStyle, FormBorderStyle, Application, Panel; from System.Drawing import Size, Font, FontStyle, ContentAlignment"
RUN PYTHON CODE "def format_time(seconds): days = seconds // 86400; hours = (seconds % 86400) // 3600; mins = (seconds % 3600) // 60; secs = seconds % 60; return (str(days) + ' Days ' if days > 0 else '') + str(hours).zfill(2) + ':' + str(mins).zfill(2) + ':' + str(secs).zfill(2)"
RUN PYTHON CODE "def update_countdown(form, labels, target_dt, stopped): remaining = int((target_dt - dt.datetime.utcnow()).total_seconds()); (labels[1].__setattr__('Text', format_time(remaining)) if remaining > 0 else None); Application.DoEvents(); sleep_time = 1 if remaining <= 300 else 60; (time.sleep(sleep_time) if remaining > 0 and not stopped[0] and SharpCap.Sequencer.IsRunning else None); (update_countdown(form, labels, target_dt, stopped) if remaining > 0 and not stopped[0] and SharpCap.Sequencer.IsRunning else form.Close())"
RUN PYTHON CODE "def countdown_dialog(date_string, message): target_dt = dt.datetime.strptime(date_string,'%Y-%m-%dT%H:%M:%S'); form = Form(); form.Text = message; form.Size = Size(400, 180); form.FormBorderStyle = FormBorderStyle.FixedDialog; form.StartPosition = FormStartPosition.CenterScreen; form.MaximizeBox = False; form.MinimizeBox = False; form.TopMost = True; panel = Panel(); panel.Dock = DockStyle.Fill; lbl_msg = Label(); lbl_msg.Text = message; lbl_msg.Font = Font('Arial', 10, FontStyle.Regular); lbl_msg.Dock = DockStyle.Top; lbl_msg.Height = 30; lbl_msg.TextAlign = ContentAlignment.MiddleCenter; lbl_time = Label(); lbl_time.Font = Font('Arial', 20, FontStyle.Bold); lbl_time.Dock = DockStyle.Fill; lbl_time.TextAlign = ContentAlignment.MiddleCenter; lbl_remain = Label(); lbl_remain.Text = 'remaining'; lbl_remain.Font = Font('Arial', 9, FontStyle.Regular); lbl_remain.Dock = DockStyle.Bottom; lbl_remain.Height = 25; lbl_remain.TextAlign = ContentAlignment.MiddleCenter; panel.Controls.Add(lbl_time); panel.Controls.Add(lbl_msg); panel.Controls.Add(lbl_remain); button = Button(); button.Text = 'Stop Countdown'; button.Dock = DockStyle.Bottom; button.Height = 40; stopped = [False]; button.Click += lambda s, e: (stopped.__setitem__(0, True), form.Close()); form.Controls.Add(panel); form.Controls.Add(button); form.Show(); update_countdown(form, [lbl_msg, lbl_time, lbl_remain], target_dt, stopped)"

# Example
# NOTE: Must use the UTC tags, e.g. {goto_time}, {start_time}. See the Help menu
RUN PYTHON CODE "countdown_dialog('{goto_time}', 'Waiting for GOTO')"
Post Reply