Send Command - Bad Request

Macedo2 years ago

Hi, I'm trying to send commands saved by api, but I always get BAD Request as a response. I already downloaded the sample app and managed to run it, but it has the same error.
This is the function:

          @POST("/api/commands/send")
          Call<Command> sendCommand(@Body Command command);

This is the json I send via post:

          {"attributes":{"frequency":5},"description":"5 sec","deviceId":143,"type":"positionPeriodic"}

This is the error I get:

          APIService.ServiceException: Bad Request

I'm asking for help, because I've already done all the tests and comparisons, but I haven't found the error.

Anton Tananaev2 years ago

What's the detailed error message in the payload?

Macedo2 years ago

In Android Studio, only "APIService.ServiceException: Bad Request"

This is the code (Traccar Manager example in git hub), I did tests pointing to https://demo4.traccar.org/ and got the same error.

public class SendCommandFragment extends Fragment {

    class CommandTypeDataHolder {
        private String type;
        private String name;

        public CommandTypeDataHolder(String type, String name) {
            this.type = type;
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    class CommandTypeAdapter extends ArrayAdapter<CommandTypeDataHolder> {

        CommandTypeAdapter(List<CommandTypeDataHolder> items) {
            super(getActivity(), R.layout.list_item, android.R.id.text1, items);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup container) {
            View view = super.getView(position, convertView, container);

            CommandTypeDataHolder commandTypeDataHolder = getItem(position);
            TextView popupText = (TextView) view.findViewById(android.R.id.text1);
            popupText.setText(commandTypeDataHolder.name);
            view.setTag(commandTypeDataHolder.type);

            return view;
        }
    }

    private static Map<String, Integer> i10nMapping = new HashMap<>();

    static {
        i10nMapping.put(Command.TYPE_ALARM_ARM, R.string.command_alarm_arm);
        i10nMapping.put(Command.TYPE_ALARM_DISARM, R.string.command_alarm_disarm);
        i10nMapping.put(Command.TYPE_ENGINE_STOP, R.string.command_engine_stop);
        i10nMapping.put(Command.TYPE_ENGINE_RESUME, R.string.command_engine_resume);
        i10nMapping.put(Command.TYPE_POSITION_PERIODIC, R.string.command_position_periodic);
    }

    public static final String EXTRA_DEVICE_ID = "deviceId";

    private Spinner commandsSpinner;
    private LinearLayout frequencyGroup;
    private EditText frequencyEditText;
    private LinearLayout unitGroup;
    private Spinner unitSpinner;
    private View sendButton;

    @Nullable
    @Override
    public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_send_command, container, false);

        commandsSpinner = (Spinner) view.findViewById(R.id.commands);
        frequencyGroup = (LinearLayout) view.findViewById(R.id.frequencyGroup);
        frequencyEditText = (EditText) view.findViewById(R.id.frequency);
        unitGroup = (LinearLayout) view.findViewById(R.id.unitGroup);
        unitSpinner = (Spinner) view.findViewById(R.id.unit);
        sendButton = (Button) view.findViewById(R.id.button_send);

        frequencyGroup.setVisibility(View.GONE);
        unitGroup.setVisibility(View.GONE);

        commandsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                if(view.getTag().toString().equals(Command.TYPE_POSITION_PERIODIC)) {
                    frequencyGroup.setVisibility(View.VISIBLE);
                    unitGroup.setVisibility(View.VISIBLE);
                } else {
                    frequencyGroup.setVisibility(View.GONE);
                    unitGroup.setVisibility(View.GONE);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                frequencyGroup.setVisibility(View.GONE);
                unitGroup.setVisibility(View.GONE);
            }
        });

        final long deviceId = getActivity().getIntent().getExtras().getLong(EXTRA_DEVICE_ID);

        final MainApplication application = (MainApplication) getActivity().getApplication();
        final WebService service = application.getService();
        service.putCommands(deviceId).enqueue(new WebServiceCallback<List<CommandType>>(getContext()) {
            @Override
            public void onSuccess(Response<List<CommandType>> response) {
                List<CommandTypeDataHolder> commandTypeDataHolders = new ArrayList<>();

                for (CommandType commandType: response.body()) {
                    String name = getI10nString(commandType.getType());
                    commandTypeDataHolders.add(new CommandTypeDataHolder(commandType.getType(), name));
                }

                commandsSpinner.setAdapter(new CommandTypeAdapter(commandTypeDataHolders));
            }
        });

        sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Command command = new Command();
                command.setDeviceId(deviceId);
                command.setType((String) commandsSpinner.getSelectedView().getTag());

                if (command.getType().equals(Command.TYPE_POSITION_PERIODIC)) {
                    int value = 0;
                    try {
                        value = Integer.parseInt(frequencyEditText.getText().toString());
                    } catch (NumberFormatException e) {
                        Toast.makeText(getContext(), R.string.error_invalid_frequency, Toast.LENGTH_LONG).show();
                        return;
                    }
                    int multiplier = getResources().getIntArray(R.array.unit_values)[unitSpinner.getSelectedItemPosition()];
                    command.set(Command.KEY_FREQUENCY, value * multiplier);
                }

                final MainApplication application = (MainApplication) getActivity().getApplication();
                final WebService service = application.getService();
                service.sendCommand(command).enqueue(new WebServiceCallback<Command>(getContext()) {
                    @Override
                    public void onSuccess(Response<Command> response) {
                        Toast.makeText(getContext(), R.string.command_sent, Toast.LENGTH_LONG).show();
                    }

                    @Override
                    public void onFailure(Call<Command> call, Throwable t) {
                        super.onFailure(call, t);
                        Toast.makeText(getContext(), (new Gson().toJson(command) + "\n" + t.toString()),Toast.LENGTH_LONG).show();
                    }

                });

                getActivity().finish();
            }
        });

        return view;
    }

    private String getI10nString(String key) {
        String result = key;

        Integer resId = i10nMapping.get(key);
        if(resId != null) {
            try {
                CharSequence nameCharSequence = getContext().getResources().getText(resId);
                result = nameCharSequence.toString();
            } catch (Resources.NotFoundException e) {
                Log.w(SendCommandFragment.class.getSimpleName(), e);
            }
        }

        return result;
    }
}
Anton Tananaev2 years ago

You have to check the payload.

Macedo2 years ago

Hello, I don't understand, my payload is:

 {"attributes":{"frequency":5},"description":"5 sec","deviceId":143,"type":"positionPeriodic"}

I get this same bad request error in the traccar-manager-android-native app. I am not able to find the error in the tracer log for more information and in the debug mode of Android Studio neither. Is there a way to activate the more complete log to identify the error, or can you give me some more guidance? Thanks

Anton Tananaev2 years ago

I'm asking for response payload, not the request. I'm not asking for logs either.

Macedo2 years ago

I didn't get the payload of the response, I just get this in onFailure(Call<Command> call, Throwable t):
.APIService.ServiceException: Bad Request

Anton Tananaev2 years ago

There is always a payload if there's a "Bad Request" error. If you don't know how to see the payload, you should read the documentation on the tool you're using to send API requests and figure out how to do it. Once you know the payload, we can continue this conversation.

Macedo2 years ago

Hello, thanks to you I just learned a little more about Android Studio. What are you asking for this?

Account has limit sending commands - SecurityException (PermissionsManager:290 < CommandResource: 69 < ...)
Anton Tananaev2 years ago

It means what it says. Commands are restricted for the account or the server.

Macedo2 years ago

One more piece of information, with the same user logged in via the browser, the command is sent normally.

Macedo2 years ago

please, be clearer, I did not find this information that you posted, much less understood what you meant, especially when the same user manages to send the commands through the browser, but logged in through the app it does not send them. When you say restricted to the server, I understood even less and searching the traccar forum and the documentation, I didn't find anything about it. Thank you very much for your time.

Anton Tananaev2 years ago

There must be some difference.

Hello!
Bad request means that the server does not recognize the command because it is missing necessary data, or it is not in the correct format or data is missing.

try this way:

{id: 0, attributes: {"frequency":5}, deviceId: 143, type: "positionPeriodic", textChannel: false, description: "5 sec"}
  • A good tip when you have doubts about the API is to perform the process in Traccar WEB and inspect the output.
Macedo2 years ago

Problem solved.

When sending the command requesting the saved commands for the desired device, as a response I get:

{
  "id": 7,
  "attributes": {
    "frequency": 5
  },
  "deviceId": 128,
  "type": "positionPeriodic",
  "textChannel": false,
  "description": "5 sec"
}

[
  {
    "id": 7,
    "attributes": {
      "frequency": 5
    },
    "deviceId": 0,
    "type": "positionPeriodic",
    "textChannel": false,
    "description": "5 sec"
  },
  {
    "id": 8,
    "attributes": {
      "frequency": 10
    },
    "deviceId": 0,
    "type": "positionPeriodic",
    "textChannel": false,
    "description": "10 sec"
  },
  {
    "id": 9,
    "attributes": {
      "frequency": 15
    },
    "deviceId": 0,
    "type": "positionPeriodic",
    "textChannel": false,
    "description": "15 sec"
  }
]

The id field needs to be saved to be sent correctly later.
The code had to be changed to store the id and when sending the command to be executed, send it with the same id, getting my request like this:

{
  "id": 7,
  "attributes": {
    "frequency": 5
  },
  "deviceId": 130,
  "type": "positionPeriodic",
  "textChannel": false,
  "description": "5 sec"
}

If you wish, I can send you the Traccar Manager Native code corrected with this implementation and working in the new version of android studio. Just let me know how to send it to you.