def start_clock(self, time_left, time_right, side): self.time_left = hours_minutes_seconds(time_left) self.time_right = hours_minutes_seconds(time_right) self.time_side = side print('Clock time started at {} - {} on {}'. format(self.time_left, self.time_right, side)) if self.rt: self.rt.stop() if side != ClockSide.NONE: self.rt = RepeatedTimer(1, self._runclock) self.rt.start() self.clock_running = (side != ClockSide.NONE)
def __init__(self, device: str, disable_revelation_leds: bool, is_pi: bool, disable_end: bool, field_factor=0): super(DgtBoard, self).__init__() self.given_device = device self.device = device # rev2 flags self.disable_revelation_leds = disable_revelation_leds self.enable_revelation_pi = False self.is_revelation = False self.is_pi = is_pi self.disable_end = disable_end # @todo for test - XL needs a "end_text" maybe! self.field_factor = field_factor % 10 self.serial = None self.lock = Lock() # lock the serial write self.incoming_board_thread = None self.lever_pos = None # the next three are only used for "not dgtpi" mode self.clock_lock = False # serial connected clock is locked self.last_clock_command = [ ] # Used for resend last (failed) clock command self.enable_ser_clock = None # None = "unknown status" False="only board found" True="clock also found" self.watchdog_timer = RepeatedTimer(1, self._watchdog) # bluetooth vars for Jessie upwards & autoconnect self.btctl = None self.bt_rfcomm = None self.bt_state = -1 self.bt_line = '' self.bt_current_device = -1 self.bt_mac_list = [] self.bt_name_list = [] self.bt_name = '' self.wait_counter = 0 # keep the last time to find out errorous DGT_MSG_BWTIME messages (error: current time > last time) self.r_time = 3600 * 10 # max value cause 10h cant be reached by clock self.l_time = 3600 * 10 # max value cause 10h cant be reached by clock self.bconn_text = None # keep track of changed board positions self.field_timer = None self.field_timer_running = False self.channel = None self.in_settime = False # this is true between set_clock and clock_start => use set values instead of clock self.low_time = False # This is set from picochess.py and used to limit the field timer
def start_clock(self, side: ClockSide, devs: set): """Start the time on the web clock.""" if self.get_name() not in devs: logging.debug('ignored startClock - devs: %s', devs) return True if self.virtual_timer and self.virtual_timer.is_running(): self.virtual_timer.stop() if side != ClockSide.NONE: self.virtual_timer = RepeatedTimer(1, self._runclock) self.virtual_timer.start() self._resume_clock(side) self.clock_show_time = True self._display_time(self.l_time, self.r_time) return True
def start_clock(self, time_left, time_right, side): self.time_left = hours_minutes_seconds(time_left) self.time_right = hours_minutes_seconds(time_right) self.time_side = side print('Clock time started at {} - {}'. format(self.time_left, self.time_right)) if self.rt: self.rt.stop() self.rt = RepeatedTimer(1, self.runclock) self.rt.start() self.clock_running = (side != 0x04)
def __init__(self, device: str, disable_revelation_leds: bool, is_pi: bool, disable_end: bool, field_factor=0): super(DgtBoard, self).__init__() self.given_device = device self.device = device # rev2 flags self.disable_revelation_leds = disable_revelation_leds self.enable_revelation_pi = False self.is_revelation = False self.is_pi = is_pi self.disable_end = disable_end # @todo for test - XL needs a "end_text" maybe! self.field_factor = field_factor % 10 self.serial = None self.lock = Lock() # lock the serial write self.incoming_board_thread = None self.lever_pos = None # the next three are only used for "not dgtpi" mode self.clock_lock = False # serial connected clock is locked self.last_clock_command = [] # Used for resend last (failed) clock command self.enable_ser_clock = None # None = "unknown status" False="only board found" True="clock also found" self.watchdog_timer = RepeatedTimer(1, self._watchdog) # bluetooth vars for Jessie upwards & autoconnect self.btctl = None self.bt_rfcomm = None self.bt_state = -1 self.bt_line = '' self.bt_current_device = -1 self.bt_mac_list = [] self.bt_name_list = [] self.bt_name = '' self.wait_counter = 0 # keep the last time to find out errorous DGT_MSG_BWTIME messages (error: current time > last time) self.r_time = 3600 * 10 # max value cause 10h cant be reached by clock self.l_time = 3600 * 10 # max value cause 10h cant be reached by clock self.bconn_text = None # keep track of changed board positions self.field_timer = None self.field_timer_running = False self.channel = None self.in_settime = False # this is true between set_clock and clock_start => use set values instead of clock self.low_time = False # This is set from picochess.py and used to limit the field timer
class WebVr(DgtIface): """Handle the web (clock) communication.""" def __init__(self, shared, dgtboard: DgtBoard): super(WebVr, self).__init__(dgtboard) self.shared = shared self.virtual_timer = None self.enable_dgtpi = dgtboard.is_pi sub = 2 if dgtboard.is_pi else 0 DisplayMsg.show(Message.DGT_CLOCK_VERSION(main=2, sub=sub, dev='web', text=None)) self.clock_show_time = True # keep the last time to find out errorous DGT_MSG_BWTIME messages (error: current time > last time) self.r_time = 3600 * 10 # max value cause 10h cant be reached by clock self.l_time = 3600 * 10 # max value cause 10h cant be reached by clock def _create_clock_text(self): if 'clock_text' not in self.shared: self.shared['clock_text'] = {} def _runclock(self): if self.side_running == ClockSide.LEFT: time_left = self.l_time - 1 if time_left <= 0: logging.info('negative/zero time left: %s', time_left) self.virtual_timer.stop() time_left = 0 self.l_time = time_left if self.side_running == ClockSide.RIGHT: time_right = self.r_time - 1 if time_right <= 0: logging.info('negative/zero time right: %s', time_right) self.virtual_timer.stop() time_right = 0 self.r_time = time_right logging.info('(web) clock new time received l:%s r:%s', hms_time(self.l_time), hms_time(self.r_time)) DisplayMsg.show(Message.DGT_CLOCK_TIME(time_left=self.l_time, time_right=self.r_time, connect=True, dev='web')) self._display_time(self.l_time, self.r_time) def _display_time(self, time_left: int, time_right: int): if time_left >= 3600 * 10 or time_right >= 3600 * 10: logging.debug('time values not set - abort function') elif self.clock_show_time: l_hms = hms_time(time_left) r_hms = hms_time(time_right) text_l = '{}:{:02d}.{:02d}'.format(l_hms[0], l_hms[1], l_hms[2]) text_r = '{}:{:02d}.{:02d}'.format(r_hms[0], r_hms[1], r_hms[2]) icon_d = 'fa-caret-right' if self.side_running == ClockSide.RIGHT else 'fa-caret-left' if self.side_running == ClockSide.NONE: icon_d = 'fa-sort' text = text_l + ' <i class="fa ' + icon_d + '"></i> ' + text_r self._create_clock_text() self.shared['clock_text'] = text result = {'event': 'Clock', 'msg': text} EventHandler.write_to_clients(result) def display_move_on_clock(self, message): """Display a move on the web clock.""" is_new_rev2 = self.dgtboard.is_revelation and self.dgtboard.enable_revelation_pi if self.enable_dgt3000 or is_new_rev2 or self.enable_dgtpi: bit_board, text = self.get_san(message, not self.enable_dgtpi) points = '...' if message.side == ClockSide.RIGHT else '.' if self.enable_dgtpi: text = '{:3d}{:s}{:s}'.format(bit_board.fullmove_number, points, text) else: text = '{:2d}{:s}{:s}'.format(bit_board.fullmove_number % 100, points, text) else: text = message.move.uci() if message.side == ClockSide.RIGHT: text = text[:2].rjust(3) + text[2:].rjust(3) else: text = text[:2].ljust(3) + text[2:].ljust(3) if self.get_name() not in message.devs: logging.debug('ignored %s - devs: %s', text, message.devs) return True self.clock_show_time = False self._create_clock_text() logging.debug('[%s]', text) self.shared['clock_text'] = text result = {'event': 'Clock', 'msg': text} DisplayMsg.show(Message.DISPLAY_TEXT(text=result)) EventHandler.write_to_clients(result) return True def display_text_on_clock(self, message): """Display a text on the web clock.""" is_new_rev2 = self.dgtboard.is_revelation and self.dgtboard.enable_revelation_pi if self.enable_dgtpi or is_new_rev2: text = message.l else: text = message.m if self.enable_dgt3000 else message.s if self.get_name() not in message.devs: logging.debug('ignored %s - devs: %s', text, message.devs) return True self.clock_show_time = False self._create_clock_text() logging.debug('[%s]', text) self.shared['clock_text'] = text result = {'event': 'Clock', 'msg': text} EventHandler.write_to_clients(result) DisplayMsg.show(Message.DISPLAY_TEXT(text=result)) return True def display_time_on_clock(self, message): """Display the time on the web clock.""" if self.get_name() not in message.devs: logging.debug('ignored endText - devs: %s', message.devs) return True if self.side_running != ClockSide.NONE or message.force: self.clock_show_time = True self._display_time(self.l_time, self.r_time) else: logging.debug('(web) clock isnt running - no need for endText') return True def stop_clock(self, devs: set): """Stop the time on the web clock.""" if self.get_name() not in devs: logging.debug('ignored stopClock - devs: %s', devs) return True if self.virtual_timer: self.virtual_timer.stop() return self._resume_clock(ClockSide.NONE) def _resume_clock(self, side: ClockSide): self.side_running = side return True def start_clock(self, side: ClockSide, devs: set): """Start the time on the web clock.""" if self.get_name() not in devs: logging.debug('ignored startClock - devs: %s', devs) return True if self.virtual_timer and self.virtual_timer.is_running(): self.virtual_timer.stop() if side != ClockSide.NONE: self.virtual_timer = RepeatedTimer(1, self._runclock) self.virtual_timer.start() self._resume_clock(side) self.clock_show_time = True self._display_time(self.l_time, self.r_time) return True def set_clock(self, time_left: int, time_right: int, devs: set): """Start the time on the web clock.""" if self.get_name() not in devs: logging.debug('ignored setClock - devs: %s', devs) return True self.l_time = time_left self.r_time = time_right return True def light_squares_on_revelation(self, uci_move): """Light the rev2 squares.""" result = {'event': 'Light', 'move': uci_move} EventHandler.write_to_clients(result) return True def clear_light_on_revelation(self): """Clear all leds from rev2.""" result = {'event': 'Clear'} EventHandler.write_to_clients(result) return True def get_name(self): """Return name.""" return 'web' def _create_task(self, msg): IOLoop.instance().add_callback(callback=lambda: self._process_message(msg))
class DgtBoard(object): """Handle the DGT board communication.""" def __init__(self, device: str, disable_revelation_leds: bool, is_pi: bool, disable_end: bool, field_factor=0): super(DgtBoard, self).__init__() self.given_device = device self.device = device # rev2 flags self.disable_revelation_leds = disable_revelation_leds self.enable_revelation_pi = False self.is_revelation = False self.is_pi = is_pi self.disable_end = disable_end # @todo for test - XL needs a "end_text" maybe! self.field_factor = field_factor % 10 self.serial = None self.lock = Lock() # lock the serial write self.incoming_board_thread = None self.lever_pos = None # the next three are only used for "not dgtpi" mode self.clock_lock = False # serial connected clock is locked self.last_clock_command = [ ] # Used for resend last (failed) clock command self.enable_ser_clock = None # None = "unknown status" False="only board found" True="clock also found" self.watchdog_timer = RepeatedTimer(1, self._watchdog) # bluetooth vars for Jessie upwards & autoconnect self.btctl = None self.bt_rfcomm = None self.bt_state = -1 self.bt_line = '' self.bt_current_device = -1 self.bt_mac_list = [] self.bt_name_list = [] self.bt_name = '' self.wait_counter = 0 # keep the last time to find out errorous DGT_MSG_BWTIME messages (error: current time > last time) self.r_time = 3600 * 10 # max value cause 10h cant be reached by clock self.l_time = 3600 * 10 # max value cause 10h cant be reached by clock self.bconn_text = None # keep track of changed board positions self.field_timer = None self.field_timer_running = False self.channel = None self.in_settime = False # this is true between set_clock and clock_start => use set values instead of clock self.low_time = False # This is set from picochess.py and used to limit the field timer def expired_field_timer(self): """Board position hasnt changed for some time.""" logging.debug('board position now stable => ask for complete board') self.field_timer_running = False self.write_command([DgtCmd.DGT_SEND_BRD ]) # Ask for the board when a piece moved def stop_field_timer(self): """Stop the field timer cause another field change been send.""" logging.debug( 'board position was unstable => ignore former field update') self.field_timer.cancel() self.field_timer.join() self.field_timer_running = False def start_field_timer(self): """Start the field timer waiting for a stable board position.""" if self.low_time: wait = ( 0.2 if self.channel == 'BT' else 0.10 ) + 0.06 * self.field_factor # bullet => allow more sliding else: wait = (0.5 if self.channel == 'BT' else 0.25 ) + 0.03 * self.field_factor # BT's scanning in half speed logging.debug( 'board position changed => wait %.2fsecs for a stable result low_time: %s', wait, self.low_time) self.field_timer = Timer(wait, self.expired_field_timer) self.field_timer.start() self.field_timer_running = True def write_command(self, message: list): """Write the message list to the dgt board.""" mes = message[3] if message[ 0].value == DgtCmd.DGT_CLOCK_MESSAGE.value else message[0] if not mes == DgtCmd.DGT_RETURN_SERIALNR: logging.debug('(ser) board put [%s] length: %i', mes, len(message)) if mes.value == DgtClk.DGT_CMD_CLOCK_ASCII.value: logging.debug('sending text [%s] to (ser) clock', ''.join([chr(elem) for elem in message[4:12]])) if mes.value == DgtClk.DGT_CMD_REV2_ASCII.value: logging.debug('sending text [%s] to (rev) clock', ''.join([chr(elem) for elem in message[4:15]])) array = [] char_to_xl = { '0': 0x3f, '1': 0x06, '2': 0x5b, '3': 0x4f, '4': 0x66, '5': 0x6d, '6': 0x7d, '7': 0x07, '8': 0x7f, '9': 0x6f, 'a': 0x5f, 'b': 0x7c, 'c': 0x58, 'd': 0x5e, 'e': 0x7b, 'f': 0x71, 'g': 0x3d, 'h': 0x74, 'i': 0x10, 'j': 0x1e, 'k': 0x75, 'l': 0x38, 'm': 0x55, 'n': 0x54, 'o': 0x5c, 'p': 0x73, 'q': 0x67, 'r': 0x50, 's': 0x6d, 't': 0x78, 'u': 0x3e, 'v': 0x2a, 'w': 0x7e, 'x': 0x64, 'y': 0x6e, 'z': 0x5b, ' ': 0x00, '-': 0x40, '/': 0x52, '|': 0x36, '\\': 0x64, '?': 0x53, '@': 0x65, '=': 0x48, '_': 0x08 } for item in message: if isinstance(item, int): array.append(item) elif isinstance(item, enum.Enum): array.append(item.value) elif isinstance(item, str): for character in item: array.append(char_to_xl[character.lower()]) else: logging.error('type not supported [%s]', type(item)) return False while True: if self.serial: with self.lock: try: self.serial.write(bytearray(array)) break except ValueError: logging.error('invalid bytes sent %s', message) return False except SerialException as write_expection: logging.error(write_expection) self.serial.close() self.serial = None except IOError as write_expection: logging.error(write_expection) self.serial.close() self.serial = None if mes == DgtCmd.DGT_RETURN_SERIALNR: break time.sleep(0.1) if message[0] == DgtCmd.DGT_SET_LEDS: logging.debug('(rev) leds turned %s', 'on' if message[2] else 'off') if message[0] == DgtCmd.DGT_CLOCK_MESSAGE: self.last_clock_command = message if self.clock_lock: logging.warning( '(ser) clock is already locked. Maybe a "resend"?') else: logging.debug('(ser) clock is locked now') self.clock_lock = time.time() else: time.sleep(0.1) # give the board some time to process the command return True def _process_board_message(self, message_id: int, message: tuple, message_length: int): if False: # switch-case pass elif message_id == DgtMsg.DGT_MSG_VERSION: if message_length != 2: logging.warning('illegal length in data') board_version = str(message[0]) + '.' + str(message[1]) logging.debug('(ser) board version %0.2f', float(board_version)) self.write_command([DgtCmd.DGT_SEND_BRD ]) # Update the board => get first FEN if self.device.find('rfc') == -1: text_l, text_m, text_s = 'USB e-Board', 'USBboard', 'ok usb' self.channel = 'USB' else: btname5 = self.bt_name[-5:] if 'REVII' in self.bt_name: text_l, text_m, text_s = 'RevII ' + btname5, 'Rev' + btname5, 'b' + btname5 self.is_revelation = True self.write_command([DgtCmd.DGT_RETURN_LONG_SERIALNR]) elif 'DGT_BT' in self.bt_name: text_l, text_m, text_s = 'DGTBT ' + btname5, 'BT ' + btname5, 'b' + btname5 else: text_l, text_m, text_s = 'BT e-Board', 'BT board', 'ok bt' self.channel = 'BT' self.ask_battery_status() self.bconn_text = Dgt.DISPLAY_TEXT(l=text_l, m=text_m, s=text_s, wait=True, beep=False, maxtime=1.1, devs={'i2c', 'web' }) # serial clock lateron DisplayMsg.show( Message.DGT_EBOARD_VERSION(text=self.bconn_text, channel=self.channel)) self.startup_serial_clock() # now ask the serial clock to answer if self.watchdog_timer.is_running(): logging.warning('watchdog timer is already running') else: logging.debug('watchdog timer is started') self.watchdog_timer.start() elif message_id == DgtMsg.DGT_MSG_BWTIME: if message_length != 7: logging.warning('illegal length in data') if ((message[0] & 0x0f) == 0x0a) or ((message[3] & 0x0f) == 0x0a): # Clock ack message # Construct the ack message ack0 = ((message[1]) & 0x7f) | ((message[3] << 3) & 0x80) ack1 = ((message[2]) & 0x7f) | ((message[3] << 2) & 0x80) ack2 = ((message[4]) & 0x7f) | ((message[0] << 3) & 0x80) ack3 = ((message[5]) & 0x7f) | ((message[0] << 2) & 0x80) if ack0 != 0x10: logging.warning('(ser) clock ACK error %s', (ack0, ack1, ack2, ack3)) if self.last_clock_command: logging.debug( '(ser) clock resending failed message [%s]', self.last_clock_command) self.write_command(self.last_clock_command) self.last_clock_command = [] # only resend once return else: logging.debug('(ser) clock ACK okay [%s]', DgtAck(ack1)) if self.last_clock_command: cmd = self.last_clock_command[3] # type: DgtClk if cmd.value != ack1 and ack1 < 0x80: logging.warning( '(ser) clock ACK [%s] out of sync - last: [%s]', DgtAck(ack1), cmd) # @todo these lines are better as what is done on DgtHw but it doesnt work # if ack1 == DgtAck.DGT_ACK_CLOCK_SETNRUN.value: # logging.info('(ser) clock out of set time now') # self.in_settime = False if ack1 == DgtAck.DGT_ACK_CLOCK_BUTTON.value: # this are the other (ack2-ack3) codes # 05-49 33-52 17-51 09-50 65-53 | button 0-4 (single) # 37-52 21-51 13-50 69-53 | button 0 + 1-4 # 49-51 41-50 97-53 | button 1 + 2-4 # 25-50 81-53 | button 2 + 3-4 # 73-53 | button 3 + 4 if ack3 == 49: logging.info('(ser) clock button 0 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=0, dev='ser')) if ack3 == 52: logging.info('(ser) clock button 1 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=1, dev='ser')) if ack3 == 51: logging.info('(ser) clock button 2 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=2, dev='ser')) if ack3 == 50: logging.info('(ser) clock button 3 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=3, dev='ser')) if ack3 == 53: if ack2 == 69: logging.info( '(ser) clock button 0+4 pressed - ack2: %i', ack2) DisplayMsg.show( Message.DGT_BUTTON(button=0x11, dev='ser')) else: logging.info( '(ser) clock button 4 pressed - ack2: %i', ack2) DisplayMsg.show( Message.DGT_BUTTON(button=4, dev='ser')) if ack1 == DgtAck.DGT_ACK_CLOCK_VERSION.value: self.enable_ser_clock = True main = ack2 >> 4 sub = ack2 & 0x0f logging.debug('(ser) clock version %0.2f', float(str(main) + '.' + str(sub))) if self.bconn_text: self.bconn_text.devs = { 'ser' } # Now send the (delayed) message to serial clock dev = 'ser' else: dev = 'err' DisplayMsg.show( Message.DGT_CLOCK_VERSION(main=main, sub=sub, dev=dev, text=self.bconn_text)) elif any(message[:7]): r_hours = message[0] & 0x0f r_mins = (message[1] >> 4) * 10 + (message[1] & 0x0f) r_secs = (message[2] >> 4) * 10 + (message[2] & 0x0f) l_hours = message[3] & 0x0f l_mins = (message[4] >> 4) * 10 + (message[4] & 0x0f) l_secs = (message[5] >> 4) * 10 + (message[5] & 0x0f) r_time = r_hours * 3600 + r_mins * 60 + r_secs l_time = l_hours * 3600 + l_mins * 60 + l_secs errtim = r_hours > 9 or l_hours > 9 or r_mins > 59 or l_mins > 59 or r_secs > 59 or l_secs > 59 if errtim: # complete illegal package received logging.warning('(ser) clock illegal new time received %s', message) elif r_time > self.r_time or l_time > self.l_time: # the new time is higher as the old => ignore logging.warning( '(ser) clock strange old time received %s l:%s r:%s', message, hms_time(self.l_time), hms_time(self.r_time)) if self.in_settime: logging.info( '(ser) clock still in set mode, ignore received time' ) errtim = True elif r_time - self.r_time > 3600 or l_time - self.l_time > 3600: logging.info( '(ser) clock new time over 1h difference, ignore received time' ) errtim = True else: logging.info('(ser) clock new time received l:%s r:%s', hms_time(l_time), hms_time(r_time)) status = message[6] & 0x3f connect = not status & 0x20 if connect: right_side_down = -0x40 if status & 0x02 else 0x40 if self.lever_pos != right_side_down: logging.debug( '(ser) clock button status: 0x%x old lever: %s', status, self.lever_pos) if self.lever_pos is not None: DisplayMsg.show( Message.DGT_BUTTON(button=right_side_down, dev='ser')) self.lever_pos = right_side_down else: logging.info( '(ser) clock not connected, sending old time l:%s r:%s', hms_time(self.l_time), hms_time(self.r_time)) l_time = self.l_time r_time = self.r_time if self.in_settime: logging.info( '(ser) clock still in set mode, sending old time l:%s r:%s', hms_time(self.l_time), hms_time(self.r_time)) l_time = self.l_time r_time = self.r_time DisplayMsg.show( Message.DGT_CLOCK_TIME(time_left=l_time, time_right=r_time, connect=connect, dev='ser')) if not self.enable_ser_clock: dev = 'rev' if 'REVII' in self.bt_name else 'ser' if self.watchdog_timer.is_running( ): # a running watchdog means: board already found logging.info('(%s) clock restarting setup', dev) self.startup_serial_clock() else: logging.info( '(%s) clock sends messages already but (%s) board still not found', dev, dev) if not errtim: self.r_time = r_time self.l_time = l_time else: logging.debug('(ser) clock null message ignored') if self.clock_lock: logging.debug('(ser) clock unlocked after %.3f secs', time.time() - self.clock_lock) self.clock_lock = False elif message_id == DgtMsg.DGT_MSG_BOARD_DUMP: if message_length != 64: logging.warning('illegal length in data') piece_to_char = { 0x01: 'P', 0x02: 'R', 0x03: 'N', 0x04: 'B', 0x05: 'K', 0x06: 'Q', 0x07: 'p', 0x08: 'r', 0x09: 'n', 0x0a: 'b', 0x0b: 'k', 0x0c: 'q', 0x0d: '$', 0x0e: '%', 0x0f: '&', 0x00: '.' } board = '' for character in message: board += piece_to_char[character & 0x0f] logging.debug('\n' + '\n'.join( board[0 + i:8 + i] for i in range(0, len(board), 8))) # Show debug board # Create fen from board fen = '' empty = 0 for square in range(0, 64): if message[square] != 0 and message[ square] < 0x0d: # @todo for the moment ignore the special pieces if empty > 0: fen += str(empty) empty = 0 fen += piece_to_char[message[square] & 0x0f] else: empty += 1 if (square + 1) % 8 == 0: if empty > 0: fen += str(empty) empty = 0 if square < 63: fen += '/' # Attention! This fen is NOT flipped logging.debug('raw fen [%s]', fen) DisplayMsg.show(Message.DGT_FEN(fen=fen, raw=True)) elif message_id == DgtMsg.DGT_MSG_FIELD_UPDATE: if message_length != 2: logging.warning('illegal length in data') if self.field_timer_running: self.stop_field_timer() self.start_field_timer() elif message_id == DgtMsg.DGT_MSG_SERIALNR: if message_length != 5: logging.warning('illegal length in data') DisplayMsg.show( Message.DGT_SERIAL_NR( number=''.join([chr(elem) for elem in message]))) elif message_id == DgtMsg.DGT_MSG_LONG_SERIALNR: if message_length != 10: logging.warning('illegal length in data') number = ''.join([chr(elem) for elem in message]) self.enable_revelation_pi = float( number[:4]) >= 3.25 # "3.250010001"=yes "0000000001"=no logging.info('(rev) clock in PiMode: %s - serial: %s', 'yes' if self.enable_revelation_pi else 'no', number) elif message_id == DgtMsg.DGT_MSG_BATTERY_STATUS: if message_length != 9: logging.warning('illegal length in data') DisplayMsg.show(Message.BATTERY(percent=message[0])) else: # Default logging.warning('message not handled [%s]', DgtMsg(message_id)) def _read_serial(self, bytes_toread=1): try: return self.serial.read(bytes_toread) except SerialException: pass except AttributeError: # serial is None (race condition) pass return b'' def _read_board_message(self, head: bytes): message = () header_len = 3 header = head + self._read_serial(header_len - 1) try: header = struct.unpack('>BBB', header) except struct.error: logging.warning('timeout in header reading') return message message_id = header[0] message_length = counter = (header[1] << 7) + header[2] - header_len if message_length <= 0 or message_length > 64: if message_id == 0x8f and message_length == 0x1f00: # @todo find out why this can happen logging.warning( 'falsely DGT_SEND_EE_MOVES send before => receive and ignore EE_MOVES result' ) self.watchdog_timer.stop( ) # this serial read gonna take around 8secs now = time.time() while counter > 0: ee_moves = self._read_serial(counter) logging.info('EE_MOVES 0x%x bytes read - inWaiting: 0x%x', len(ee_moves), self.serial.inWaiting()) counter -= len(ee_moves) if time.time() - now > 15: logging.warning( 'EE_MOVES needed over 15secs => ignore not readed 0x%x bytes now', counter) break self.watchdog_timer.start() else: logging.warning( 'illegal length in message header 0x%x length: %i', message_id, message_length) return message try: if not message_id == DgtMsg.DGT_MSG_SERIALNR: logging.debug('(ser) board get [%s] length: %i', DgtMsg(message_id), message_length) except ValueError: logging.warning('illegal id in message header 0x%x length: %i', message_id, message_length) return message while counter: byte = self._read_serial() try: if byte: data = struct.unpack('>B', byte) counter -= 1 if data[0] & 0x80: logging.warning('illegal data in message 0x%x found', message_id) logging.warning('ignore collected message data %s', message) return self._read_board_message(byte) message += data else: logging.warning('timeout in data reading') except struct.error: logging.warning('struct error => maybe a reconnected board?') self._process_board_message(message_id, message, message_length) return message def _process_incoming_board_forever(self): counter = 0 logging.info('incoming_board ready') while True: byte = b'' if self.serial: byte = self._read_serial() else: self._setup_serial_port() if self.serial: logging.debug( 'sleeping for 0.5 secs. Afterwards startup the (ser) board' ) time.sleep(0.5) counter = 0 self._startup_serial_board() if byte and byte[0] & 0x80: self._read_board_message(head=byte) else: counter = (counter + 1) % 10 if counter == 0 and not self.watchdog_timer.is_running(): self._watchdog( ) # issue 150 - check for alive connection, so write something to the board time.sleep(0.1) def ask_battery_status(self): """Ask the BT board for the battery status.""" self.write_command([DgtCmd.DGT_SEND_BATTERY_STATUS ]) # Get battery status def startup_serial_clock(self): """Ask the clock for its version.""" self.clock_lock = False self.enable_ser_clock = False command = [ DgtCmd.DGT_CLOCK_MESSAGE, 0x03, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_VERSION, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ] self.write_command(command) # Get clock version def _startup_serial_board(self): self.write_command([DgtCmd.DGT_SEND_UPDATE_NICE ]) # Set the board update mode self.write_command([DgtCmd.DGT_SEND_VERSION]) # Get board version def _watchdog(self): if self.clock_lock and not self.is_pi: if time.time() - self.clock_lock > 2: logging.warning('(ser) clock is locked over 2secs') logging.debug('resending locked (ser) clock message [%s]', self.last_clock_command) self.clock_lock = False self.write_command(self.last_clock_command) self.write_command([ DgtCmd.DGT_RETURN_SERIALNR ]) # ask for this AFTER cause of - maybe - old board hardware def _open_bluetooth(self): # if self.bt_state == -1: # # only for jessie upwards # if path.exists('/usr/bin/bluetoothctl'): # self.bt_state = 0 # # # get rid of old rfcomm # if path.exists('/dev/rfcomm123'): # logging.debug('BT releasing /dev/rfcomm123') # subprocess.call(['rfcomm', 'release', '123']) # subprocess.call(['cat', '/dev/rfcomm123']) # Lucas # self.bt_current_device = -1 # self.bt_mac_list = [] # self.bt_name_list = [] # # logging.debug('BT starting bluetoothctl') # self.btctl = subprocess.Popen('/usr/bin/bluetoothctl', # stdin=subprocess.PIPE, # stdout=subprocess.PIPE, # stderr=subprocess.STDOUT, # universal_newlines=True, # shell=True) # # # set the O_NONBLOCK flag of file descriptor: # flags = fcntl(self.btctl.stdout, F_GETFL) # get current flags # fcntl(self.btctl.stdout, F_SETFL, flags | O_NONBLOCK) # # self.btctl.stdin.write("power on\n") # self.btctl.stdin.flush() # else: # state >= 0 so bluetoothctl is running # try: # check for new data from bluetoothctl # while True: # bt_byte = read(self.btctl.stdout.fileno(), 1).decode(encoding='UTF-8', errors='ignore') # self.bt_line += bt_byte # if bt_byte == '' or bt_byte == '\n': # break # except OSError: # time.sleep(0.1) # # # complete line # if '\n' in self.bt_line: # if False: # switch-case # pass # elif 'Changing power on succeeded' in self.bt_line: # self.bt_state = 1 # self.btctl.stdin.write("agent on\n") # self.btctl.stdin.flush() # elif 'Agent registered' in self.bt_line: # self.bt_state = 2 # self.btctl.stdin.write("default-agent\n") # self.btctl.stdin.flush() # elif 'Default agent request successful' in self.bt_line: # self.bt_state = 3 # self.btctl.stdin.write("scan on\n") # self.btctl.stdin.flush() # elif 'Discovering: yes' in self.bt_line: # self.bt_state = 4 # elif 'Pairing successful' in self.bt_line: # self.bt_state = 6 # logging.debug('BT pairing successful') # elif 'Failed to pair: org.bluez.Error.AlreadyExists' in self.bt_line: # self.bt_state = 6 # logging.debug('BT already paired') # elif 'Failed to pair' in self.bt_line: # # try the next # self.bt_state = 4 # logging.debug('BT pairing failed') # elif 'not available' in self.bt_line: # # remove and try the next # self.bt_state = 4 # self.bt_mac_list.remove(self.bt_mac_list[self.bt_current_device]) # self.bt_name_list.remove(self.bt_name_list[self.bt_current_device]) # self.bt_current_device -= 1 # logging.debug('BT pairing failed, unknown device') # elif ('DGT_BT_' in self.bt_line or 'PCS-REVII' in self.bt_line) and \ # ('NEW' in self.bt_line or 'CHG' in self.bt_line) and 'Device' in self.bt_line: # # New e-Board found add to list # try: # if not self.bt_line.split()[3] in self.bt_mac_list: # self.bt_mac_list.append(self.bt_line.split()[3]) # self.bt_name_list.append(self.bt_line.split()[4]) # logging.debug('BT found device: %s %s', self.bt_line.split()[3], self.bt_line.split()[4]) # except IndexError: # logging.error('BT wrong line [%s]', self.bt_line) # # clear the line # self.bt_line = '' # # # if 'Enter PIN code:' in self.bt_line: # if 'PIN code' in self.bt_line: # if 'DGT_BT_' in self.bt_name_list[self.bt_current_device]: # self.btctl.stdin.write("0000\n") # self.btctl.stdin.flush() # if 'PCS-REVII' in self.bt_name_list[self.bt_current_device]: # self.btctl.stdin.write("1234\n") # self.btctl.stdin.flush() # self.bt_line = '' # # if 'Confirm passkey' in self.bt_line: # self.btctl.stdin.write("yes\n") # self.btctl.stdin.flush() # self.bt_line = '' # # # if there are devices in the list try one # if self.bt_state == 4: # if len(self.bt_mac_list) > 0: # self.bt_state = 5 # self.bt_current_device += 1 # if self.bt_current_device >= len(self.bt_mac_list): # self.bt_current_device = 0 # logging.debug('BT pairing to: %s %s', # self.bt_mac_list[self.bt_current_device], # self.bt_name_list[self.bt_current_device]) # self.btctl.stdin.write('pair ' + self.bt_mac_list[self.bt_current_device] + "\n") # self.btctl.stdin.flush() # # # pair successful, try rfcomm # if self.bt_state == 6: # # now try rfcomm # self.bt_state = 7 # self.bt_rfcomm = subprocess.Popen('rfcomm connect 123 ' + self.bt_mac_list[self.bt_current_device], # stdin=subprocess.PIPE, # stdout=subprocess.PIPE, # stderr=subprocess.PIPE, # universal_newlines=True, # shell=True) # # # wait for rfcomm to fail or succeed # if self.bt_state == 7: # # rfcomm succeeded # if path.exists('/dev/rfcomm123'): # logging.debug('BT connected to: %s', self.bt_name_list[self.bt_current_device]) # if self._open_serial('/dev/rfcomm123'): # self.btctl.stdin.write("quit\n") # self.btctl.stdin.flush() # self.bt_name = self.bt_name_list[self.bt_current_device] # # self.bt_state = -1 # return True # # rfcomm failed # if self.bt_rfcomm.poll() is not None: # logging.debug('BT rfcomm failed') # self.btctl.stdin.write('remove ' + self.bt_mac_list[self.bt_current_device] + "\n") # self.bt_mac_list.remove(self.bt_mac_list[self.bt_current_device]) # self.bt_name_list.remove(self.bt_name_list[self.bt_current_device]) # self.bt_current_device -= 1 # self.btctl.stdin.flush() # self.bt_state = 4 return False def _open_serial(self, device: str): assert not self.serial, 'serial connection still active: %s' % self.serial try: self.serial = Serial(device, stopbits=STOPBITS_ONE, parity=PARITY_NONE, bytesize=EIGHTBITS, timeout=0.5) except SerialException: return False return True def _setup_serial_port(self): # def _success(device: str): # self.device = device # logging.debug('(ser) board connected to %s', self.device) # return True # # waitchars = ['/', '-', '\\', '|'] # # if self.watchdog_timer.is_running(): # logging.debug('watchdog timer is stopped now') # self.watchdog_timer.stop() # if self.serial: # return True # with self.lock: # if self.given_device: # if self._open_serial(self.given_device): # return _success(self.given_device) # else: # for file in listdir('/dev'): # if file.startswith('ttyACM') or file.startswith('ttyUSB') or file == 'rfcomm0': # dev = path.join('/dev', file) # if self._open_serial(dev): # return _success(dev) # if self._open_bluetooth(): # return _success('/dev/rfcomm123') # # # text = self.dgttranslate.text('N00_noboard', 'Board' + waitchars[self.wait_counter]) # bwait = 'Board' + waitchars[self.wait_counter] # text = Dgt.DISPLAY_TEXT(l='no e-' + bwait, m='no' + bwait, s=bwait, wait=True, beep=False, maxtime=0.1, # devs={'i2c', 'web'}) # DisplayMsg.show(Message.DGT_NO_EBOARD_ERROR(text=text)) # self.wait_counter = (self.wait_counter + 1) % len(waitchars) return False # dgtHw functions start def _wait_for_clock(self, func: str): has_to_wait = False counter = 0 while self.clock_lock: if not has_to_wait: has_to_wait = True logging.debug('(ser) clock is locked => waiting to serve: %s', func) time.sleep(0.1) counter = (counter + 1) % 30 if counter == 0: logging.warning('(ser) clock is locked over 3secs') if has_to_wait: logging.debug('(ser) clock is released now') def set_text_rp(self, text: str, beep: int): """Display a text on a Pi enabled Rev2.""" self._wait_for_clock('SetTextRp()') res = self.write_command([ DgtCmd.DGT_CLOCK_MESSAGE, 0x0f, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_REV2_ASCII, text[0], text[1], text[2], text[3], text[4], text[5], text[6], text[7], text[8], text[9], text[10], beep, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ]) return res def set_text_3k(self, text: str, beep: int): """Display a text on a 3000 Clock.""" self._wait_for_clock('SetText3K()') res = self.write_command([ DgtCmd.DGT_CLOCK_MESSAGE, 0x0c, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_ASCII, text[0], text[1], text[2], text[3], text[4], text[5], text[6], text[7], beep, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ]) return res def set_text_xl(self, text: str, beep: int, left_icons=ClockIcons.NONE, right_icons=ClockIcons.NONE): """Display a text on a XL clock.""" def _transfer(icons: ClockIcons): result = 0 if icons == ClockIcons.DOT: result = 0x01 if icons == ClockIcons.COLON: result = 0x02 return result self._wait_for_clock('SetTextXL()') icn = (_transfer(right_icons) & 0x07) | (_transfer(left_icons) << 3) & 0x38 res = self.write_command([ DgtCmd.DGT_CLOCK_MESSAGE, 0x0b, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_DISPLAY, text[2], text[1], text[0], text[5], text[4], text[3], icn, beep, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ]) return res def set_and_run(self, lr: int, lh: int, lm: int, ls: int, rr: int, rh: int, rm: int, rs: int): """Set the clock with times and let it run.""" self._wait_for_clock('SetAndRun()') side = ClockSide.NONE if lr == 1 and rr == 0: side = ClockSide.LEFT if lr == 0 and rr == 1: side = ClockSide.RIGHT res = self.write_command([ DgtCmd.DGT_CLOCK_MESSAGE, 0x0a, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_SETNRUN, lh, lm, ls, rh, rm, rs, side, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ]) return res def end_text(self): """Return the clock display to time display.""" self._wait_for_clock('EndText()') res = self.write_command([ DgtCmd.DGT_CLOCK_MESSAGE, 0x03, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_END, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ]) return res def light_squares_on_revelation(self, uci_move: str): """Light the Rev2 leds.""" if self.is_revelation and not self.disable_revelation_leds: # self._wait_for_clock('LIGHTon') logging.debug('(rev) leds turned on - move: %s', uci_move) fr_s = (8 - int(uci_move[1])) * 8 + ord(uci_move[0]) - ord('a') to_s = (8 - int(uci_move[3])) * 8 + ord(uci_move[2]) - ord('a') self.write_command([ DgtCmd.DGT_SET_LEDS, 0x04, 0x01, fr_s, to_s, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ]) def clear_light_on_revelation(self): """Clear the Rev2 leds.""" if self.is_revelation and not self.disable_revelation_leds: # self._wait_for_clock('LIGHToff') logging.debug('(rev) leds turned off') self.write_command([ DgtCmd.DGT_SET_LEDS, 0x04, 0x00, 0x40, 0x40, DgtClk.DGT_CMD_CLOCK_END_MESSAGE ]) # dgtHw functions end def run(self): """NOT called from threading.Thread instead inside the __init__ function from hw.py.""" self.incoming_board_thread = Timer( 0, self._process_incoming_board_forever) self.incoming_board_thread.start()
class WebVr(DgtIface): """Handle the web (clock) communication.""" def __init__(self, shared, dgtboard: DgtBoard): super(WebVr, self).__init__(dgtboard) self.shared = shared self.virtual_timer = None self.enable_dgtpi = dgtboard.is_pi sub = 2 if dgtboard.is_pi else 0 DisplayMsg.show( Message.DGT_CLOCK_VERSION(main=2, sub=sub, dev='web', text=None)) self.clock_show_time = True # keep the last time to find out errorous DGT_MSG_BWTIME messages (error: current time > last time) self.r_time = 3600 * 10 # max value cause 10h cant be reached by clock self.l_time = 3600 * 10 # max value cause 10h cant be reached by clock def _create_clock_text(self): if 'clock_text' not in self.shared: self.shared['clock_text'] = {} def _runclock(self): if self.side_running == ClockSide.LEFT: time_left = self.l_time - 1 if time_left <= 0: logging.info('negative/zero time left: %s', time_left) self.virtual_timer.stop() time_left = 0 self.l_time = time_left if self.side_running == ClockSide.RIGHT: time_right = self.r_time - 1 if time_right <= 0: logging.info('negative/zero time right: %s', time_right) self.virtual_timer.stop() time_right = 0 self.r_time = time_right logging.info('(web) clock new time received l:%s r:%s', hms_time(self.l_time), hms_time(self.r_time)) DisplayMsg.show( Message.DGT_CLOCK_TIME(time_left=self.l_time, time_right=self.r_time, connect=True, dev='web')) self._display_time(self.l_time, self.r_time) def _display_time(self, time_left: int, time_right: int): if time_left >= 3600 * 10 or time_right >= 3600 * 10: logging.debug('time values not set - abort function') elif self.clock_show_time: l_hms = hms_time(time_left) r_hms = hms_time(time_right) text_l = '{}:{:02d}.{:02d}'.format(l_hms[0], l_hms[1], l_hms[2]) text_r = '{}:{:02d}.{:02d}'.format(r_hms[0], r_hms[1], r_hms[2]) icon_d = 'fa-caret-right' if self.side_running == ClockSide.RIGHT else 'fa-caret-left' if self.side_running == ClockSide.NONE: icon_d = 'fa-sort' text = text_l + ' <i class="fa ' + icon_d + '"></i> ' + text_r self._create_clock_text() self.shared['clock_text'] = text result = {'event': 'Clock', 'msg': text} EventHandler.write_to_clients(result) def display_move_on_clock(self, message): """Display a move on the web clock.""" is_new_rev2 = self.dgtboard.is_revelation and self.dgtboard.enable_revelation_pi if self.enable_dgt3000 or is_new_rev2 or self.enable_dgtpi: bit_board, text = self.get_san(message, not self.enable_dgtpi) points = '...' if message.side == ClockSide.RIGHT else '.' if self.enable_dgtpi: text = '{:3d}{:s}{:s}'.format(bit_board.fullmove_number, points, text) else: text = '{:2d}{:s}{:s}'.format(bit_board.fullmove_number % 100, points, text) else: text = message.move.uci() if message.side == ClockSide.RIGHT: text = text[:2].rjust(3) + text[2:].rjust(3) else: text = text[:2].ljust(3) + text[2:].ljust(3) if self.get_name() not in message.devs: logging.debug('ignored %s - devs: %s', text, message.devs) return True self.clock_show_time = False self._create_clock_text() logging.debug('[%s]', text) self.shared['clock_text'] = text result = {'event': 'Clock', 'msg': text} DisplayMsg.show(Message.DISPLAY_TEXT(text=result)) EventHandler.write_to_clients(result) return True def display_text_on_clock(self, message): """Display a text on the web clock.""" is_new_rev2 = self.dgtboard.is_revelation and self.dgtboard.enable_revelation_pi if self.enable_dgtpi or is_new_rev2: text = message.l else: text = message.m if self.enable_dgt3000 else message.s if self.get_name() not in message.devs: logging.debug('ignored %s - devs: %s', text, message.devs) return True self.clock_show_time = False self._create_clock_text() logging.debug('[%s]', text) self.shared['clock_text'] = text result = {'event': 'Clock', 'msg': text} EventHandler.write_to_clients(result) DisplayMsg.show(Message.DISPLAY_TEXT(text=result)) return True def display_time_on_clock(self, message): """Display the time on the web clock.""" if self.get_name() not in message.devs: logging.debug('ignored endText - devs: %s', message.devs) return True if self.side_running != ClockSide.NONE or message.force: self.clock_show_time = True self._display_time(self.l_time, self.r_time) else: logging.debug('(web) clock isnt running - no need for endText') return True def stop_clock(self, devs: set): """Stop the time on the web clock.""" if self.get_name() not in devs: logging.debug('ignored stopClock - devs: %s', devs) return True if self.virtual_timer: self.virtual_timer.stop() return self._resume_clock(ClockSide.NONE) def _resume_clock(self, side: ClockSide): self.side_running = side return True def start_clock(self, side: ClockSide, devs: set): """Start the time on the web clock.""" if self.get_name() not in devs: logging.debug('ignored startClock - devs: %s', devs) return True if self.virtual_timer and self.virtual_timer.is_running(): self.virtual_timer.stop() if side != ClockSide.NONE: self.virtual_timer = RepeatedTimer(1, self._runclock) self.virtual_timer.start() self._resume_clock(side) self.clock_show_time = True self._display_time(self.l_time, self.r_time) return True def set_clock(self, time_left: int, time_right: int, devs: set): """Start the time on the web clock.""" if self.get_name() not in devs: logging.debug('ignored setClock - devs: %s', devs) return True self.l_time = time_left self.r_time = time_right return True def light_squares_on_revelation(self, uci_move): """Light the rev2 squares.""" result = {'event': 'Light', 'move': uci_move} EventHandler.write_to_clients(result) return True def clear_light_on_revelation(self): """Clear all leds from rev2.""" result = {'event': 'Clear'} EventHandler.write_to_clients(result) return True def get_name(self): """Return name.""" return 'web' def _create_task(self, msg): IOLoop.instance().add_callback( callback=lambda: self._process_message(msg))
class DgtBoard(object): """Handle the DGT board communication.""" def __init__(self, device: str, disable_revelation_leds: bool, is_pi: bool, disable_end: bool, field_factor=0): super(DgtBoard, self).__init__() self.given_device = device self.device = device # rev2 flags self.disable_revelation_leds = disable_revelation_leds self.enable_revelation_pi = False self.is_revelation = False self.is_pi = is_pi self.disable_end = disable_end # @todo for test - XL needs a "end_text" maybe! self.field_factor = field_factor % 10 self.serial = None self.lock = Lock() # lock the serial write self.incoming_board_thread = None self.lever_pos = None # the next three are only used for "not dgtpi" mode self.clock_lock = False # serial connected clock is locked self.last_clock_command = [] # Used for resend last (failed) clock command self.enable_ser_clock = None # None = "unknown status" False="only board found" True="clock also found" self.watchdog_timer = RepeatedTimer(1, self._watchdog) # bluetooth vars for Jessie upwards & autoconnect self.btctl = None self.bt_rfcomm = None self.bt_state = -1 self.bt_line = '' self.bt_current_device = -1 self.bt_mac_list = [] self.bt_name_list = [] self.bt_name = '' self.wait_counter = 0 # keep the last time to find out errorous DGT_MSG_BWTIME messages (error: current time > last time) self.r_time = 3600 * 10 # max value cause 10h cant be reached by clock self.l_time = 3600 * 10 # max value cause 10h cant be reached by clock self.bconn_text = None # keep track of changed board positions self.field_timer = None self.field_timer_running = False self.channel = None self.in_settime = False # this is true between set_clock and clock_start => use set values instead of clock self.low_time = False # This is set from picochess.py and used to limit the field timer def expired_field_timer(self): """Board position hasnt changed for some time.""" logging.debug('board position now stable => ask for complete board') self.field_timer_running = False self.write_command([DgtCmd.DGT_SEND_BRD]) # Ask for the board when a piece moved def stop_field_timer(self): """Stop the field timer cause another field change been send.""" logging.debug('board position was unstable => ignore former field update') self.field_timer.cancel() self.field_timer.join() self.field_timer_running = False def start_field_timer(self): """Start the field timer waiting for a stable board position.""" if self.low_time: wait = (0.2 if self.channel == 'BT' else 0.10) + 0.06 * self.field_factor # bullet => allow more sliding else: wait = (0.5 if self.channel == 'BT' else 0.25) + 0.03 * self.field_factor # BT's scanning in half speed logging.debug('board position changed => wait %.2fsecs for a stable result low_time: %s', wait, self.low_time) self.field_timer = Timer(wait, self.expired_field_timer) self.field_timer.start() self.field_timer_running = True def write_command(self, message: list): """Write the message list to the dgt board.""" mes = message[3] if message[0].value == DgtCmd.DGT_CLOCK_MESSAGE.value else message[0] if not mes == DgtCmd.DGT_RETURN_SERIALNR: logging.debug('(ser) board put [%s] length: %i', mes, len(message)) if mes.value == DgtClk.DGT_CMD_CLOCK_ASCII.value: logging.debug('sending text [%s] to (ser) clock', ''.join([chr(elem) for elem in message[4:12]])) if mes.value == DgtClk.DGT_CMD_REV2_ASCII.value: logging.debug('sending text [%s] to (rev) clock', ''.join([chr(elem) for elem in message[4:15]])) array = [] char_to_xl = { '0': 0x3f, '1': 0x06, '2': 0x5b, '3': 0x4f, '4': 0x66, '5': 0x6d, '6': 0x7d, '7': 0x07, '8': 0x7f, '9': 0x6f, 'a': 0x5f, 'b': 0x7c, 'c': 0x58, 'd': 0x5e, 'e': 0x7b, 'f': 0x71, 'g': 0x3d, 'h': 0x74, 'i': 0x10, 'j': 0x1e, 'k': 0x75, 'l': 0x38, 'm': 0x55, 'n': 0x54, 'o': 0x5c, 'p': 0x73, 'q': 0x67, 'r': 0x50, 's': 0x6d, 't': 0x78, 'u': 0x3e, 'v': 0x2a, 'w': 0x7e, 'x': 0x64, 'y': 0x6e, 'z': 0x5b, ' ': 0x00, '-': 0x40, '/': 0x52, '|': 0x36, '\\': 0x64, '?': 0x53, '@': 0x65, '=': 0x48, '_': 0x08 } for item in message: if isinstance(item, int): array.append(item) elif isinstance(item, enum.Enum): array.append(item.value) elif isinstance(item, str): for character in item: array.append(char_to_xl[character.lower()]) else: logging.error('type not supported [%s]', type(item)) return False while True: if self.serial: with self.lock: try: self.serial.write(bytearray(array)) break except ValueError: logging.error('invalid bytes sent %s', message) return False except SerialException as write_expection: logging.error(write_expection) self.serial.close() self.serial = None except IOError as write_expection: logging.error(write_expection) self.serial.close() self.serial = None if mes == DgtCmd.DGT_RETURN_SERIALNR: break time.sleep(0.1) if message[0] == DgtCmd.DGT_SET_LEDS: logging.debug('(rev) leds turned %s', 'on' if message[2] else 'off') if message[0] == DgtCmd.DGT_CLOCK_MESSAGE: self.last_clock_command = message if self.clock_lock: logging.warning('(ser) clock is already locked. Maybe a "resend"?') else: logging.debug('(ser) clock is locked now') self.clock_lock = time.time() else: time.sleep(0.1) # give the board some time to process the command return True def _process_board_message(self, message_id: int, message: tuple, message_length: int): if False: # switch-case pass elif message_id == DgtMsg.DGT_MSG_VERSION: if message_length != 2: logging.warning('illegal length in data') board_version = str(message[0]) + '.' + str(message[1]) logging.debug('(ser) board version %0.2f', float(board_version)) self.write_command([DgtCmd.DGT_SEND_BRD]) # Update the board => get first FEN if self.device.find('rfc') == -1: text_l, text_m, text_s = 'USB e-Board', 'USBboard', 'ok usb' self.channel = 'USB' else: btname5 = self.bt_name[-5:] if 'REVII' in self.bt_name: text_l, text_m, text_s = 'RevII ' + btname5, 'Rev' + btname5, 'b' + btname5 self.is_revelation = True self.write_command([DgtCmd.DGT_RETURN_LONG_SERIALNR]) elif 'DGT_BT' in self.bt_name: text_l, text_m, text_s = 'DGTBT ' + btname5, 'BT ' + btname5, 'b' + btname5 else: text_l, text_m, text_s = 'BT e-Board', 'BT board', 'ok bt' self.channel = 'BT' self.ask_battery_status() self.bconn_text = Dgt.DISPLAY_TEXT(l=text_l, m=text_m, s=text_s, wait=True, beep=False, maxtime=1.1, devs={'i2c', 'web'}) # serial clock lateron DisplayMsg.show(Message.DGT_EBOARD_VERSION(text=self.bconn_text, channel=self.channel)) self.startup_serial_clock() # now ask the serial clock to answer if self.watchdog_timer.is_running(): logging.warning('watchdog timer is already running') else: logging.debug('watchdog timer is started') self.watchdog_timer.start() elif message_id == DgtMsg.DGT_MSG_BWTIME: if message_length != 7: logging.warning('illegal length in data') if ((message[0] & 0x0f) == 0x0a) or ((message[3] & 0x0f) == 0x0a): # Clock ack message # Construct the ack message ack0 = ((message[1]) & 0x7f) | ((message[3] << 3) & 0x80) ack1 = ((message[2]) & 0x7f) | ((message[3] << 2) & 0x80) ack2 = ((message[4]) & 0x7f) | ((message[0] << 3) & 0x80) ack3 = ((message[5]) & 0x7f) | ((message[0] << 2) & 0x80) if ack0 != 0x10: logging.warning('(ser) clock ACK error %s', (ack0, ack1, ack2, ack3)) if self.last_clock_command: logging.debug('(ser) clock resending failed message [%s]', self.last_clock_command) self.write_command(self.last_clock_command) self.last_clock_command = [] # only resend once return else: logging.debug('(ser) clock ACK okay [%s]', DgtAck(ack1)) if self.last_clock_command: cmd = self.last_clock_command[3] # type: DgtClk if cmd.value != ack1 and ack1 < 0x80: logging.warning('(ser) clock ACK [%s] out of sync - last: [%s]', DgtAck(ack1), cmd) # @todo these lines are better as what is done on DgtHw but it doesnt work # if ack1 == DgtAck.DGT_ACK_CLOCK_SETNRUN.value: # logging.info('(ser) clock out of set time now') # self.in_settime = False if ack1 == DgtAck.DGT_ACK_CLOCK_BUTTON.value: # this are the other (ack2-ack3) codes # 05-49 33-52 17-51 09-50 65-53 | button 0-4 (single) # 37-52 21-51 13-50 69-53 | button 0 + 1-4 # 49-51 41-50 97-53 | button 1 + 2-4 # 25-50 81-53 | button 2 + 3-4 # 73-53 | button 3 + 4 if ack3 == 49: logging.info('(ser) clock button 0 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=0, dev='ser')) if ack3 == 52: logging.info('(ser) clock button 1 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=1, dev='ser')) if ack3 == 51: logging.info('(ser) clock button 2 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=2, dev='ser')) if ack3 == 50: logging.info('(ser) clock button 3 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=3, dev='ser')) if ack3 == 53: if ack2 == 69: logging.info('(ser) clock button 0+4 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=0x11, dev='ser')) else: logging.info('(ser) clock button 4 pressed - ack2: %i', ack2) DisplayMsg.show(Message.DGT_BUTTON(button=4, dev='ser')) if ack1 == DgtAck.DGT_ACK_CLOCK_VERSION.value: self.enable_ser_clock = True main = ack2 >> 4 sub = ack2 & 0x0f logging.debug('(ser) clock version %0.2f', float(str(main) + '.' + str(sub))) if self.bconn_text: self.bconn_text.devs = {'ser'} # Now send the (delayed) message to serial clock dev = 'ser' else: dev = 'err' DisplayMsg.show(Message.DGT_CLOCK_VERSION(main=main, sub=sub, dev=dev, text=self.bconn_text)) elif any(message[:7]): r_hours = message[0] & 0x0f r_mins = (message[1] >> 4) * 10 + (message[1] & 0x0f) r_secs = (message[2] >> 4) * 10 + (message[2] & 0x0f) l_hours = message[3] & 0x0f l_mins = (message[4] >> 4) * 10 + (message[4] & 0x0f) l_secs = (message[5] >> 4) * 10 + (message[5] & 0x0f) r_time = r_hours * 3600 + r_mins * 60 + r_secs l_time = l_hours * 3600 + l_mins * 60 + l_secs errtim = r_hours > 9 or l_hours > 9 or r_mins > 59 or l_mins > 59 or r_secs > 59 or l_secs > 59 if errtim: # complete illegal package received logging.warning('(ser) clock illegal new time received %s', message) elif r_time > self.r_time or l_time > self.l_time: # the new time is higher as the old => ignore logging.warning('(ser) clock strange old time received %s l:%s r:%s', message, hms_time(self.l_time), hms_time(self.r_time)) if self.in_settime: logging.info('(ser) clock still in set mode, ignore received time') errtim = True elif r_time - self.r_time > 3600 or l_time - self.l_time > 3600: logging.info('(ser) clock new time over 1h difference, ignore received time') errtim = True else: logging.info('(ser) clock new time received l:%s r:%s', hms_time(l_time), hms_time(r_time)) status = message[6] & 0x3f connect = not status & 0x20 if connect: right_side_down = -0x40 if status & 0x02 else 0x40 if self.lever_pos != right_side_down: logging.debug('(ser) clock button status: 0x%x old lever: %s', status, self.lever_pos) if self.lever_pos is not None: DisplayMsg.show(Message.DGT_BUTTON(button=right_side_down, dev='ser')) self.lever_pos = right_side_down else: logging.info('(ser) clock not connected, sending old time l:%s r:%s', hms_time(self.l_time), hms_time(self.r_time)) l_time = self.l_time r_time = self.r_time if self.in_settime: logging.info('(ser) clock still in set mode, sending old time l:%s r:%s', hms_time(self.l_time), hms_time(self.r_time)) l_time = self.l_time r_time = self.r_time DisplayMsg.show(Message.DGT_CLOCK_TIME(time_left=l_time, time_right=r_time, connect=connect, dev='ser')) if not self.enable_ser_clock: dev = 'rev' if 'REVII' in self.bt_name else 'ser' if self.watchdog_timer.is_running(): # a running watchdog means: board already found logging.info('(%s) clock restarting setup', dev) self.startup_serial_clock() else: logging.info('(%s) clock sends messages already but (%s) board still not found', dev, dev) if not errtim: self.r_time = r_time self.l_time = l_time else: logging.debug('(ser) clock null message ignored') if self.clock_lock: logging.debug('(ser) clock unlocked after %.3f secs', time.time() - self.clock_lock) self.clock_lock = False elif message_id == DgtMsg.DGT_MSG_BOARD_DUMP: if message_length != 64: logging.warning('illegal length in data') piece_to_char = { 0x01: 'P', 0x02: 'R', 0x03: 'N', 0x04: 'B', 0x05: 'K', 0x06: 'Q', 0x07: 'p', 0x08: 'r', 0x09: 'n', 0x0a: 'b', 0x0b: 'k', 0x0c: 'q', 0x0d: '$', 0x0e: '%', 0x0f: '&', 0x00: '.' } board = '' for character in message: board += piece_to_char[character & 0x0f] logging.debug('\n' + '\n'.join(board[0 + i:8 + i] for i in range(0, len(board), 8))) # Show debug board # Create fen from board fen = '' empty = 0 for square in range(0, 64): if message[square] != 0 and message[square] < 0x0d: # @todo for the moment ignore the special pieces if empty > 0: fen += str(empty) empty = 0 fen += piece_to_char[message[square] & 0x0f] else: empty += 1 if (square + 1) % 8 == 0: if empty > 0: fen += str(empty) empty = 0 if square < 63: fen += '/' # Attention! This fen is NOT flipped logging.debug('raw fen [%s]', fen) DisplayMsg.show(Message.DGT_FEN(fen=fen, raw=True)) elif message_id == DgtMsg.DGT_MSG_FIELD_UPDATE: if message_length != 2: logging.warning('illegal length in data') if self.field_timer_running: self.stop_field_timer() self.start_field_timer() elif message_id == DgtMsg.DGT_MSG_SERIALNR: if message_length != 5: logging.warning('illegal length in data') DisplayMsg.show(Message.DGT_SERIAL_NR(number=''.join([chr(elem) for elem in message]))) elif message_id == DgtMsg.DGT_MSG_LONG_SERIALNR: if message_length != 10: logging.warning('illegal length in data') number = ''.join([chr(elem) for elem in message]) self.enable_revelation_pi = float(number[:4]) >= 3.25 # "3.250010001"=yes "0000000001"=no logging.info('(rev) clock in PiMode: %s - serial: %s', 'yes' if self.enable_revelation_pi else 'no', number) elif message_id == DgtMsg.DGT_MSG_BATTERY_STATUS: if message_length != 9: logging.warning('illegal length in data') DisplayMsg.show(Message.BATTERY(percent=message[0])) else: # Default logging.warning('message not handled [%s]', DgtMsg(message_id)) def _read_serial(self, bytes_toread=1): try: return self.serial.read(bytes_toread) except SerialException: pass except AttributeError: # serial is None (race condition) pass return b'' def _read_board_message(self, head: bytes): message = () header_len = 3 header = head + self._read_serial(header_len - 1) try: header = struct.unpack('>BBB', header) except struct.error: logging.warning('timeout in header reading') return message message_id = header[0] message_length = counter = (header[1] << 7) + header[2] - header_len if message_length <= 0 or message_length > 64: if message_id == 0x8f and message_length == 0x1f00: # @todo find out why this can happen logging.warning('falsely DGT_SEND_EE_MOVES send before => receive and ignore EE_MOVES result') self.watchdog_timer.stop() # this serial read gonna take around 8secs now = time.time() while counter > 0: ee_moves = self._read_serial(counter) logging.info('EE_MOVES 0x%x bytes read - inWaiting: 0x%x', len(ee_moves), self.serial.inWaiting()) counter -= len(ee_moves) if time.time() - now > 15: logging.warning('EE_MOVES needed over 15secs => ignore not readed 0x%x bytes now', counter) break self.watchdog_timer.start() else: logging.warning('illegal length in message header 0x%x length: %i', message_id, message_length) return message try: if not message_id == DgtMsg.DGT_MSG_SERIALNR: logging.debug('(ser) board get [%s] length: %i', DgtMsg(message_id), message_length) except ValueError: logging.warning('illegal id in message header 0x%x length: %i', message_id, message_length) return message while counter: byte = self._read_serial() try: if byte: data = struct.unpack('>B', byte) counter -= 1 if data[0] & 0x80: logging.warning('illegal data in message 0x%x found', message_id) logging.warning('ignore collected message data %s', message) return self._read_board_message(byte) message += data else: logging.warning('timeout in data reading') except struct.error: logging.warning('struct error => maybe a reconnected board?') self._process_board_message(message_id, message, message_length) return message def _process_incoming_board_forever(self): counter = 0 logging.info('incoming_board ready') while True: byte = b'' if self.serial: byte = self._read_serial() else: self._setup_serial_port() if self.serial: logging.debug('sleeping for 0.5 secs. Afterwards startup the (ser) board') time.sleep(0.5) counter = 0 self._startup_serial_board() if byte and byte[0] & 0x80: self._read_board_message(head=byte) else: counter = (counter + 1) % 10 if counter == 0 and not self.watchdog_timer.is_running(): self._watchdog() # issue 150 - check for alive connection, so write something to the board time.sleep(0.1) def ask_battery_status(self): """Ask the BT board for the battery status.""" self.write_command([DgtCmd.DGT_SEND_BATTERY_STATUS]) # Get battery status def startup_serial_clock(self): """Ask the clock for its version.""" self.clock_lock = False self.enable_ser_clock = False command = [DgtCmd.DGT_CLOCK_MESSAGE, 0x03, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_VERSION, DgtClk.DGT_CMD_CLOCK_END_MESSAGE] self.write_command(command) # Get clock version def _startup_serial_board(self): self.write_command([DgtCmd.DGT_SEND_UPDATE_NICE]) # Set the board update mode self.write_command([DgtCmd.DGT_SEND_VERSION]) # Get board version def _watchdog(self): if self.clock_lock and not self.is_pi: if time.time() - self.clock_lock > 2: logging.warning('(ser) clock is locked over 2secs') logging.debug('resending locked (ser) clock message [%s]', self.last_clock_command) self.clock_lock = False self.write_command(self.last_clock_command) self.write_command([DgtCmd.DGT_RETURN_SERIALNR]) # ask for this AFTER cause of - maybe - old board hardware def _open_bluetooth(self): # if self.bt_state == -1: # # only for jessie upwards # if path.exists('/usr/bin/bluetoothctl'): # self.bt_state = 0 # # # get rid of old rfcomm # if path.exists('/dev/rfcomm123'): # logging.debug('BT releasing /dev/rfcomm123') # subprocess.call(['rfcomm', 'release', '123']) # subprocess.call(['cat', '/dev/rfcomm123']) # Lucas # self.bt_current_device = -1 # self.bt_mac_list = [] # self.bt_name_list = [] # # logging.debug('BT starting bluetoothctl') # self.btctl = subprocess.Popen('/usr/bin/bluetoothctl', # stdin=subprocess.PIPE, # stdout=subprocess.PIPE, # stderr=subprocess.STDOUT, # universal_newlines=True, # shell=True) # # # set the O_NONBLOCK flag of file descriptor: # flags = fcntl(self.btctl.stdout, F_GETFL) # get current flags # fcntl(self.btctl.stdout, F_SETFL, flags | O_NONBLOCK) # # self.btctl.stdin.write("power on\n") # self.btctl.stdin.flush() # else: # state >= 0 so bluetoothctl is running # try: # check for new data from bluetoothctl # while True: # bt_byte = read(self.btctl.stdout.fileno(), 1).decode(encoding='UTF-8', errors='ignore') # self.bt_line += bt_byte # if bt_byte == '' or bt_byte == '\n': # break # except OSError: # time.sleep(0.1) # # # complete line # if '\n' in self.bt_line: # if False: # switch-case # pass # elif 'Changing power on succeeded' in self.bt_line: # self.bt_state = 1 # self.btctl.stdin.write("agent on\n") # self.btctl.stdin.flush() # elif 'Agent registered' in self.bt_line: # self.bt_state = 2 # self.btctl.stdin.write("default-agent\n") # self.btctl.stdin.flush() # elif 'Default agent request successful' in self.bt_line: # self.bt_state = 3 # self.btctl.stdin.write("scan on\n") # self.btctl.stdin.flush() # elif 'Discovering: yes' in self.bt_line: # self.bt_state = 4 # elif 'Pairing successful' in self.bt_line: # self.bt_state = 6 # logging.debug('BT pairing successful') # elif 'Failed to pair: org.bluez.Error.AlreadyExists' in self.bt_line: # self.bt_state = 6 # logging.debug('BT already paired') # elif 'Failed to pair' in self.bt_line: # # try the next # self.bt_state = 4 # logging.debug('BT pairing failed') # elif 'not available' in self.bt_line: # # remove and try the next # self.bt_state = 4 # self.bt_mac_list.remove(self.bt_mac_list[self.bt_current_device]) # self.bt_name_list.remove(self.bt_name_list[self.bt_current_device]) # self.bt_current_device -= 1 # logging.debug('BT pairing failed, unknown device') # elif ('DGT_BT_' in self.bt_line or 'PCS-REVII' in self.bt_line) and \ # ('NEW' in self.bt_line or 'CHG' in self.bt_line) and 'Device' in self.bt_line: # # New e-Board found add to list # try: # if not self.bt_line.split()[3] in self.bt_mac_list: # self.bt_mac_list.append(self.bt_line.split()[3]) # self.bt_name_list.append(self.bt_line.split()[4]) # logging.debug('BT found device: %s %s', self.bt_line.split()[3], self.bt_line.split()[4]) # except IndexError: # logging.error('BT wrong line [%s]', self.bt_line) # # clear the line # self.bt_line = '' # # # if 'Enter PIN code:' in self.bt_line: # if 'PIN code' in self.bt_line: # if 'DGT_BT_' in self.bt_name_list[self.bt_current_device]: # self.btctl.stdin.write("0000\n") # self.btctl.stdin.flush() # if 'PCS-REVII' in self.bt_name_list[self.bt_current_device]: # self.btctl.stdin.write("1234\n") # self.btctl.stdin.flush() # self.bt_line = '' # # if 'Confirm passkey' in self.bt_line: # self.btctl.stdin.write("yes\n") # self.btctl.stdin.flush() # self.bt_line = '' # # # if there are devices in the list try one # if self.bt_state == 4: # if len(self.bt_mac_list) > 0: # self.bt_state = 5 # self.bt_current_device += 1 # if self.bt_current_device >= len(self.bt_mac_list): # self.bt_current_device = 0 # logging.debug('BT pairing to: %s %s', # self.bt_mac_list[self.bt_current_device], # self.bt_name_list[self.bt_current_device]) # self.btctl.stdin.write('pair ' + self.bt_mac_list[self.bt_current_device] + "\n") # self.btctl.stdin.flush() # # # pair successful, try rfcomm # if self.bt_state == 6: # # now try rfcomm # self.bt_state = 7 # self.bt_rfcomm = subprocess.Popen('rfcomm connect 123 ' + self.bt_mac_list[self.bt_current_device], # stdin=subprocess.PIPE, # stdout=subprocess.PIPE, # stderr=subprocess.PIPE, # universal_newlines=True, # shell=True) # # # wait for rfcomm to fail or succeed # if self.bt_state == 7: # # rfcomm succeeded # if path.exists('/dev/rfcomm123'): # logging.debug('BT connected to: %s', self.bt_name_list[self.bt_current_device]) # if self._open_serial('/dev/rfcomm123'): # self.btctl.stdin.write("quit\n") # self.btctl.stdin.flush() # self.bt_name = self.bt_name_list[self.bt_current_device] # # self.bt_state = -1 # return True # # rfcomm failed # if self.bt_rfcomm.poll() is not None: # logging.debug('BT rfcomm failed') # self.btctl.stdin.write('remove ' + self.bt_mac_list[self.bt_current_device] + "\n") # self.bt_mac_list.remove(self.bt_mac_list[self.bt_current_device]) # self.bt_name_list.remove(self.bt_name_list[self.bt_current_device]) # self.bt_current_device -= 1 # self.btctl.stdin.flush() # self.bt_state = 4 return False def _open_serial(self, device: str): assert not self.serial, 'serial connection still active: %s' % self.serial try: self.serial = Serial(device, stopbits=STOPBITS_ONE, parity=PARITY_NONE, bytesize=EIGHTBITS, timeout=0.5) except SerialException: return False return True def _setup_serial_port(self): # def _success(device: str): # self.device = device # logging.debug('(ser) board connected to %s', self.device) # return True # # waitchars = ['/', '-', '\\', '|'] # # if self.watchdog_timer.is_running(): # logging.debug('watchdog timer is stopped now') # self.watchdog_timer.stop() # if self.serial: # return True # with self.lock: # if self.given_device: # if self._open_serial(self.given_device): # return _success(self.given_device) # else: # for file in listdir('/dev'): # if file.startswith('ttyACM') or file.startswith('ttyUSB') or file == 'rfcomm0': # dev = path.join('/dev', file) # if self._open_serial(dev): # return _success(dev) # if self._open_bluetooth(): # return _success('/dev/rfcomm123') # # # text = self.dgttranslate.text('N00_noboard', 'Board' + waitchars[self.wait_counter]) # bwait = 'Board' + waitchars[self.wait_counter] # text = Dgt.DISPLAY_TEXT(l='no e-' + bwait, m='no' + bwait, s=bwait, wait=True, beep=False, maxtime=0.1, # devs={'i2c', 'web'}) # DisplayMsg.show(Message.DGT_NO_EBOARD_ERROR(text=text)) # self.wait_counter = (self.wait_counter + 1) % len(waitchars) return False # dgtHw functions start def _wait_for_clock(self, func: str): has_to_wait = False counter = 0 while self.clock_lock: if not has_to_wait: has_to_wait = True logging.debug('(ser) clock is locked => waiting to serve: %s', func) time.sleep(0.1) counter = (counter + 1) % 30 if counter == 0: logging.warning('(ser) clock is locked over 3secs') if has_to_wait: logging.debug('(ser) clock is released now') def set_text_rp(self, text: str, beep: int): """Display a text on a Pi enabled Rev2.""" self._wait_for_clock('SetTextRp()') res = self.write_command([DgtCmd.DGT_CLOCK_MESSAGE, 0x0f, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_REV2_ASCII, text[0], text[1], text[2], text[3], text[4], text[5], text[6], text[7], text[8], text[9], text[10], beep, DgtClk.DGT_CMD_CLOCK_END_MESSAGE]) return res def set_text_3k(self, text: str, beep: int): """Display a text on a 3000 Clock.""" self._wait_for_clock('SetText3K()') res = self.write_command([DgtCmd.DGT_CLOCK_MESSAGE, 0x0c, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_ASCII, text[0], text[1], text[2], text[3], text[4], text[5], text[6], text[7], beep, DgtClk.DGT_CMD_CLOCK_END_MESSAGE]) return res def set_text_xl(self, text: str, beep: int, left_icons=ClockIcons.NONE, right_icons=ClockIcons.NONE): """Display a text on a XL clock.""" def _transfer(icons: ClockIcons): result = 0 if icons == ClockIcons.DOT: result = 0x01 if icons == ClockIcons.COLON: result = 0x02 return result self._wait_for_clock('SetTextXL()') icn = (_transfer(right_icons) & 0x07) | (_transfer(left_icons) << 3) & 0x38 res = self.write_command([DgtCmd.DGT_CLOCK_MESSAGE, 0x0b, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_DISPLAY, text[2], text[1], text[0], text[5], text[4], text[3], icn, beep, DgtClk.DGT_CMD_CLOCK_END_MESSAGE]) return res def set_and_run(self, lr: int, lh: int, lm: int, ls: int, rr: int, rh: int, rm: int, rs: int): """Set the clock with times and let it run.""" self._wait_for_clock('SetAndRun()') side = ClockSide.NONE if lr == 1 and rr == 0: side = ClockSide.LEFT if lr == 0 and rr == 1: side = ClockSide.RIGHT res = self.write_command([DgtCmd.DGT_CLOCK_MESSAGE, 0x0a, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_SETNRUN, lh, lm, ls, rh, rm, rs, side, DgtClk.DGT_CMD_CLOCK_END_MESSAGE]) return res def end_text(self): """Return the clock display to time display.""" self._wait_for_clock('EndText()') res = self.write_command([DgtCmd.DGT_CLOCK_MESSAGE, 0x03, DgtClk.DGT_CMD_CLOCK_START_MESSAGE, DgtClk.DGT_CMD_CLOCK_END, DgtClk.DGT_CMD_CLOCK_END_MESSAGE]) return res def light_squares_on_revelation(self, uci_move: str): """Light the Rev2 leds.""" if self.is_revelation and not self.disable_revelation_leds: # self._wait_for_clock('LIGHTon') logging.debug('(rev) leds turned on - move: %s', uci_move) fr_s = (8 - int(uci_move[1])) * 8 + ord(uci_move[0]) - ord('a') to_s = (8 - int(uci_move[3])) * 8 + ord(uci_move[2]) - ord('a') self.write_command([DgtCmd.DGT_SET_LEDS, 0x04, 0x01, fr_s, to_s, DgtClk.DGT_CMD_CLOCK_END_MESSAGE]) def clear_light_on_revelation(self): """Clear the Rev2 leds.""" if self.is_revelation and not self.disable_revelation_leds: # self._wait_for_clock('LIGHToff') logging.debug('(rev) leds turned off') self.write_command([DgtCmd.DGT_SET_LEDS, 0x04, 0x00, 0x40, 0x40, DgtClk.DGT_CMD_CLOCK_END_MESSAGE]) # dgtHw functions end def run(self): """NOT called from threading.Thread instead inside the __init__ function from hw.py.""" self.incoming_board_thread = Timer(0, self._process_incoming_board_forever) self.incoming_board_thread.start()
class DgtVr(DgtIface): def __init__(self, dgtserial, dgttranslate): super(DgtVr, self).__init__(dgtserial, dgttranslate) # virtual lib self.rt = None self.time_side = ClockSide.NONE # setup virtual clock main = 2 if dgtserial.is_pi else 0 DisplayMsg.show(Message.DGT_CLOCK_VERSION(main=main, sub=0, attached='virtual')) # (START) dgtserial class simulation def _runclock(self): if self.time_side == ClockSide.LEFT: h, m, s = self.time_left time_left = 3600*h + 60*m + s - 1 if time_left <= 0: print('Clock flag: left') self.rt.stop() self.time_left = hours_minutes_seconds(time_left) if self.time_side == ClockSide.RIGHT: h, m, s = self.time_right time_right = 3600*h + 60*m + s - 1 if time_right <= 0: print('Clock flag: right') self.rt.stop() self.time_right = hours_minutes_seconds(time_right) if self.maxtimer_running: print('Clock maxtime not run out') else: print('Clock time: {} - {}'.format(self.time_left, self.time_right)) DisplayMsg.show(Message.DGT_CLOCK_TIME(time_left=self.time_left, time_right=self.time_right)) # (END) dgtserial simulation class def display_move_on_clock(self, message): if self.enable_dgt_3000 or self.enable_dgt_pi: bit_board = chess.Board(message.fen) text = bit_board.san(message.move) text = self.dgttranslate.move(text) if self.enable_dgt_pi: text = text.rjust(8) if message.side == ClockSide.RIGHT else text.ljust(8) text = '{0:3d}.'.format(bit_board.fullmove_number) + text else: text = text.rjust(6) if message.side == ClockSide.RIGHT else text.ljust(6) text = '{0:2d}.'.format(bit_board.fullmove_number % 100) + text else: text = str(message.move) if message.side == ClockSide.RIGHT: text = text.rjust(6) logging.debug(text) print('Clock move: {} Beep: {}'. format(text, message.beep)) def display_text_on_clock(self, message): if self.enable_dgt_pi: text = message.l else: text = message.m if self.enable_dgt_3000 else message.s if text is None: text = message.m logging.debug(text) print('Clock text: {} Beep: {}'. format(text, message.beep)) def display_time_on_clock(self, force=False): if self.clock_running or force: print('Clock showing time again - running state: {}'. format(self.clock_running)) else: logging.debug('virtual clock isnt running - no need for endClock') def stop_clock(self): if self.rt: print('Clock time stopped at {} - {}'. format(self.time_left, self.time_right)) self.rt.stop() else: print('Clock not ready') self.clock_running = False def resume_clock(self, side): pass def start_clock(self, time_left, time_right, side): self.time_left = hours_minutes_seconds(time_left) self.time_right = hours_minutes_seconds(time_right) self.time_side = side print('Clock time started at {} - {} on {}'. format(self.time_left, self.time_right, side)) if self.rt: self.rt.stop() if side != ClockSide.NONE: self.rt = RepeatedTimer(1, self._runclock) self.rt.start() self.clock_running = (side != ClockSide.NONE) def light_squares_revelation_board(self, squares): pass def clear_light_revelation_board(self): pass
class DgtVr(DgtIface): def __init__(self, enable_revelation_leds): super(DgtVr, self).__init__(enable_revelation_leds) # virtual lib self.rt = None self.time_side = None # setup virtual clock DisplayMsg.show(Message.DGT_CLOCK_VERSION(main_version=0, sub_version=0, attached="virtual")) def startup(self): pass # (START) dgtserial class simulation def runclock(self): if self.time_side == 1: h, m, s = self.time_left time_left = 3600*h + 60*m + s - 1 if time_left <= 0: print('Clock flag: left') self.rt.stop() self.time_left = hours_minutes_seconds(time_left) else: h, m, s = self.time_right time_right = 3600*h + 60*m + s - 1 if time_right <= 0: print('Clock flag: right') self.rt.stop() self.time_right = hours_minutes_seconds(time_right) if self.timer_running: print('Clock duration not run out') else: print('Clock time: {} - {}'.format(self.time_left, self.time_right)) DisplayMsg.show(Message.DGT_CLOCK_TIME(time_left=self.time_left, time_right=self.time_right)) # (END) dgtserial simulation class def display_move_on_clock(self, move, fen, beep=False): if self.enable_dgt_3000: bit_board = chess.Board(fen) text = bit_board.san(move) else: text = str(move) logging.debug(text) print('Clock move: {} Beep: {}'. format(text, beep)) def display_text_on_clock(self, text, beep=False): logging.debug(text) print('Clock text: {} Beep: {}'. format(text, beep)) def stop_clock(self): if self.rt: print('Clock time stopped at {} - {}'. format(self.time_left, self.time_right)) self.rt.stop() else: print('Clock not ready') self.clock_running = False def start_clock(self, time_left, time_right, side): self.time_left = hours_minutes_seconds(time_left) self.time_right = hours_minutes_seconds(time_right) self.time_side = side print('Clock time started at {} - {}'. format(self.time_left, self.time_right)) if self.rt: self.rt.stop() self.rt = RepeatedTimer(1, self.runclock) self.rt.start() self.clock_running = (side != 0x04) def light_squares_revelation_board(self, squares): pass def clear_light_revelation_board(self): pass def end_clock(self, force=False): if self.clock_running or force: pass else: logging.debug('Clock isnt running - no need for endClock')