Ejemplo n.º 1
0
def settings_from_params(
        type_def: type[Generic],
        settings: list[Parameter],
        overrides: Optional[list[Parameter]] = None) -> Settings:
    """Constructs instance of Settings from two arrays of parameters (scene
    settings and project overrides).

    :param type_def:
    :param settings:
    :param overrides:
    :return:
    """

    if overrides is None:
        overrides = []

    final: dict[str, Parameter] = {s.name: s for s in settings}
    for over in overrides:
        if over.name not in final:
            raise Arcor2Exception("Invalid override.")

        if over.type != final[over.name].type:
            raise Arcor2Exception("Type mismatch.")

        final[over.name] = over

    settings_def = get_settings_def(type_def)
    settings_data: dict[str, Any] = {}

    settings_def_type_hints = get_type_hints(settings_def.__init__)

    for s in final.values():

        try:
            setting_def = settings_def_type_hints[s.name]
        except KeyError as e:
            raise Arcor2Exception(f"Unknown property {s.name}.") from e

        try:
            if issubclass(setting_def, JsonSchemaMixin):
                settings_data[s.name] = json.loads(s.value)
            else:
                settings_data[s.name] = setting_def(json.loads(s.value))
        except (json.JsonException, ValidationError) as e:
            raise Arcor2Exception(
                f"Parameter {s.name} has invalid value.") from e

    try:
        settings_cls = settings_def.from_dict(settings_data)
    except (ValueError, ValidationError) as e:
        raise Arcor2Exception("Validation of settings failed.") from e

    return settings_cls
Ejemplo n.º 2
0
    def _call_rpc(self, req: rpc.common.RPC.Request,
                  resp_type: Type[RR]) -> RR:

        self._ws.send(req.to_json())

        # wait for RPC response, put any incoming event into the queue
        while True:
            try:
                recv_dict = json.loads(self._ws.recv())
            except websocket.WebSocketTimeoutException:
                raise ARServerClientException("RPC timeouted.")

            if not isinstance(recv_dict, dict):
                self._logger.debug(f"Invalid data received: {recv_dict}")
                continue

            if "response" in recv_dict:
                break
            elif "event" in recv_dict:
                self._event_queue.put(self.event_mapping[
                    recv_dict["event"]].from_dict(recv_dict))

        try:
            resp = resp_type.from_dict(recv_dict)
        except ValidationError as e:
            self._logger.error(
                f"Request: {req.to_dict()}, response: {recv_dict}.")
            raise ARServerClientException(
                "RPC response validation failed.") from e

        assert req.id == resp.id
        assert req.request == resp.response
        return resp
Ejemplo n.º 3
0
    def get_event(
        self,
        drop_everything_until: Optional[Type[events.Event]] = None
    ) -> events.Event:
        """Returns queued events (if any) or wait until some event arrives.

        :param drop_everything_until: Drop any event until there is one of required type.
        :return:
        """

        try:
            evt = self._event_queue.get_nowait()
        except Empty:
            try:
                recv_dict = json.loads(self._ws.recv())
            except websocket.WebSocketTimeoutException:
                raise ARServerClientException("Timeouted.")

            if not isinstance(recv_dict, dict):
                raise ARServerClientException(
                    f"Invalid data received: {recv_dict}")

            if "event" not in recv_dict:
                raise ARServerClientException(
                    f"Expected event, got: {recv_dict}")
            evt = self.event_mapping[recv_dict["event"]].from_dict(recv_dict)

        if drop_everything_until and not isinstance(evt,
                                                    drop_everything_until):
            return self.get_event(drop_everything_until)

        return evt
Ejemplo n.º 4
0
def _handle_response(resp: requests.Response) -> None:
    """Raises exception if there is something wrong with the response.

    :param resp:
    :return:
    """

    if resp.status_code < 400:
        return

    decoded_content = resp.content.decode()

    # here we try to handle different cases
    try:
        cont = json.loads(decoded_content)
    except json.JsonException:
        # response contains invalid JSON
        raise RestHttpException(decoded_content, error_code=resp.status_code)

    if isinstance(cont, str):  # just plain text
        raise RestHttpException(cont, error_code=resp.status_code)
    elif isinstance(cont, dict):  # this could be WebApiError

        try:
            err = WebApiError.from_dict(cont)
        except ValidationError:
            raise RestHttpException(str(cont), error_code=resp.status_code)

        raise WebApiException(err.message,
                              error_code=resp.status_code,
                              web_api_error=err)
Ejemplo n.º 5
0
    def str_from_value(self) -> str:

        val = json.loads(self.value)

        if not isinstance(val, str):
            raise Arcor2Exception("Value should be string.")

        return val
Ejemplo n.º 6
0
def check_parameter(parameter: Parameter) -> None:

    # TODO check using (some) plugin
    from arcor2 import json

    val = json.loads(parameter.value)

    # however, analysis in get_dataclass_params() can handle also (nested) dataclasses, etc.
    if not isinstance(val, (int, float, str, bool)):
        raise Arcor2Exception("Only basic types are supported so far.")
Ejemplo n.º 7
0
    def _param_value_list(cls, param: ActionParameter) -> List[str]:

        lst = json.loads(param.value)

        if not isinstance(lst, list):
            raise ParameterPluginException(
                "Parameter value should be list of references to action points."
            )

        return lst
Ejemplo n.º 8
0
def program_src(type_defs: TypesDict,
                project: CProject,
                scene: CScene,
                add_logic: bool = True) -> str:

    tree = empty_script_tree(project.id, add_main_loop=add_logic)

    # get object instances from resources object
    main = find_function("main", tree)
    last_assign = find_last_assign(main)
    for obj in scene.objects:
        add_import(tree,
                   "object_types." + humps.depascalize(obj.type),
                   obj.type,
                   try_to_import=False)
        last_assign += 1
        main.body.insert(last_assign,
                         object_instance_from_res(obj.name, obj.id, obj.type))

    # TODO temporary solution - should be (probably) handled by plugin(s)
    from arcor2 import json

    # TODO should we put there even unused parameters?
    for param in project.parameters:
        val = json.loads(param.value)

        aval: Optional[expr] = None

        if isinstance(val, bool):  # subclass of int
            aval = NameConstant(value=val, kind=None)
        elif isinstance(val, (int, float)):
            aval = Num(n=val, kind=None)
        elif isinstance(val, str):
            aval = Str(s=val, kind="")

        if not aval:
            raise Arcor2Exception(
                f"Unsupported project parameter type ({param.type}) or value ({val})."
            )

        last_assign += 1
        main.body.insert(
            last_assign,
            Assign(  # TODO use rather AnnAssign?
                targets=[Name(id=param.name, ctx=Store())],
                value=aval,
                type_comment=None),
        )

    if add_logic:
        add_logic_to_loop(type_defs, tree, scene, project)

    return SCRIPT_HEADER + tree_to_str(tree)
Ejemplo n.º 9
0
def ws_thread() -> None:  # TODO use (refactored) arserver client

    global package_info
    global package_state
    global action_state_before
    global action_state_after
    assert ws

    event_mapping: dict[str, type[events.Event]] = {
        evt.__name__: evt
        for evt in EVENTS
    }
    rpc_mapping: dict[str, type[arcor2_rpc.common.RPC]] = {
        rpc.__name__: rpc
        for rpc in EXPOSED_RPCS
    }

    while True:

        data = json.loads(
            ws.recv())  # TODO handle WebSocketConnectionClosedException

        if not isinstance(data, dict):
            continue

        if "event" in data:

            evt = event_mapping[data["event"]].from_dict(data)

            if isinstance(evt, PackageInfo):
                package_info = evt.data
            elif isinstance(evt, PackageState):
                package_state = evt.data

                if package_state.state == PackageState.Data.StateEnum.RUNNING:
                    exception_messages.clear()

            elif isinstance(evt, ProjectException):
                exception_messages.append(evt.data.message)
            elif isinstance(evt, ActionStateBefore):
                # assume ActionStateBefore event to be fired always before ActionStateAfter
                # thus ActionStateBefore here belong to previous action - unset it
                action_state_after = None
                action_state_before = evt.data
            elif isinstance(evt, ActionStateAfter):
                action_state_after = evt.data

        elif "response" in data:
            resp = rpc_mapping[data["response"]].Response.from_dict(data)
            rpc_responses[resp.id].put(resp)
Ejemplo n.º 10
0
def _handle_response(resp: requests.Response) -> None:
    """Raises exception if there is something wrong with the response.

    :param resp:
    :return:
    """

    if resp.status_code >= 400:

        decoded_content = resp.content.decode()

        # here we try to handle different cases
        try:
            raise RestHttpException(str(json.loads(decoded_content)),
                                    error_code=resp.status_code)
        except json.JsonException:
            # response contains invalid JSON
            raise RestHttpException(decoded_content,
                                    error_code=resp.status_code)
Ejemplo n.º 11
0
    def parameter_value(cls, type_defs: TypesDict, scene: CScene,
                        project: CProject, action_id: str,
                        parameter_id: str) -> Enum:

        action = project.action(action_id)
        param = action.parameter(parameter_id)
        obj_id, action_type = action.parse_type()
        obj_type_name = scene.object(obj_id).type
        try:
            obj_type = type_defs[obj_type_name]
        except KeyError:
            raise ParameterPluginException(
                f"Unknown object type {obj_type_name}.")

        try:
            method = getattr(obj_type, action_type)
        except AttributeError:
            raise ParameterPluginException(
                f"Object type {obj_type_name} does not have method {action_type}."
            )

        try:
            ttype = get_type_hints(method)[param.name]
        except KeyError:
            raise ParameterPluginException(
                f"Method {obj_type}/{method.__name__} does not have parameter {param.name}."
            )

        if not issubclass(ttype, cls.type()):
            raise ParameterPluginException(
                f"Type {ttype.__name__} is not subclass of {cls.type().__name__}."
            )

        try:
            return ttype(json.loads(param.value))
        except (ValueError, json.JsonException):
            raise ParameterPluginException(
                f"Parameter {parameter_id} of action {action.name} has invalid value."
            )
Ejemplo n.º 12
0
def read_dc_from_zip(zip_file: zipfile.ZipFile, file_name: str,
                     cls: Type[T]) -> T:

    return cls.from_dict(
        humps.decamelize(json.loads(read_str_from_zip(zip_file, file_name))))
Ejemplo n.º 13
0
def test_project_const(start_processes: None, ars: ARServer) -> None:

    event(ars, events.c.ShowMainScreen)

    assert ars.call_rpc(
        rpc.s.NewScene.Request(uid(),
                               rpc.s.NewScene.Request.Args("Test scene")),
        rpc.s.NewScene.Response).result

    scene_data = event(ars, events.s.OpenScene).data
    assert scene_data
    scene = scene_data.scene

    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.s.AddObjectToScene.Request(
            uid(),
            rpc.s.AddObjectToScene.Request.Args("random_actions",
                                                RandomActions.__name__)),
        rpc.s.AddObjectToScene.Response,
    ).result

    obj = event(ars, events.s.SceneObjectChanged).data
    assert obj

    # ------------------------------------------------------------------------------------------------------------------

    assert ars.call_rpc(
        rpc.p.NewProject.Request(
            uid(), rpc.p.NewProject.Request.Args(scene.id, "Project name")),
        rpc.p.NewProject.Response,
    ).result

    open_project = event(ars, events.p.OpenProject).data
    assert open_project

    proj = open_project.project

    # test project parameters added by the arserver
    d: Dict[str, common.ProjectParameter] = {
        par.name: par
        for par in proj.parameters
    }
    assert len(d) == 2
    assert d["scene_id"].type == "string"
    assert json.loads(d["scene_id"].value) == scene.id
    assert d["project_id"].type == "string"
    assert json.loads(d["project_id"].value) == proj.id

    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.p.AddProjectParameter.Request(
            uid(),
            rpc.p.AddProjectParameter.Request.Args("min_time", "double",
                                                   json.dumps(0.45))),
        rpc.p.AddProjectParameter.Response,
    ).result

    c1 = event(ars, events.p.ProjectParameterChanged).data
    assert c1

    assert not ars.call_rpc(
        rpc.p.AddProjectParameter.Request(
            uid(),
            rpc.p.AddProjectParameter.Request.Args("min_time", "double",
                                                   json.dumps(0.62))),
        rpc.p.AddProjectParameter.Response,
    ).result

    assert not ars.call_rpc(  # attempt to update without lock
        rpc.p.UpdateProjectParameter.Request(
            uid(),
            rpc.p.UpdateProjectParameter.Request.Args(
                c1.id, name="min_time_updated")),
        rpc.p.UpdateProjectParameter.Response,
    ).result

    # ------------------------------------------------------------------------------------------------------------------
    # the user opens a menu and then closes it without actually changing anything

    lock_object(ars, c1.id)

    assert ars.call_rpc(
        rpc.p.UpdateProjectParameter.Request(
            uid(),
            rpc.p.UpdateProjectParameter.Request.Args(c1.id,
                                                      name="min_time_1"),
            dry_run=True),
        rpc.p.UpdateProjectParameter.Response,
    ).result

    assert ars.call_rpc(
        rpc.p.UpdateProjectParameter.Request(
            uid(),
            rpc.p.UpdateProjectParameter.Request.Args(c1.id,
                                                      name="min_time_2"),
            dry_run=True),
        rpc.p.UpdateProjectParameter.Response,
    ).result

    unlock_object(ars, c1.id)

    # ------------------------------------------------------------------------------------------------------------------

    lock_object(ars, c1.id)

    assert ars.call_rpc(
        rpc.p.UpdateProjectParameter.Request(
            uid(),
            rpc.p.UpdateProjectParameter.Request.Args(
                c1.id, name="min_time_updated")),
        rpc.p.UpdateProjectParameter.Response,
    ).result

    c1u = event(ars, events.p.ProjectParameterChanged).data
    assert c1u

    event(ars, events.lk.ObjectsUnlocked)

    assert c1u.id == c1.id
    assert c1.name != c1u.name
    assert c1.type == c1u.type

    # ------------------------------------------------------------------------------------------------------------------
    # try to add and remove

    assert ars.call_rpc(
        rpc.p.AddProjectParameter.Request(
            uid(),
            rpc.p.AddProjectParameter.Request.Args("min_time_2", "double",
                                                   json.dumps(0.62))),
        rpc.p.AddProjectParameter.Response,
    ).result

    c2 = event(ars, events.p.ProjectParameterChanged).data
    assert c2

    assert ars.call_rpc(
        rpc.p.RemoveProjectParameter.Request(
            uid(), rpc.p.RemoveProjectParameter.Request.Args(c2.id)),
        rpc.p.RemoveProjectParameter.Response,
    ).result

    c2e = event(ars, events.p.ProjectParameterChanged)
    assert c2e.data
    assert c2e.data.id == c2.id
    assert c2e.change_type == c2e.Type.REMOVE

    # ------------------------------------------------------------------------------------------------------------------
    # attempt to add a constant with duplicate name

    assert not ars.call_rpc(
        rpc.p.AddProjectParameter.Request(
            uid(),
            rpc.p.AddProjectParameter.Request.Args(c1u.name, "double",
                                                   json.dumps(0.62))),
        rpc.p.AddProjectParameter.Response,
    ).result

    # ------------------------------------------------------------------------------------------------------------------

    assert ars.call_rpc(
        rpc.p.AddActionPoint.Request(
            uid(), rpc.p.AddActionPoint.Request.Args("ap1",
                                                     common.Position())),
        rpc.p.AddActionPoint.Response,
    ).result

    ap = event(ars, events.p.ActionPointChanged).data
    assert ap is not None

    assert ars.call_rpc(
        rpc.p.AddAction.Request(
            uid(),
            rpc.p.AddAction.Request.Args(
                ap.id,
                "test_action",
                f"{obj.id}/{RandomActions.random_double.__name__}",
                [
                    common.ActionParameter(
                        "range_min",
                        common.ActionParameter.TypeEnum.PROJECT_PARAMETER,
                        json.dumps(c1.id)),
                    common.ActionParameter("range_max", "double", "0.55"),
                ],
                [common.Flow(outputs=["random_value"])],
            ),
        ),
        rpc.p.AddAction.Response,
    ).result

    action = event(ars, events.p.ActionChanged).data
    assert action

    assert not ars.call_rpc(
        rpc.p.RemoveProjectParameter.Request(
            uid(), rpc.p.RemoveProjectParameter.Request.Args(c1.id)),
        rpc.p.RemoveProjectParameter.Response,
    ).result

    # ------------------------------------------------------------------------------------------------------------------
    # try to execute action using constant parameter

    assert ars.call_rpc((rpc.s.StartScene.Request(uid())),
                        rpc.s.StartScene.Response).result

    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Starting
    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Started

    assert ars.call_rpc(
        rpc.p.ExecuteAction.Request(
            uid(), rpc.p.ExecuteAction.Request.Args(action.id)),
        rpc.p.ExecuteAction.Response)

    event(ars, events.a.ActionExecution)
    res = event(ars, events.a.ActionResult)

    assert res.data
    assert res.data.action_id == action.id
    assert not res.data.error

    assert ars.call_rpc((rpc.s.StopScene.Request(uid())),
                        rpc.s.StopScene.Response).result
    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Stopping
    assert event(ars, events.s.SceneState
                 ).data.state == events.s.SceneState.Data.StateEnum.Stopped

    assert ars.call_rpc(
        rpc.p.RemoveAction.Request(uid(), rpc.p.IdArgs(action.id)),
        rpc.p.RemoveAction.Response).result
    assert event(ars, events.p.ActionChanged).data

    assert ars.call_rpc(
        rpc.p.RemoveProjectParameter.Request(
            uid(), rpc.p.RemoveProjectParameter.Request.Args(c1.id)),
        rpc.p.RemoveProjectParameter.Response,
    ).result
    event(ars, events.p.ProjectParameterChanged)
Ejemplo n.º 14
0
def check_logic_item(
    obj_types: ObjectTypeDict, scene: CachedScene, parent: LogicContainer, logic_item: LogicItem
) -> None:
    """Checks if newly added/updated ProjectLogicItem is ok.

    :param parent:
    :param logic_item:
    :return:
    """

    action_ids = parent.action_ids()

    if logic_item.start == LogicItem.START and logic_item.end == LogicItem.END:
        raise Arcor2Exception("This does not make sense.")

    if logic_item.start != LogicItem.START:

        start_action_id, start_flow = logic_item.parse_start()

        if start_action_id == logic_item.end:
            raise Arcor2Exception("Start and end can't be the same.")

        if start_action_id not in action_ids:
            raise Arcor2Exception("Logic item has unknown start.")

        if start_flow != "default":  # TODO enum
            raise Arcor2Exception("Only flow 'default' is supported so far.'")

    if logic_item.end != LogicItem.END:

        if logic_item.end not in action_ids:
            raise Arcor2Exception("Logic item has unknown end.")

    if logic_item.condition is not None:

        what = logic_item.condition.parse_what()
        action = parent.action(what.action_id)  # action that produced the result which we depend on here
        flow = action.flow(what.flow_name)
        try:
            flow.outputs[what.output_index]
        except IndexError:
            raise Arcor2Exception(f"Flow {flow.type} does not have output with index {what.output_index}.")

        action_meta = find_object_action(obj_types, scene, action)

        try:
            return_type = action_meta.returns[what.output_index]
        except IndexError:
            raise Arcor2Exception(f"Invalid output index {what.output_index} for action {action_meta.name}.")

        return_type_plugin = plugin_from_type_name(return_type)

        if not return_type_plugin.COUNTABLE:
            raise Arcor2Exception(f"Output of type {return_type} can't be branched.")

        # TODO for now there is only support for bool
        if return_type_plugin.type() != bool:
            raise Arcor2Exception("Unsupported condition type.")

        # check that condition value is ok, actual value is not interesting
        # TODO perform this check using plugin
        from arcor2 import json

        if not isinstance(json.loads(logic_item.condition.value), bool):
            raise Arcor2Exception("Invalid condition value.")

    for existing_item in parent.logic:

        if existing_item.id == logic_item.id:  # item is updated
            continue

        if logic_item.start == logic_item.START and existing_item.start == logic_item.START:
            raise Arcor2Exception("START already defined.")

        if logic_item.start == existing_item.start:

            if None in (logic_item.condition, existing_item.condition):
                raise Arcor2Exception("Two junctions has the same start action without condition.")

            # when there are more logical connections from A to B, their condition values must be different
            if logic_item.condition == existing_item.condition:
                raise Arcor2Exception("Two junctions with the same start should have different conditions.")

        if logic_item.end == existing_item.end:
            if logic_item.start == existing_item.start:
                raise Arcor2Exception("Junctions can't have the same start and end.")
Ejemplo n.º 15
0
    def _value_from_json(cls, value: str) -> Any:

        try:
            return json.loads(value)
        except json.JsonException as e:
            raise ParameterPluginException(f"Invalid value '{value}'.") from e
Ejemplo n.º 16
0
async def read_proc_stdout() -> None:

    global PACKAGE_STATE_EVENT
    global ACTION_EVENT
    global ACTION_ARGS_EVENT
    global PACKAGE_INFO_EVENT
    global RUNNING_PACKAGE_ID

    logger.info("Reading script stdout...")

    assert PROCESS is not None
    assert PROCESS.stdout is not None
    assert RUNNING_PACKAGE_ID is not None

    await package_state(PackageState(PackageState.Data(PackageState.Data.StateEnum.RUNNING, RUNNING_PACKAGE_ID)))

    printed_out: List[str] = []

    while process_running():
        try:
            stdout = await PROCESS.stdout.readuntil()
        except asyncio.exceptions.IncompleteReadError:
            break

        decoded = stdout.decode("utf-8")
        stripped = decoded.strip()

        try:
            data = json.loads(stripped)
        except json.JsonException:
            printed_out.append(decoded)
            logger.error(decoded.strip())
            continue

        if not isinstance(data, dict) or "event" not in data:
            logger.error("Strange data from script: {}".format(data))
            continue

        try:
            evt = EVENT_MAPPING[data["event"]].from_dict(data)
        except ValidationError as e:
            logger.error("Invalid event: {}, error: {}".format(data, e))
            continue

        if isinstance(evt, PackageState):
            evt.data.package_id = RUNNING_PACKAGE_ID
            await package_state(evt)
            continue
        elif isinstance(evt, PackageInfo):
            PACKAGE_INFO_EVENT = evt

        await send_to_clients(evt)

    PACKAGE_INFO_EVENT = None

    if PROCESS.returncode:

        if printed_out:

            # TODO remember this (until another package is started) and send it to new clients?
            last_line = printed_out[-1].strip()

            try:
                exception_type, message = last_line.split(":", 1)
            except ValueError:
                exception_type, message = "Unknown", last_line

            await send_to_clients(ProjectException(ProjectException.Data(message, exception_type)))

            with open("traceback-{}.txt".format(time.strftime("%Y%m%d-%H%M%S")), "w") as tb_file:
                tb_file.write("".join(printed_out))

        else:
            logger.warn(
                f"Process ended with non-zero return code ({PROCESS.returncode}), but didn't printed out anything."
            )

    await package_state(PackageState(PackageState.Data(PackageState.Data.StateEnum.STOPPED, RUNNING_PACKAGE_ID)))
    logger.info(f"Process finished with returncode {PROCESS.returncode}.")

    RUNNING_PACKAGE_ID = None
Ejemplo n.º 17
0
    def _add_logic(container: Container,
                   current_action: Action,
                   super_container: Optional[Container] = None) -> None:

        # more paths could lead  to the same action, so it might be already added
        # ...this is easier than searching the tree
        if current_action.id in added_actions:
            logger.debug(
                f"Action {current_action.name} already added, skipping.")
            return

        inputs, outputs = project.action_io(current_action.id)
        logger.debug(
            f"Adding action {current_action.name}, with {len(inputs)} input(s) and {len(outputs)} output(s)."
        )

        act = current_action.parse_type()
        ac_obj = scene.object(act.obj_id).name

        args: List[AST] = []

        # TODO make sure that the order of parameters is correct / re-order
        for param in current_action.parameters:

            if param.type == ActionParameter.TypeEnum.LINK:

                parsed_link = param.parse_link()
                parent_action = project.action(parsed_link.action_id)

                # TODO add support for tuples
                assert len(parent_action.flow(FlowTypes.DEFAULT).outputs
                           ) == 1, "Only one result is supported atm."
                assert parsed_link.output_index == 0

                res_name = parent_action.flow(FlowTypes.DEFAULT).outputs[0]

                # make sure that the result already exists
                if parent_action.id not in added_actions:
                    raise SourceException(
                        f"Action {current_action.name} attempts to use result {res_name} "
                        f"of subsequent action {parent_action.name}.")

                args.append(Name(id=res_name, ctx=Load()))

            elif param.type == ActionParameter.TypeEnum.PROJECT_PARAMETER:
                args.append(
                    Name(id=project.parameter(param.str_from_value()).name,
                         ctx=Load()))
            else:

                plugin = plugin_from_type_name(param.type)

                args.append(
                    plugin.parameter_ast(type_defs, scene, project,
                                         current_action.id, param.name))

                list_of_imp_tup = plugin.need_to_be_imported(
                    type_defs, scene, project, current_action.id, param.name)

                if list_of_imp_tup:
                    # TODO what if there are two same names?
                    for imp_tup in list_of_imp_tup:
                        add_import(tree,
                                   imp_tup.module_name,
                                   imp_tup.class_name,
                                   try_to_import=False)

        add_method_call(
            container.body,
            ac_obj,
            act.action_type,
            args,
            [keyword(arg="an", value=Str(s=current_action.name, kind=""))],
            current_action.flow(FlowTypes.DEFAULT).outputs,
        )

        added_actions.add(current_action.id)

        if not outputs:
            raise SourceException(
                f"Action {current_action.name} has no outputs.")
        elif len(outputs) == 1:
            output = outputs[0]

            if output.end == output.END:
                # TODO this is just temporary (while there is while loop), should be rather Return()
                container.body.append(Continue())
                return

            seq_act = project.action(output.end)
            seq_act_inputs, _ = project.action_io(seq_act.id)
            if len(seq_act_inputs
                   ) > 1:  # the action belongs to a different block

                if seq_act.id in added_actions:
                    return

                logger.debug(
                    f"Action {seq_act.name} going to be added to super_container."
                )

                # test if this is the correct super_container -> count distance (number of blocks) to the START
                blocks_to_start: Dict[str, int] = {}

                for inp in seq_act_inputs:
                    parsed_start = inp.parse_start()
                    pact = project.action(parsed_start.start_action_id)
                    blocks_to_start[pact.id] = _blocks_to_start(pact)
                winner = min(blocks_to_start, key=blocks_to_start.get
                             )  # type: ignore  # TODO what is wrong with it?

                # TODO if blocks_to_start is cached somewhere, the second part of the condition is useless
                # it might happen that there are two different ways with the same distance
                if winner == current_action.id or all(
                        value == list(blocks_to_start.values())[0]
                        for value in blocks_to_start.values()):
                    assert super_container is not None
                    _add_logic(super_container, seq_act)
                return

            logger.debug(f"Sequential action: {seq_act.name}")
            _add_logic(container, seq_act, super_container)

        else:

            root_if: Optional[If] = None

            # action has more outputs - each output should have condition
            for idx, output in enumerate(outputs):
                if not output.condition:
                    raise SourceException("Missing condition.")

                # TODO use parameter plugin (action metadata will be needed - to get the return types)
                # TODO support for other countable types
                # ...this will only work for booleans
                from arcor2 import json

                condition_value = json.loads(output.condition.value)
                comp = NameConstant(value=condition_value, kind=None)
                what = output.condition.parse_what()
                output_name = project.action(what.action_id).flow(
                    what.flow_name).outputs[what.output_index]

                cond = If(
                    test=Compare(left=Name(id=output_name, ctx=Load()),
                                 ops=[Eq()],
                                 comparators=[comp]),
                    body=[],
                    orelse=[],
                )

                if idx == 0:
                    root_if = cond
                    container.body.append(root_if)
                    logger.debug(f"Adding branch for: {condition_value}")
                else:
                    assert isinstance(root_if, If)
                    root_if.orelse.append(cond)

                if output.end == output.END:
                    cond.body.append(
                        Continue())  # TODO should be rather return
                    continue

                _add_logic(cond, project.action(output.end), container)
Ejemplo n.º 18
0
    async def handle_message(msg: str) -> None:

        try:
            data = json.loads(msg)
        except json.JsonException as e:
            logger.error(f"Invalid data: '{msg}'.")
            logger.debug(e)
            return

        if not isinstance(data, dict):
            logger.error(f"Invalid data: '{data}'.")
            return

        if "request" in data:  # ...then it is RPC

            req_type = data["request"]

            try:
                rpc_cls, rpc_cb = rpc_dict[req_type]
            except KeyError:
                logger.error(f"Unknown RPC request: {data}.")
                return

            assert req_type == rpc_cls.__name__

            try:
                req = rpc_cls.Request.from_dict(data)
            except ValidationError as e:
                logger.error(f"Invalid RPC: {data}, error: {e}")
                return
            except Arcor2Exception as e:
                # this might happen if e.g. some dataclass does additional validation of values in its __post_init__
                try:
                    await client.send(
                        rpc_cls.Response(data["id"], False,
                                         messages=[str(e)]).to_json())
                    logger.debug(e, exc_info=True)
                except (KeyError, websockets.exceptions.ConnectionClosed):
                    pass
                return

            else:

                try:
                    rpc_start = time.monotonic()
                    resp = await rpc_cb(req, client)
                    rpc_dur = time.monotonic() - rpc_start
                    if rpc_dur > MAX_RPC_DURATION:
                        logger.warn(
                            f"{req.request} callback took {rpc_dur:.3f}s.")

                except Arcor2Exception as e:
                    logger.debug(e, exc_info=True)
                    resp = rpc_cls.Response(req.id, False, [str(e)])
                else:
                    if resp is None:  # default response
                        resp = rpc_cls.Response(req.id, True)
                    else:
                        assert isinstance(resp, rpc_cls.Response)
                        resp.id = req.id

            try:
                await client.send(resp.to_json())
            except websockets.exceptions.ConnectionClosed:
                return

            if logger.level == LogLevel.DEBUG:

                # Silencing of repetitive log messages
                # ...maybe this could be done better and in a more general way using logging.Filter?

                now = time.monotonic()
                if req.request not in req_last_ts:
                    req_last_ts[req.request] = deque()

                while req_last_ts[req.request]:
                    if req_last_ts[req.request][0] < now - 5.0:
                        req_last_ts[req.request].popleft()
                    else:
                        break

                req_last_ts[req.request].append(now)
                req_per_sec = len(req_last_ts[req.request]) / 5.0

                if req_per_sec > 2:
                    if req.request not in ignored_reqs:
                        ignored_reqs.add(req.request)
                        logger.debug(
                            f"Request of type {req.request} will be silenced.")
                elif req_per_sec < 1:
                    if req.request in ignored_reqs:
                        ignored_reqs.remove(req.request)

                if req.request not in ignored_reqs:
                    logger.debug(f"RPC request: {req}, result: {resp}")

        elif "event" in data:  # ...event from UI

            assert event_dict

            try:
                event_cls, event_cb = event_dict[data["event"]]
            except KeyError as e:
                logger.error(f"Unknown event type: {e}.")
                return

            try:
                event = event_cls.from_dict(data)
            except ValidationError as e:
                logger.error(f"Invalid event: {data}, error: {e}")
                return

            await event_cb(event, client)

        else:
            logger.error(f"unsupported format of message: {data}")
Ejemplo n.º 19
0
def test_project_ap_rpcs(start_processes: None, ars: ARServer) -> None:

    upload_def(Box, BoxModel(Box.__name__, 1, 2, 3))

    event(ars, events.c.ShowMainScreen)

    assert ars.call_rpc(
        rpc.s.NewScene.Request(get_id(), rpc.s.NewScene.Request.Args("Test scene")), rpc.s.NewScene.Response
    ).result

    assert len(event(ars, events.o.ChangedObjectTypes).data) == 1

    scene_data = event(ars, events.s.OpenScene).data
    assert scene_data
    scene = scene_data.scene

    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.s.AddObjectToScene.Request(get_id(), rpc.s.AddObjectToScene.Request.Args("box", Box.__name__, Pose())),
        rpc.s.AddObjectToScene.Response,
    ).result

    obj = event(ars, events.s.SceneObjectChanged).data
    assert obj

    # ------------------------------------------------------------------------------------------------------------------

    assert ars.call_rpc(
        rpc.p.NewProject.Request(get_id(), rpc.p.NewProject.Request.Args(scene.id, "Project name")),
        rpc.p.NewProject.Response,
    ).result

    event(ars, events.s.SceneSaved)
    event(ars, events.p.OpenProject)
    event(ars, events.s.SceneState)

    assert ars.call_rpc(
        rpc.p.AddActionPoint.Request(get_id(), rpc.p.AddActionPoint.Request.Args("parent_ap", Position())),
        rpc.p.AddActionPoint.Response,
    ).result

    parent_ap_evt = event(ars, events.p.ActionPointChanged)

    assert ars.call_rpc(
        rpc.p.AddActionPoint.Request(
            get_id(), rpc.p.AddActionPoint.Request.Args("child_ap", Position(-1), parent_ap_evt.data.id)
        ),
        rpc.p.AddActionPoint.Response,
    ).result

    child_ap_evt = event(ars, events.p.ActionPointChanged)
    assert child_ap_evt.data.parent == parent_ap_evt.data.id

    lock_object(ars, child_ap_evt.data.id)

    assert ars.call_rpc(
        rpc.p.AddActionPointOrientation.Request(
            get_id(), rpc.p.AddActionPointOrientation.Request.Args(child_ap_evt.data.id, Orientation())
        ),
        rpc.p.AddActionPointOrientation.Response,
    ).result

    ori = event(ars, events.p.OrientationChanged)

    assert ars.call_rpc(
        rpc.p.AddAction.Request(
            get_id(),
            rpc.p.AddAction.Request.Args(
                child_ap_evt.data.id,
                "act_name",
                f"{obj.id}/{Box.update_pose.__name__}",
                [ActionParameter("new_pose", PosePlugin.type_name(), json.dumps(ori.data.id))],
                [Flow()],
            ),
        ),
        rpc.p.AddAction.Response,
    ).result

    event(ars, events.p.ActionChanged)

    unlock_object(ars, child_ap_evt.data.id)

    ars.event_mapping[ActionChanged.__name__] = ActionChanged

    assert ars.call_rpc(
        rpc.p.CopyActionPoint.Request(get_id(), rpc.p.CopyActionPoint.Request.Args(parent_ap_evt.data.id)),
        rpc.p.CopyActionPoint.Response,
    ).result

    new_parent_ap = event(ars, events.p.ActionPointChanged)
    assert not new_parent_ap.data.parent

    new_child_ap = event(ars, events.p.ActionPointChanged)
    assert new_child_ap.data.parent == new_parent_ap.data.id

    new_ori = event(ars, events.p.OrientationChanged)
    assert new_ori.parent_id == new_child_ap.data.id

    # with events.p.ActionChanged it would return only BareAction (without parameters)
    new_action = event(ars, ActionChanged)
    ars.event_mapping[ActionChanged.__name__] = events.p.ActionChanged
    assert new_action.parent_id == new_child_ap.data.id

    # Pose parameter (orientation id) should be updated now
    assert len(new_action.data.parameters) == 1
    assert json.loads(new_action.data.parameters[0].value) == new_ori.data.id