Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
8db2cf1
Compute and apply noon
g7gpr Aug 3, 2025
58d8abd
Handle checks
g7gpr Aug 3, 2025
193401f
Add debug prints
g7gpr Aug 3, 2025
e97f4a3
Round to 2 dp
g7gpr Aug 3, 2025
d078f22
Round to 0dp
g7gpr Aug 3, 2025
9288cd3
Remove debug prints
g7gpr Aug 3, 2025
b073443
Clarify help text
g7gpr Aug 3, 2025
7308fdb
Get camera time
g7gpr Aug 4, 2025
1a77d3f
Get camera time
g7gpr Aug 4, 2025
9219a79
Get camera time
g7gpr Aug 4, 2025
a9d67c6
Get camera time
g7gpr Aug 4, 2025
2303f50
Get camera time
g7gpr Aug 4, 2025
aa3722e
Debug calls
g7gpr Aug 4, 2025
04f97be
Debug calls
g7gpr Aug 4, 2025
fbd4775
Fix datetime call
g7gpr Aug 4, 2025
46ea5c5
Testing work
g7gpr Aug 4, 2025
351824c
Remove print calls inline trivial methods
g7gpr Aug 4, 2025
038a1c0
Remove print calls inline trivial methods
g7gpr Aug 4, 2025
d0e9ca4
Update camera_settings.json
g7gpr Aug 4, 2025
ea25d95
Handle strange longitudes
g7gpr Aug 4, 2025
9d87abf
Handle strange longitudes causing hours out of range
g7gpr Aug 4, 2025
7ce818e
Remove debug log prints
g7gpr Aug 4, 2025
29abca0
Update camera_settings.json
g7gpr Aug 4, 2025
7cbd634
Update docstrings
g7gpr Aug 4, 2025
9442763
Wrap time offset to +/- 12
g7gpr Aug 4, 2025
20ab511
Wrap hour
g7gpr Aug 4, 2025
544c7e0
Remove unused function
g7gpr Aug 4, 2025
0e386a1
Start work on unit tests
g7gpr Aug 4, 2025
0e690ac
Test unit tests
g7gpr Aug 4, 2025
5315100
Fix error in help text
g7gpr Aug 4, 2025
60f08fe
Fix command call
g7gpr Aug 4, 2025
0ccbd24
Test on target hardware
g7gpr Aug 4, 2025
dd6e3a4
Test on target hardware
g7gpr Aug 4, 2025
8581fbc
Test on target hardware
g7gpr Aug 4, 2025
0b708a8
Test on target hardware
g7gpr Aug 4, 2025
a37ad3e
Test on target hardware
g7gpr Aug 4, 2025
6c4b15f
Reinstate print calls
g7gpr Aug 4, 2025
d94f147
Simplify computeCameraTimeOffset
g7gpr Aug 4, 2025
28d9902
Abort if time cannot be read
g7gpr Aug 4, 2025
bb460ba
Fix docstring
g7gpr Aug 4, 2025
e1b363f
10 degree increments for testing
g7gpr Aug 5, 2025
daa568b
Record actual time in test data
g7gpr Aug 5, 2025
a2a0bcb
Record actual time in test data
g7gpr Aug 5, 2025
2b21369
Unit test completed
g7gpr Aug 6, 2025
1c03d14
Remove if false
g7gpr Aug 6, 2025
24b063a
Suggest reboot time change if time adjusted
g7gpr Aug 6, 2025
9f2ae25
Suggest reboot time change if time adjusted
g7gpr Aug 6, 2025
22516dc
Suggest reboot time change if time adjusted
g7gpr Aug 6, 2025
4f13ddc
Fix time formats
g7gpr Aug 6, 2025
9dc2754
Fix time formats
g7gpr Aug 6, 2025
2d2b541
Add command suggestion
g7gpr Aug 6, 2025
6412dc3
Add command suggestion
g7gpr Aug 6, 2025
3b88e50
Add command suggestion
g7gpr Aug 6, 2025
243d7b2
Tidy logging
g7gpr Aug 6, 2025
d091759
Add comment
g7gpr Aug 6, 2025
b108f7a
Wrap suggested reboot time
g7gpr Aug 6, 2025
5a8f2ab
Get previous reboot day
g7gpr Aug 6, 2025
5bf8577
use two spaces for indenting in log to match style
g7gpr Aug 6, 2025
ccc63e7
Remove double space in log
g7gpr Aug 6, 2025
6d83a9f
Tidy logging
g7gpr Aug 6, 2025
d93041f
Work on timezone changes
g7gpr Aug 6, 2025
9ff8006
Work on timezone changes
g7gpr Aug 6, 2025
ea9b57f
Work on timezone changes
g7gpr Aug 6, 2025
85550db
Work on timezone changes
g7gpr Aug 6, 2025
3c227ac
Work on timezone changes
g7gpr Aug 6, 2025
a9c3f78
Work on timezone changes
g7gpr Aug 6, 2025
2a48513
Work on timezone changes
g7gpr Aug 6, 2025
f220158
Work on timezone changes
g7gpr Aug 6, 2025
749e229
Work on timezone changes
g7gpr Aug 6, 2025
9cadadd
Work on timezone changes
g7gpr Aug 6, 2025
d933892
Work on timezone changes
g7gpr Aug 6, 2025
ae3481d
Work on timezone changes
g7gpr Aug 6, 2025
8bbc42c
Work on timezone changes
g7gpr Aug 6, 2025
70df90c
Work on timezone changes
g7gpr Aug 6, 2025
0ea4c34
Work on timezone changes
g7gpr Aug 6, 2025
ee74b47
Work on timezone changes
g7gpr Aug 6, 2025
b3ba934
Work on timezone changes
g7gpr Aug 6, 2025
2e55ea5
Work on timezone changes
g7gpr Aug 6, 2025
ad63226
Work on timezone changes
g7gpr Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions Tests/RebootAtNoonTesting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import os.path
import json
import RMS.Misc
import unittest
import datetime
import re
import dvrip as dvr
import subprocess
import pickle

process_pickle_only = False

def readFileAsLines(file_path="~/source/RMS/.config"):

with open(os.path.expanduser(file_path), 'r') as file:
line_list = file.readlines()

file_as_lines_list, stripped_line = [], ""
for line in line_list:
stripped_line = line.strip().replace("\n","")
file_as_lines_list.append(stripped_line)

return file_as_lines_list

def writeFileFromLines(lines_list, file_path="~/source/RMS/.config"):

with open(os.path.expanduser(file_path), 'w') as file:

for line in lines_list:
file.write("{}\n".format(line))
file.flush()



def getValue(lines_list,section, key):

this_section, output_line_list = None, []
for line in lines_list:
if line.strip().startswith("[") and line.strip().endswith("]"):
this_section = line.replace("[","").replace("]","").strip()

if this_section == section and line.startswith("{}:".format(key)):
value = line[len(key)+1:].strip()

return value



def setValue(lines_list, section, key, value):

this_section, output_line_list = None, []
for line in lines_list:
if line.strip().startswith("[") and line.strip().endswith("]"):
this_section = line.replace("[","").replace("]","").strip()

if this_section == section and line.startswith("{}:".format(key)):
line = "{}: {}".format(key, value)
output_line_list.append(line)
return output_line_list

class TestRebootAtNoon(unittest.TestCase):

def test_RebootAtNoon(self):

config_file = readFileAsLines()
rms_data_path = os.path.expanduser(getValue(config_file, "Capture", "data_dir"))

if not process_pickle_only:
camera_ip = re.findall(r"[0-9]+(?:\.[0-9]+){3}", getValue(config_file, "Capture", "device"))[0]
cam = dvr.DVRIPCam(camera_ip)

if cam.login():

print("Logged in to {}".format(camera_ip))
# Store the station longitude setting
station_longitude = getValue(config_file, "System", "longitude")
test_data = []
for test_longitude in range(-360, 360, 10):
writeFileFromLines(setValue(config_file, "System", "longitude", test_longitude))
for test_hour in range(0, 23):
for test_minute in range(0,60,15):
real_time = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)
test_time_python_object = real_time.replace(hour=test_hour, minute=test_minute, second=0, microsecond=0)
print("Testing at longitude: {} and time:{}".format(test_longitude, test_time_python_object))
cam.set_time(test_time_python_object)
camera_time = cam.get_time()
print("Camera set to {}".format(camera_time))
result = subprocess.run(['python','-m','Utils.CameraControl','SetAutoReboot','Everyday,noon'], capture_output=True, text=True)
print("Command feedback was {}".format(result))
reboot_hour_read_back = cam.get_info("General.AutoMaintain")['AutoRebootHour']
print("Reboot time read back as {}".format(reboot_hour_read_back))
test_data.append([test_longitude,real_time,camera_time,reboot_hour_read_back])
with open(os.path.join(rms_data_path,"reboot_at_noon_test.pkl"), 'wb') as f:
pickle.dump(test_data, f)

# Put the station longitude back
writeFileFromLines(setValue(config_file, "System", "longitude", station_longitude))

with open(os.path.join(rms_data_path, "reboot_at_noon_test.pkl"), 'rb') as f:
test_data_list = pickle.load(f)
output_data = []
max_divergence = 0
output_data.append(
"Longitude | Longitude wrapped | Local noon | Test time | Camera time | Camera time ahead (hrs) | Noon in camera time | Camera reboot time | Divergence |\n")
for test_data in test_data_list:
longitude, test_time_utc, camera_time, reboot_hour_read_back = test_data
longitude_wrapped = (longitude + 180) % 360 - 180




local_noon_utc = 12 - 24 * longitude_wrapped / 360
camera_time_offset_from_utc_hours = (camera_time - test_time_utc).total_seconds() / 3600
noon_in_camera_time = (local_noon_utc + camera_time_offset_from_utc_hours) % 24
divergence_hrs = round(min(noon_in_camera_time - reboot_hour_read_back, reboot_hour_read_back, noon_in_camera_time),1)
output_data.append("{:>9} |{:>18} | {:>10} | {:>5} | {:>5} | {:>23} | {:>19} | {:>18} | {:>10} |\n".format(longitude, longitude_wrapped,
round(local_noon_utc,1), test_time_utc.strftime("%H:%M:%S"),
camera_time.strftime("%H:%M:%S"), round(camera_time_offset_from_utc_hours,1),
round(noon_in_camera_time,1), reboot_hour_read_back, divergence_hrs))

pass

self.assertLess (divergence_hrs, 2 )

with open(os.path.join(rms_data_path, "reboot_at_noon_test.txt"), 'w') as f:
f.writelines(output_data)
f.flush()

if __name__ == '__main__':

unittest.main()
82 changes: 78 additions & 4 deletions Utils/CameraControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,20 @@ def setNetworkParam(cam, opts):

elif fld == 'setTimezone':
val = opts[2]
data_dict = cam.get_info("NetWork.NetNTP")
print("Data dict: {}".format(data_dict))
existing_timezone = data_dict.get('TimeZone')
print("Existing timezone: {}".format(existing_timezone))
if existing_timezone is not None:
if existing_timezone != val:
reboot_time = cam.get_info("General.AutoMaintain")['AutoRebootHour']
reboot_day = cam.get_info("General.AutoMaintain")['AutoRebootDay']
timezone_change = int(val) - existing_timezone
reboot_time_compensated = reboot_time - timezone_change
log.info('Setting timezone to {}'.format(val))
log.info('Consider adjusting camera reboot time of {} to {} using command :'.format(reboot_time, reboot_time_compensated))
log.info(' python -m Utils.CameraControl SetAutoReboot {},{}'.format(reboot_day, reboot_time_compensated))

cam.set_info("NetWork.NetNTP.TimeZone", val)

elif fld == 'EnableNTP':
Expand Down Expand Up @@ -590,23 +604,36 @@ def setAutoReboot(cam, opts):
info = cam.get_info("General.AutoMaintain")
# print(json.dumps(info, ensure_ascii=False, indent=4, sort_keys=True))
if len(opts) < 1:
log.info('usage: setAutoReboot dayofweek,hour')
log.info('usage: SetAutoReboot dayofweek,hour')
log.info(' where dayofweek is Never EveryDay Monday Tuesday etc')
log.info(' and hour is a number between 0 and 23')
log.info(' and hour is a number between 0 and 23 or "noon" for station solar noon')
return
spls = opts[0].split(',')
day = spls[0]
hour = 0
if len(spls) > 1:
hour = int(spls[1])
hour = spls[1]
valid_days = [
'Everyday','Monday','Tuesday','Wednesday','Thursday','Friday',
'Saturday','Sunday','Never'
]

if hour == "noon":
camera_time_offset = computeCameraTimeOffset(cam)
if camera_time_offset is None:
log.warning("Unable to retrieve camera time, aborting")
return
# compute station noon from longitude, and wrap to 24 hour window
station_noon_in_utc = int(12 - config.longitude / 15) % 24
station_noon_in_machine_time = station_noon_in_utc + camera_time_offset
hour = round(station_noon_in_machine_time,0) % 24

hour = int(hour)

if day not in valid_days or hour < 0 or hour > 23:
log.info('usage: SetAutoReboot dayofweek,hour')
log.info(' where dayofweek is Never, Everyday, Monday, Tuesday, Wednesday etc')
log.info(' and hour is a number between 0 and 23')
log.info(' and hour is a number between 0 and 23 or "noon" for station solar noon')
return

info["AutoRebootDay"] = day
Expand Down Expand Up @@ -781,6 +808,30 @@ def dvripCall(cam, cmd, opts, camera_settings_path='./camera_settings.json'):
reqtime = datetime.datetime.strptime(opts[1], '%Y%m%d_%H%M%S')
except:
reqtime = datetime.datetime.now()
time_before_adjustment = datetime.datetime.strptime(str(cam.get_time()), '%Y-%m-%d %H:%M:%S')
time_increment_hrs = (reqtime - time_before_adjustment).total_seconds() / 3600

if abs(time_increment_hrs) > 1:
reboot_time = cam.get_info("General.AutoMaintain")['AutoRebootHour']
reboot_day = cam.get_info("General.AutoMaintain")['AutoRebootDay']
reboot_time_compensated = round(reboot_time - time_increment_hrs) % 24
if time_increment_hrs > 0:

if round(time_increment_hrs,2) != 1:
log.info("Moving camera clock forwards by {} hours.".format(round(time_increment_hrs,2)))
else:
log.info("Moving camera clock forwards by {} hour.".format(round(time_increment_hrs, 2)))
else:

if round(time_increment_hrs,2) != 1:
log.info("Moving camera clock backwards by {} hours.".format(round(time_increment_hrs,2)))
else:
log.info("Moving camera clock backwards by {} hour.".format(round(time_increment_hrs, 2)))
if reboot_time != reboot_time_compensated:
sleep(0.1) # Needed to make the logs get written in the correct order
log.info("Reboot time is hour {}, consider setting to hour {} using command: ".format(reboot_time,reboot_time_compensated))
log.info(" python -m Utils.CameraControl SetAutoReboot {},{}".format(reboot_day, reboot_time_compensated))

cam.set_time(reqtime)
log.info('time set to %s', reqtime)
else:
Expand Down Expand Up @@ -859,6 +910,25 @@ def cameraControlV2(config, cmd, opts=''):

cameraControl(camera_ip, cmd, opts, camera_settings_path=camera_settings_path)

def computeCameraTimeOffset(cam):
"""
Compute camera time offset.

Returns:
Time offset of camera relative to UTC in hours. If the call fails, return None
"""

utc_time_naive = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
try:
camera_time_naive = datetime.datetime.strptime(str(cam.get_time()), "%Y-%m-%d %H:%M:%S")
except:
camera_time_naive = None
return None

camera_time_offset = round((camera_time_naive - utc_time_naive).total_seconds() / 3600,2)

# wrap to +/- 12
return ((camera_time_offset + 12) % 24) - 12

if __name__ == '__main__':
"""Main function
Expand All @@ -867,6 +937,8 @@ def cameraControlV2(config, cmd, opts=''):
opts - optional list of fields and a value to pass to SetParam
"""



# list of supported commands
cmd_list = [
'reboot', 'GetHostname', 'GetSettings','GetDeviceInformation','GetNetConfig',
Expand Down Expand Up @@ -923,6 +995,8 @@ def cameraControlV2(config, cmd, opts=''):
log = getLogger("logger")




if cmd not in cmd_list:
log.info('Error: command "%s" not supported', cmd)
exit(1)
Expand Down
3 changes: 2 additions & 1 deletion camera_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"init": [
["SetOSD", "off"],
["SetColor", "100,50,50,50,0,0"],
["SetAutoReboot", "Everyday,15"],
["CameraTime", "set"],
["CloudConnection", "off"],
["SetParam", "Network", "TransferPlan", "Fluency"],
Expand Down Expand Up @@ -48,6 +47,7 @@

"day": [
["CameraTime", "set"],
["SetAutoReboot", "Everyday,noon"],
["SetParam", "Camera", "DayNightColor", "1"],
["SetParam", "Camera", "ElecLevel", "50"],
["SetParam", "Camera", "BroadTrends", "AutoGain", "1"],
Expand All @@ -58,6 +58,7 @@

"night": [
["CameraTime", "set"],
["SetAutoReboot", "Everyday,noon"],
["SetParam", "Camera", "DayNightColor", "2"],
["SetParam", "Camera", "ElecLevel", "60"],
["SetParam", "Camera", "BroadTrends", "AutoGain", "0"],
Expand Down