def firmware_check(s: Session) -> Union[dict, bool]:
    """
    Checks for the latest version of available firmware.
    Returns a {dict} if new firmware is available.
    {'version': '<new version>', 'download_url': '<url to download firmware>'}
    :param s: Requests Session object
    :type s: Session
    :return: Returns False if configured firmware is equal latest.
    :rtype: Union[dict, bool]
    """
    logger.info('CHECKING')
    r = request(s, get_cfg('SERVICE.SERVER_URL') + '/update/current', 'GET')
    if not r:
        logger.error('FAILED')
        return False
    rj = r.json()
    logger.debug(rj)
    if rj['version'] is not None and not rj['version'] == get_cfg(
            'FACTORY_FIRMWARE.FW_VERSION'):
        logger.info('New Firmware Available: Installed (%s), Available (%s)' %
                    (get_cfg('FACTORY_FIRMWARE.FW_VERSION'), rj['version']))
        return rj
    else:
        logger.info('LATEST ALREADY INSTALLED.')
        return False
示例#2
0
 def _lid_image(self, msg: dict) -> None:
     logger.info('capturing Lid Image')
     img = cam.capture(cam.GFCAM_LID)
     logger.info('uploading Lid Image')
     img_upload(self._session, img, msg)
     if get_cfg('LOGGING.SAVE_SENT_IMAGES'):
         logger.info('saving Lid Image')
         open('%s/%s.jpeg' % (get_cfg('LOGGING.DIR'), msg['id']),
              'wb').write(img)
示例#3
0
 def _head_image(self, msg: dict, settings: dict = None) -> None:
     if settings is not None and settings.get('HCil') > 0:
         img = 'HEAD_LASER_%s.jpg' % get_cfg('EMULATOR.MATERIAL_THICKNESS')
     else:
         img = 'HEAD_NO_LASER_%s.jpg' % get_cfg(
             'EMULATOR.MATERIAL_THICKNESS')
     with open('%s/%s' % (get_cfg('EMULATOR.IMAGE_SRC_DIR'), img),
               'rb') as f:
         img_upload(self._session, f.read(), msg)
     self._last_action = 'head_image'
示例#4
0
 def _head_image(self, msg: dict, settings: dict = None) -> None:
     logger.info('capturing Head Image')
     set_head_led_from_pulse(settings['HCil'])
     img = cam.capture(cam.GFCAM_HEAD, int(settings['HCex']),
                       int(settings['HCga']))
     head_all_led_off()
     logger.info('uploading Head Image')
     img_upload(self._session, img, msg)
     if get_cfg('LOGGING.SAVE_SENT_IMAGES'):
         logger.info('saving Head Image')
         open('%s/%s.jpeg' % (get_cfg('LOGGING.DIR'), msg['id']),
              'wb').write(img)
def get_session() -> Session:
    """
    Creates web api session object.
    :return: Requests Session object
    :rtype: Session
    """
    s = Session()
    set_cfg('SESSION.USER_AGENT',
            'OpenGlow/%s' % get_cfg('FACTORY_FIRMWARE.FW_VERSION'))
    s.headers.update({'user-agent': get_cfg('SESSION.USER_AGENT')})
    logger.debug('Returning object : %s' % s)
    return s
示例#6
0
 def _hunt(self, msg: dict) -> None:
     ZAxis.home()
     self._motion(msg)
     home_offset = int(get_cfg('MOTION.Z_HOME_OFFSET'))
     if home_offset is not None:
         logger.debug('moving z to home offset %s half steps' % home_offset)
         offset_dir = Dir.Pos if home_offset > 0 else Dir.Neg
         while home_offset != 0:
             ZAxis.step(offset_dir)
def load_motion(s: Session, url: str, out_file: str) -> Union[dict, bool]:
    """
    Downloads motion/print file, parses header
    :param s: Requests Session object
    :type s: Session
    :param url: Target URL
    :type url: str
    :param out_file: Where to write the results
    :type out_file: str
    :return: Header dict or False on failure
    :rtype: Union[dict, bool]
    """
    r = request(s, url, 'GET', stream=True)
    info = {
        'header_data': {},
        'header_len': 0,
        'size': 0,
        'run_time': None,
    }
    with open(out_file, 'wb') as f:
        res = r.iter_content(chunk_size=1024)
        puls_data = next(res)
        if puls_data[1:4].decode() != 'GF1':
            logger.error('received data not a GF puls file')
            return False
        info['header_len'] = _byte_to_int(puls_data[4:8]) - 8
        s = 8
        while s < (info['header_len'] + 8):
            info['header_data'][puls_data[s:s + 4].decode()] = _byte_to_int(
                puls_data[s + 4:s + 8])
            s += 8
        size = len(puls_data[s:])
        stat = decode_all_steps(puls_data[s:])
        # TODO: REMOVE THIS TEMP RAW WRITE - ONLY FOR QUICK TESTING
        base_file_name = '%s/%s' % (str(Path(
            get_cfg('LOGGING.FILE')).parent), time.strftime("%Y-%m-%d_%H%M%S"))
        raw = open(base_file_name + '.puls', 'wb')
        raw.write(puls_data)
        f.write(puls_data[s:])
        for chunk in res:
            if chunk:
                size = size + len(chunk)
                stat = decode_all_steps(chunk, stat)
                f.write(chunk)
                raw.write(chunk)
        raw.close()
        info['size'] = size
        info['run_time'] = timedelta(seconds=size /
                                     info['header_data']['STfr'])
        info['stats'] = stat
        raw = open(base_file_name + '.info', 'w')
        raw.write(
            json.dumps(info, sort_keys=True, indent=4, default=str) + '\n')
        raw.close()
        return info
 def __init__(self, q_rx: Queue, q_tx: Queue):
     """
     Class Initializer
     :param q_rx: WSS RX Message Queue
     :type q_rx: Queue
     :param q_tx: WSS TX Message Queue
     :type q_tx: Queue
     """
     self.msg_q_rx = q_rx
     self.msg_q_tx = q_tx
     self.stop = False
     self.ready = False
     self.ws = WebSocket(url=get_cfg('SERVICE.STATUS_SERVICE_URL') + '/' +
                         get_cfg('SESSION.WS_TOKEN'),
                         protocols=['glowforge'],
                         agent=get_cfg('SESSION.USER_AGENT'))
     self.ignore_events = [
         'ping', 'pong', 'poll', 'connecting', 'connected'
     ]
     Thread.__init__(self)
def send_report(q: Queue, msg: dict) -> None:
    """
    Send device settings report in the required format.
    :param q: {WSS message queue
    :type q: Queue
    :param msg: WSS Message
    :type msg: dict
    :return:
    """
    settings = ''
    logger.info("START")
    for item, setting in MACHINE_SETTINGS.items():
        value = '' if setting.default is None else str(setting.default)
        if setting.in_report:
            if item == 'SAid':
                value = msg['id']
            if item in _CONFIG_PROVIDERS:
                config_value = get_cfg(_CONFIG_PROVIDERS.get(item))
                if config_value is not None:
                    value = str(config_value)
            if item in _REGISTERED_PROVIDERS:
                value = str(_REGISTERED_PROVIDERS[item]())
            if setting.type is int:
                value = int(float(value))
            elif setting.type is str:
                value = '"{}"'.format(value)
            else:
                value = setting.type(value)
            settings += '"{}":{},'.format(item, value)

    # The service appears to bypass the homing process if we send it an empty settings value.
    # It is assumed that this prevents the machine from re-homing every time it loses and regains
    # the connection to the Glowforge service (a seemingly frequent occurrence).
    if not get_cfg('SETTINGS.SET') and not get_cfg('EMULATOR.BYPASS_HOMING'):
        send_wss_event(q, msg['id'], 'settings:completed',
                       data={'key': 'settings', 'value': '{"values":{%s}}' % settings[:-1]})
        set_cfg('SETTINGS.SET', True)
    else:
        send_wss_event(q, msg['id'], 'settings:completed', data={'key': 'settings', 'value': '{}'})
    logger.info("COMPLETE")
示例#10
0
 def _safe_to_move(self) -> bool:
     switches = self._sw_thread.all_switches()
     if not switches[InputSwitch.SW_DOORS]:
         logger.info('door open, unsafe to move')
         return False
     if cnc.state is not MachineState.IDLE:
         logger.info('machine is not idle, state: %s' % cnc.state.value)
         return False
     if temp_sensor.water_2.C > int(get_cfg('THERMAL.MAX_START_TEMP')):
         logger.info('machine temp is too high, temp: %s' %
                     temp_sensor.water_2.C)
         return False
     return True
示例#11
0
 def _initialize(self) -> None:
     logger.debug('initializing machine')
     self._sw_thread.start()
     # Setup machine
     set_lid_led(MACHINE_SETTINGS['LLvl'].default)
     TEC.off()
     WaterPump.on()
     WaterPump.set_heater(int(get_cfg('THERMAL.WATER_HEATER_PERCENT')))
     fans.reset()
     cnc.reset()
     ZAxis.reset()
     set_button_color(ButtonColor.OFF)
     cnc.enable()
示例#12
0
 def _motion_download(self, msg: dict):
     logger.info('DOWNLOADING')
     base_file_name = '%s/%s_%s' % (get_cfg('EMULATOR.MOTION_DL_DIR'),
                                    time.strftime("%Y-%m-%d_%H%M%S"),
                                    msg['action_type'])
     info = load_motion(self._session, msg['motion_url'],
                        base_file_name + '.puls')
     with open(base_file_name + '.info', 'w') as f:
         f.write(
             json.dumps(info, sort_keys=True, indent=4, default=str) + '\n')
     logger.debug('Header: %s' % info)
     logger.info('COMPLETE')
     return
示例#13
0
 def _lid_image(self, msg: dict) -> None:
     if self._homing_stage == 0 and self._last_action == 'motion':
         # Looks like were are homing again
         self._homing_stage = 1
     if self._homing_stage != 0:
         logger.info('HOME Step %s' % self._homing_stage)
         img = 'HOME_%s.jpg' % self._homing_stage
         self._homing_stage = self._homing_stage + 1 if self._homing_stage < 4 else 0
     else:
         img = 'LID_IMAGE.jpg'
     with open('%s/%s' % (get_cfg('EMULATOR.IMAGE_SRC_DIR'), img),
               'rb') as f:
         img_upload(self._session, f.read(), msg)
     self._last_action = 'lid_image'
def firmware_download(s: Session, fw: dict) -> bool:
    """
    Downloads latest firmware to directory set in config.
    :param s: Requests Session object
    :type s: Session
    :param fw: Dictionary returned by _api_firmware_check()
    :type fw: dict
    :return: {bool} True
    :rtype: bool
    """
    logger.info('DOWNLOADING')
    Path(get_cfg('FACTORY_FIRMWARE.DOWNLOAD_DIR')).mkdir(parents=True,
                                                         exist_ok=True)
    r = request(s, fw['download_url'], 'GET', stream=True)

    with open(
            '%s/glowforge-fw-v%s.fw' %
        (get_cfg('FACTORY_FIRMWARE.DOWNLOAD_DIR'), fw['version']), 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)
    logger.info('COMPLETE.')
    return True
def authenticate_machine(s: Session) -> bool:
    """
    Authenticates machine to service.
    POSTS serial number prefixed with an S and machine password to <server_url>/machines/sign_in.
    On success, server responds with JSON encoded 'ws_token' for WSS auth, and 'auth_token' that is
    added as a header to all session web API requests.
    Auth token header is 'Authorization': 'Bearer <auth_token>'
    :param s: Emulator session object.
    :type s: Session
    :return: Authentication result.
    :rtype: bool
    """
    logger.info('START')

    Retry.BACKOFF_MAX = 30
    retries = Retry(
        total=30,
        backoff_factor=5,
        method_whitelist=["GET"]
    )
    adapter = HTTPAdapter(max_retries=retries)
    s.mount("https://", adapter)

    r = request(s, get_cfg('SERVICE.SERVER_URL') + '/machines/sign_in', 'POST',
                data={'serial': 'S' + str(get_cfg('MACHINE.SERIAL')), 'password': get_cfg('MACHINE.PASSWORD')})
    if r:
        rj = r.json()
        logger.debug(rj)
        set_cfg('SESSION.WS_TOKEN', rj['ws_token'])
        set_cfg('SESSION.AUTH_TOKEN', rj['auth_token'])
        update_header(s, 'Authorization', "Bearer %s" % get_cfg('SESSION.AUTH_TOKEN'))
        logger.info('SUCCESS')
        return True
    else:
        logger.error('FAILED')
        return False
示例#16
0
 def __init__(self):
     """
     Initialize object
     """
     BaseMachine.__init__(self)
     # Track which step in the homing process we are at.
     # Example script has 5 saved head images, which are uploaded to the service in sequence to
     # emulate the homing process.  Files are located in _RESOURCES/IMG
     # HOME_1.jpg: Image of the head located at expected center position
     #             This image was taken with the head already in the calibrated position to speed up the process.
     # HOME_2.jpg: Image after first retreat move
     # HOME_3.jpg: Image after second retreat move
     # HOME_4.jpg: Image after third retreat move
     # LID_IMAGE.jpg: Image of bed with Proofgrade(tm) material
     #                This image is sent for every none-homing lid_image request
     self._homing_stage = 0 if get_cfg('EMULATOR.BYPASS_HOMING') else 1
     # Used to track the action just before a lid_image request. This is used to detect homing operations after
     # the initial start up (after a print job, for instance)
     self._last_action = None
def img_upload(s: Session, img: bytes, msg: dict) -> bool:
    """
    Uploads head/lid image
    :param s: Requests Session object
    :type s: Session
    :param img: Path to file to upload, or bytes object containing image
    :type img: bytes
    :param msg: WSS message
    :type msg: dict
    :return:
    :rtype: bool
    """
    logger.info('START')
    url = get_cfg('SERVICE.SERVER_URL') + '/api/machines/%s/%s' % (
        msg['action_type'], msg['id'])
    headers = {'Content-Type': 'image/jpeg'}
    r = request(s, url, 'POST', data=img, headers=headers)
    if not r:
        logger.debug(r)
        return False
    else:
        logger.debug(r.json)
    logger.info('COMPLETE')
    return True
示例#18
0
    def _motion(self, msg: dict) -> None:
        logger.info('start motion')
        if self._safe_to_move:
            cnc.clear_all()
            # Download puls file from service
            logger.info('loading motion file from %s' % msg['motion_url'])
            self._motion_stats = load_motion(self._session, msg['motion_url'],
                                             PULS_DEVICE)
            logger.info('motion stats: %s' % self._motion_stats)
            if msg['action_type'] == 'print':
                send_wss_event(self._q_msg_tx, msg['id'],
                               'print:download:completed')
                cnc.laser_latch(0)
                self._button_wait(msg)
                if not self._running_action_cancelled:
                    send_wss_event(self._q_msg_tx, msg['id'],
                                   'print:warmup:starting')

            # Configure for print, and wait for warm up
            if not self._running_action_cancelled:
                self._config_from_pulse('run',
                                        self._motion_stats['header_data'])
                if msg['action_type'] == 'print':
                    if get_cfg('MOTION.WARM_UP_DELAY'):
                        sleep(int(get_cfg('MOTION.WARM_UP_DELAY')))

            # Run motion job
            if not self._running_action_cancelled:
                if msg['action_type'] == 'print':
                    logger.info('start temps: %s' % str(temp_sensor.all))
                    send_wss_event(self._q_msg_tx, msg['id'], 'print:running')
                self._run_loop()
                cnc.laser_latch(1)
                pos = cnc.position
                logger.info(
                    'end positions (actual/expected): X (%s/%s), Y (%s/%s), Z (%s/%s)'
                    % (
                        pos.x.steps,
                        self._motion_stats['stats']['XEND'],
                        pos.y.steps,
                        self._motion_stats['stats']['YEND'],
                        pos.z.steps,
                        self._motion_stats['stats']['ZEND'],
                    ))
                logger.info('motion bytes actual:%s, expected: %s' %
                            (pos.bytes.processed, self._motion_stats['size']))
                if msg['action_type'] == 'print':
                    logger.info('end print temps: %s' % str(temp_sensor.all))

            # Cool down for prints
            if msg['action_type'] == 'print':
                self._return_home()
                logger.info('start cool down')
                self._config_from_pulse('cool_down',
                                        self._motion_stats['header_data'])
                if get_cfg('MOTION.COOL_DOWN_DELAY'):
                    sleep(int(get_cfg('MOTION.COOL_DOWN_DELAY')))
                logger.info('end cool-down temps: %s' % str(temp_sensor.all))

            # Config for idle
            logger.info('start idle')
            self._config_from_pulse('idle', self._motion_stats['header_data'])
            pos = cnc.position
            logger.info('end positions (%s, %s, %s)' %
                        (pos.x.steps, pos.y.steps, pos.z.steps))
        logger.info('end motion')