def test_serialize(self): # expected json data for valid serialization validJson = '''{"start": "12:00:00", "end": "15:00:00", "mode": "MANUAL", "position": 52}''' timeBlock = ScheduleTimeBlock(datetime.time(12, 0), datetime.time(15, 0), BlindMode.MANUAL, 52) assert (ScheduleTimeBlock.toJson(timeBlock) == validJson)
def test_invalidPosition(self): startTime = datetime.time(11, 11) endTime = datetime.time(17, 55) mode = BlindMode.MANUAL position = 7 timeBlock = ScheduleTimeBlock(startTime, endTime, mode, position) timeBlock._position = -101 # not a valid position with pytest.raises(InvalidTimeBlockException): timeBlock.validate()
def test_validFromDict(self): timeBlockDict = { "start": "08:00:00", "end": "22:11:00", "mode": "MANUAL", "position": 52 } expectedBlock = ScheduleTimeBlock(datetime.time(8, 0), datetime.time(22, 11), BlindMode.MANUAL, 52) convertedBlock = ScheduleTimeBlock.fromDict(timeBlockDict) assert (convertedBlock == expectedBlock)
def toTimeBlock( self, currentTimeProvider=None ): if currentTimeProvider is None: currentTime = datetime.datetime.utcnow().time() else: currentTime = currentTimeProvider() startTime = datetime.time( currentTime.hour, currentTime.minute ) if ( self._duration == 0 ): endTime = ScheduleTimeBlock.END_OF_DAY else: # convert duration in minutes to hours + minutes hourOffset = int( ( currentTime.minute + self._duration ) / 60 ) newHour = currentTime.hour + hourOffset newMinutes = ( currentTime.minute + self._duration ) % 60 if ( newHour > 23 ): endTime = ScheduleTimeBlock.END_OF_DAY else: endTime = datetime.time( newHour, newMinutes ) # check edge case of start and end at 23:59, which will be only case of true 0 duration # this will cause errors in the time block. In this case, there is 0 duration and thus no # real time block, so we return None for consistency if ( startTime == endTime ): return None return ScheduleTimeBlock( startTime, endTime, self._mode, self._position )
def test_validDeserialize(self): validJson = '''{"start": "12:00:00", "end": "15:00:00", "mode": "MANUAL", "position": 52}''' timeBlock = ScheduleTimeBlock.fromJson(validJson) assert (timeBlock._start == datetime.time(12, 0)) assert (timeBlock._end == datetime.time(15, 0)) assert (timeBlock._mode == BlindMode.MANUAL) assert (timeBlock._position == 52)
def test_update(self): startTime = datetime.time(3, 41) endTime = datetime.time(12, 41) mode = BlindMode.MANUAL position = 7 timeBlock = ScheduleTimeBlock(startTime, endTime, mode, position) newStart = datetime.time(15, 41) newEnd = datetime.time(18, 9) newMode = BlindMode.DARK timeBlock.update(newStart, newEnd, newMode) assert (timeBlock._start == newStart) assert (timeBlock._end == newEnd) assert (timeBlock._mode == newMode) assert (timeBlock._position == None)
def check_state_and_update( self ): current_datetime = datetime.datetime.now( self._blindsSchedule._timezone ) current_time = current_datetime.time() print( f"Checking and updating at time: {current_datetime}" ) # check active command. apply or clear the command if self._activeCommandTimeBlock is not None: check_time_result = self._activeCommandTimeBlock.checkTime( current_time ) # case 1: current time is before the command duration # ignore and do nothing, this means the command does not have to be dealt with yet # case 2: current time is within the command duration if check_time_result == 0: print( "DEBUG: Found an applicable command.", ScheduleTimeBlock.toJson( self._activeCommandTimeBlock ) ) self.do_blinds_update( self._activeCommandTimeBlock._mode, self._activeCommandTimeBlock._position ) return # case 3: current time is after the command duration elif check_time_result == 1: # clear the current command, it is no longer valid self._activeCommandTimeBlock = None # At this point, there is no need to deal with manual commands. Check the schedule for a time block current_weekday_index = current_datetime.weekday() current_weekday_name = BlindsSchedule.DAYS_OF_WEEK[ current_weekday_index ] day_sched = self._blindsSchedule._schedule.get( current_weekday_name ) active_schedule_block = None for block in day_sched: if block.checkTime( current_time ) == 0: active_schedule_block = block break # found a time block correspoding to current time if active_schedule_block is not None: print( "DEBUG: Found an applicable scheduled block.", ScheduleTimeBlock.toJson( active_schedule_block ) ) self.do_blinds_update( active_schedule_block._mode, active_schedule_block._position ) return # At this point, no time block was found, so we go to the default behaviour print( "DEBUG: Using defaults. Mode=", self._blindsSchedule._default_mode.name, " Pos=", self._blindsSchedule._default_pos ) self.do_blinds_update( self._blindsSchedule._default_mode, self._blindsSchedule._default_pos ) return
def test_toDict(self): expectedDict = { "start": datetime.time(8, 0), "end": datetime.time(19, 54), "mode": "MANUAL", "position": 52 } timeBlock = ScheduleTimeBlock(expectedDict["start"], expectedDict["end"], BlindMode[expectedDict["mode"]], expectedDict["position"]) timeBlockDict = ScheduleTimeBlock.toDict(timeBlock) assert (timeBlockDict["start"] == str(expectedDict["start"])) assert (timeBlockDict["end"] == str(expectedDict["end"])) assert (timeBlockDict["mode"] == expectedDict["mode"]) assert (timeBlockDict["position"] == expectedDict["position"])
def test_constructor(self): startTime = datetime.time(3, 41) endTime = datetime.time(12, 41) mode = BlindMode.MANUAL position = 7 timeBlock = ScheduleTimeBlock(startTime, endTime, mode, position) assert (timeBlock._start == startTime) assert (timeBlock._end == endTime) assert (timeBlock._mode == mode) assert (timeBlock._position == position)
def test_sortedTimeBlockList(self): # empty list case, verify that it runs with no errors and returns the empty list sortedList = BlindsSchedule.sortedTimeBlockList([]) assert (not sortedList ) # this checks for result being an empty sequence timeBlockList = [ ScheduleTimeBlock(datetime.time(12, 00), datetime.time(15, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(4, 3), datetime.time(6, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(22, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(10, 22), datetime.time(12, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(00, 32), datetime.time(1, 00), BlindMode.LIGHT, None) ] sortedList = BlindsSchedule.sortedTimeBlockList(timeBlockList) previousBlock = sortedList[0] for block in sortedList[1:]: assert (previousBlock._start <= block._start)
def test_hasConflict(self): # lists must be sorted by start time # list without conflict, should return False timeBlockList = [ ScheduleTimeBlock(datetime.time(4, 3), datetime.time(6, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(12, 00), datetime.time(15, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(22, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ] assert (BlindsSchedule.hasConflict(timeBlockList) == False) # list with conflict, should return True timeBlockList = [ ScheduleTimeBlock(datetime.time(4, 3), datetime.time(13, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(12, 00), datetime.time(15, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(22, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ] assert (BlindsSchedule.hasConflict(timeBlockList) == True)
def test_serialization(self): default_mode = BlindMode.LIGHT default_pos = None tz = timezone("Etc/GMT-6") sched = { BlindsSchedule.SUNDAY: [ ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(5, 00), datetime.time(7, 00), BlindMode.MANUAL, 15) ], BlindsSchedule.MONDAY: [ ScheduleTimeBlock(datetime.time(12, 00), datetime.time(15, 00), BlindMode.MANUAL, -33), ScheduleTimeBlock(datetime.time(4, 3), datetime.time(6, 00), BlindMode.LIGHT, None) ], BlindsSchedule.TUESDAY: [ ScheduleTimeBlock(datetime.time(10, 22), datetime.time(12, 00), BlindMode.DARK, None) ], BlindsSchedule.WEDNESDAY: [], BlindsSchedule.THURSDAY: [], BlindsSchedule.FRIDAY: [ ScheduleTimeBlock(datetime.time(22, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.ECO, None), ScheduleTimeBlock(datetime.time(00, 32), datetime.time(1, 00), BlindMode.LIGHT, None) ], BlindsSchedule.SATURDAY: [] } blindsSchedule = BlindsSchedule(default_mode, default_pos, sched, timezone=tz) # use the sortKeys flag to allow deterministic output for comparison scheduleJson = BlindsSchedule.toJson(blindsSchedule, sortKeys=True) # open schedule1.json to compare with open(os.path.join(testFilePath, "schedule1.json"), "r") as file: expectedJson = file.read() assert (scheduleJson == expectedJson)
def test_constructor(self): default_mode = BlindMode.LIGHT default_pos = -45 tz = timezone("Etc/GMT-6") sched = { BlindsSchedule.SUNDAY: [ ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(5, 00), datetime.time(7, 00), BlindMode.MANUAL, 15) ], BlindsSchedule.MONDAY: [ ScheduleTimeBlock(datetime.time(12, 00), datetime.time(15, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(4, 3), datetime.time(6, 00), BlindMode.LIGHT, None) ], BlindsSchedule.TUESDAY: [ ScheduleTimeBlock(datetime.time(10, 22), datetime.time(12, 00), BlindMode.DARK, None) ], BlindsSchedule.WEDNESDAY: [], BlindsSchedule.THURSDAY: [], BlindsSchedule.FRIDAY: [ ScheduleTimeBlock(datetime.time(22, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.ECO, None), ScheduleTimeBlock(datetime.time(00, 32), datetime.time(1, 00), BlindMode.LIGHT, None) ], BlindsSchedule.SATURDAY: [] } blindsSchedule = BlindsSchedule(default_mode, default_pos, sched, timezone=tz) assert (blindsSchedule._default_mode == default_mode) assert (blindsSchedule._default_pos == default_pos) assert (blindsSchedule._timezone.zone == tz.zone) for day in BlindsSchedule.DAYS_OF_WEEK: assert (len(blindsSchedule._schedule[day]) == len(sched[day])) for block in blindsSchedule._schedule[day]: assert (block in sched[day])
def test_validDeserialization(self): default_mode = BlindMode.LIGHT default_pos = None tz = timezone("Etc/GMT+6") sched = { BlindsSchedule.SUNDAY: [ ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(5, 00), datetime.time(7, 00), BlindMode.MANUAL, 15) ], BlindsSchedule.MONDAY: [ ScheduleTimeBlock(datetime.time(12, 00), datetime.time(15, 00), BlindMode.MANUAL, -33), ScheduleTimeBlock(datetime.time(4, 3), datetime.time(6, 00), BlindMode.LIGHT, None) ], BlindsSchedule.TUESDAY: [ ScheduleTimeBlock(datetime.time(10, 22), datetime.time(12, 00), BlindMode.DARK, None) ], BlindsSchedule.WEDNESDAY: [], BlindsSchedule.THURSDAY: [], BlindsSchedule.FRIDAY: [ ScheduleTimeBlock(datetime.time(22, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.ECO, None), ScheduleTimeBlock(datetime.time(00, 32), datetime.time(1, 00), BlindMode.LIGHT, None) ], BlindsSchedule.SATURDAY: [] } expectedSched = BlindsSchedule(default_mode, default_pos, sched) with open(os.path.join(testFilePath, "schedule1.json"), "r") as file: scheduleJson = file.read() parsedSched = BlindsSchedule.fromJson(scheduleJson) assert (parsedSched._default_mode == expectedSched._default_mode) assert (parsedSched._default_pos == expectedSched._default_pos) assert (parsedSched._timezone.zone == tz.zone) assert (parsedSched._schedule == expectedSched._schedule)
def test_postSchedule(self, blindsSystem): test_sched = BlindsSchedule( BlindMode.DARK, schedule={ BlindsSchedule.SUNDAY: [ ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(5, 00), datetime.time(7, 00), BlindMode.MANUAL, 15) ], BlindsSchedule.MONDAY: [ ScheduleTimeBlock(datetime.time(12, 00), datetime.time(15, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(4, 3), datetime.time(6, 00), BlindMode.LIGHT, None) ], BlindsSchedule.TUESDAY: [ ScheduleTimeBlock(datetime.time(10, 22), datetime.time(12, 00), BlindMode.DARK, None) ], BlindsSchedule.WEDNESDAY: [], BlindsSchedule.THURSDAY: [], BlindsSchedule.FRIDAY: [ ScheduleTimeBlock(datetime.time(22, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.ECO, None), ScheduleTimeBlock(datetime.time(00, 32), datetime.time(1, 00), BlindMode.LIGHT, None) ], BlindsSchedule.SATURDAY: [] }) assert (blindsSystem.postSchedule( BlindsSchedule.toDict(test_sched))[1] == RESP_CODES["ACCEPTED"])
def postBlindsCommand( self, command, forceUpdate=False ): print( "processing request for POST command") try: blindsCommand = BlindsCommand.fromDict( command ) # update active command, use custome time provider to insert a timezone self._activeCommandTimeBlock = blindsCommand.toTimeBlock( currentTimeProvider=datetime.datetime.now( self._blindsSchedule._timezone ).time ) # return the resulting time block from the command data = ScheduleTimeBlock.toDict( self._activeCommandTimeBlock ) or {} if forceUpdate: # Update current state based on the command self.check_state_and_update() return data, RESP_CODES[ "ACCEPTED" ] except Exception as err: return ( str(err), RESP_CODES[ "BAD_REQUEST" ] )
def test_scheduleConflictChecks(self): default_mode = BlindMode.LIGHT default_pos = -45 blindsSchedule = BlindsSchedule(default_mode, default_pos) # start without conflict sched = { BlindsSchedule.SUNDAY: [], BlindsSchedule.MONDAY: [], BlindsSchedule.TUESDAY: [], BlindsSchedule.WEDNESDAY: [], BlindsSchedule.THURSDAY: [], BlindsSchedule.FRIDAY: [ ScheduleTimeBlock(datetime.time(00, 32), datetime.time(6, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(16, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.ECO, None) ], BlindsSchedule.SATURDAY: [] } blindsSchedule._schedule = sched assert (blindsSchedule.checkHasNoTimeConflicts() == True) # add time conflict sched = { BlindsSchedule.SUNDAY: [], BlindsSchedule.MONDAY: [], BlindsSchedule.TUESDAY: [], BlindsSchedule.WEDNESDAY: [], BlindsSchedule.THURSDAY: [], BlindsSchedule.FRIDAY: [ ScheduleTimeBlock(datetime.time(00, 32), datetime.time(20, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(16, 44), datetime.time(23, 00), BlindMode.LIGHT, None), ScheduleTimeBlock(datetime.time(23, 38), datetime.time(23, 59), BlindMode.ECO, None) ], BlindsSchedule.SATURDAY: [] } blindsSchedule._schedule = sched with pytest.raises(BlindSchedulingException): blindsSchedule.checkHasNoTimeConflicts()
def test_invalidDeserialize(self): invalidJson = '''{"start": "12:00:00", "end": "15:00:00", "mode": "MANUAL"}''' with pytest.raises(InvalidTimeBlockException): timeBlock = ScheduleTimeBlock.fromJson(invalidJson)
def test_invalidFromDict(self): timeBlockDict = {"start": "08:00:00", "mode": "MANUAL", "position": 52} with pytest.raises(InvalidTimeBlockException): convertedBlock = ScheduleTimeBlock.fromDict(timeBlockDict)