| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/factory/camera/cpp/fidl.h> |
| #include <fuchsia/images/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <filesystem> |
| #include <iostream> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/lib/fxl/command_line.h" |
| #include "src/lib/fxl/log_settings_command_line.h" |
| |
| enum Target { |
| CONTROLLER, |
| ISP, |
| }; |
| |
| enum Command { |
| START_STREAM, |
| STOP_STREAM, |
| CAPTURE_FRAMES, |
| DISPLAY_TO_SCREEN, |
| GET_OTP_DATA, |
| GET_SENSOR_TEMPERATURE, |
| SET_AWB_MODE, |
| SET_AE_MODE, |
| SET_EXPOSURE, |
| SET_SENSOR_MODE, |
| SET_TEST_PATTERN_MODE, |
| SET_BYPASS_MODE, |
| }; |
| |
| constexpr std::string_view kDeviceDir = "/dev/class/isp"; |
| constexpr std::string_view kFlag = "command"; |
| |
| // Controller Commands |
| constexpr std::string_view kCommand2 = "CaptureFrames"; |
| constexpr std::string_view kCommand3 = "DisplayToScreen"; |
| // ISP Commands |
| constexpr std::string_view kCommand4 = "GetOtpData"; |
| constexpr std::string_view kCommand5 = "GetSensorTemperature"; |
| constexpr std::string_view kCommand6 = "SetAWBMode"; |
| constexpr std::string_view kCommand7 = "SetAEMode"; |
| constexpr std::string_view kCommand8 = "SetExposure"; |
| constexpr std::string_view kCommand9 = "SetSensorMode"; |
| constexpr std::string_view kCommand10 = "SetTestPatternMode"; |
| constexpr std::string_view kCommand11 = "SetBypassMode"; |
| |
| fpromise::result<Command, zx_status_t> StrToCommand(const std::string& str) { |
| // Controller |
| if (str == kCommand2) |
| return fpromise::ok(CAPTURE_FRAMES); |
| if (str == kCommand3) |
| return fpromise::ok(DISPLAY_TO_SCREEN); |
| // ISP |
| if (str == kCommand4) |
| return fpromise::ok(GET_OTP_DATA); |
| if (str == kCommand5) |
| return fpromise::ok(GET_SENSOR_TEMPERATURE); |
| if (str == kCommand6) |
| return fpromise::ok(SET_AWB_MODE); |
| if (str == kCommand7) |
| return fpromise::ok(SET_AE_MODE); |
| if (str == kCommand8) |
| return fpromise::ok(SET_EXPOSURE); |
| if (str == kCommand9) |
| return fpromise::ok(SET_SENSOR_MODE); |
| if (str == kCommand10) |
| return fpromise::ok(SET_TEST_PATTERN_MODE); |
| if (str == kCommand11) |
| return fpromise::ok(SET_BYPASS_MODE); |
| return fpromise::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| // TODO(https://fxbug.dev/42135937): The varius std::stoi() calls can fail here and cause a crash, be sure to |
| // sanitize input. |
| zx_status_t RunCommand(fuchsia::factory::camera::ControllerPtr& controller, |
| fuchsia::factory::camera::IspPtr& isp, const Command command, |
| const std::vector<std::string> args, |
| fit::function<void(const std::string)> exit_on_success_cb, |
| fit::function<void(zx_status_t, const std::string)> exit_on_failure_cb) { |
| switch (command) { |
| case CAPTURE_FRAMES: { |
| if (args.size() > 1) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Accepts at most one arg"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| controller->CaptureFrames( |
| args.size() == 0 ? "" : args[0], |
| [success_cb = std::move(exit_on_success_cb), failure_cb = std::move(exit_on_failure_cb)]( |
| zx_status_t capture_frames_status, fuchsia::images::ImageInfo info) { |
| if (capture_frames_status != ZX_OK) { |
| failure_cb(capture_frames_status, std::string{kCommand2}); |
| return; |
| } |
| success_cb(std::string{kCommand2}); |
| }); |
| break; |
| } |
| case GET_OTP_DATA: |
| isp->GetOtpData( |
| [success_cb = std::move(exit_on_success_cb), failure_cb = std::move(exit_on_failure_cb)]( |
| zx_status_t get_otp_status, size_t byte_count, zx::vmo otp_data) { |
| if (get_otp_status != ZX_OK) { |
| failure_cb(get_otp_status, std::string{kCommand4}); |
| return; |
| } |
| uint8_t buf[byte_count]; |
| otp_data.read(buf, 0, byte_count); |
| for (auto byte : buf) { |
| std::cout << byte; |
| } |
| success_cb(std::string{kCommand4}); |
| }); |
| break; |
| case GET_SENSOR_TEMPERATURE: |
| isp->GetSensorTemperature( |
| [success_cb = std::move(exit_on_success_cb), failure_cb = std::move(exit_on_failure_cb)]( |
| zx_status_t get_sensor_temperature_status, int32_t temperature) { |
| if (get_sensor_temperature_status != ZX_OK) { |
| failure_cb(get_sensor_temperature_status, std::string{kCommand5}); |
| return; |
| } |
| success_cb(std::string{kCommand10}); |
| }); |
| break; |
| case SET_AWB_MODE: { |
| if (args.size() != 2) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Accepts exactly two args"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| isp->SetAWBMode(static_cast<fuchsia::factory::camera::WhiteBalanceMode>(std::stoi(args[0])), |
| std::stoi(args[1]), [success_cb = std::move(exit_on_success_cb)]() { |
| success_cb(std::string{kCommand6}); |
| }); |
| break; |
| } |
| case SET_AE_MODE: |
| if (args.size() != 1) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Accepts exactly one arg"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| isp->SetAEMode( |
| static_cast<fuchsia::factory::camera::ExposureMode>(std::stoi(args[0])), |
| [success_cb = std::move(exit_on_success_cb)]() { success_cb(std::string{kCommand7}); }); |
| break; |
| case SET_EXPOSURE: { |
| if (args.size() != 3) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Accepts exactly three args"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| isp->SetExposure( |
| std::stof(args[0]), std::stof(args[1]), std::stof(args[2]), |
| [success_cb = std::move(exit_on_success_cb)]() { success_cb(std::string{kCommand8}); }); |
| break; |
| } |
| case SET_SENSOR_MODE: |
| if (args.size() != 1) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Accepts exactly one arg"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| isp->SetSensorMode( |
| static_cast<uint16_t>(std::stoul(args[0])), |
| [success_cb = std::move(exit_on_success_cb)]() { success_cb(std::string{kCommand9}); }); |
| break; |
| case SET_TEST_PATTERN_MODE: |
| if (args.size() != 1) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Accepts exactly one arg"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| isp->SetTestPatternMode( |
| static_cast<uint16_t>(std::stoul(args[0])), |
| [success_cb = std::move(exit_on_success_cb)]() { success_cb(std::string{kCommand10}); }); |
| break; |
| case SET_BYPASS_MODE: |
| if (args.size() != 1) { |
| FX_PLOGS(ERROR, ZX_ERR_INVALID_ARGS) << "Accepts exactly one arg"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| isp->SetBypassMode( |
| static_cast<uint16_t>(std::stoul(args[0])), |
| [success_cb = std::move(exit_on_success_cb)]() { success_cb(std::string{kCommand11}); }); |
| break; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| int main(int argc, char* argv[]) { |
| auto command_line = fxl::CommandLineFromArgcArgv(argc, argv); |
| if (!fxl::SetLogSettingsFromCommandLine(command_line, {"camera-factory-cli"})) { |
| FX_LOGS(ERROR); |
| return EXIT_FAILURE; |
| } |
| |
| if (!command_line.HasOption(kFlag)) { |
| FX_LOGS(ERROR) << "User must specify 'command' flag."; |
| return EXIT_FAILURE; |
| } |
| |
| std::string command; |
| command_line.GetOptionValue(kFlag, &command); |
| |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| auto exit_on_success_cb = [&loop](const std::string& command) { |
| FX_PLOGS(INFO, ZX_OK) << "camera-factory-cli ran with command '" << command << "'"; |
| loop.Quit(); |
| }; |
| auto exit_on_failure_cb = [&loop](zx_status_t status, const std::string& command) { |
| FX_PLOGS(INFO, status) << "camera-factory-cli failed with command '" << command << "'."; |
| loop.Quit(); |
| }; |
| auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory(); |
| |
| zx_status_t status; |
| // Parse command. |
| auto command_result = StrToCommand(command); |
| if (command_result.is_error()) { |
| FX_LOGS(ERROR) << "Command not valid for specified target."; |
| return EXIT_FAILURE; |
| } |
| |
| // Connect to the camera-factory Controller. |
| fuchsia::factory::camera::ControllerHandle controller_handle; |
| status = context->svc()->Connect(controller_handle.NewRequest()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to request camera-factory Controller service."; |
| return EXIT_FAILURE; |
| } |
| |
| fuchsia::factory::camera::ControllerPtr controller; |
| status = controller.Bind(std::move(controller_handle), loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to get camera-factory Controller pointer."; |
| return EXIT_FAILURE; |
| } |
| |
| // Connect to the ISP. |
| fuchsia::factory::camera::IspPtr isp; |
| for (auto const& dir_entry : std::filesystem::directory_iterator{kDeviceDir}) { |
| const char* path = dir_entry.path().c_str(); |
| status = fdio_service_connect(path, isp.NewRequest(loop.dispatcher()).TakeChannel().release()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to get handle for device at " << path; |
| return EXIT_FAILURE; |
| } |
| break; |
| } |
| |
| // Error handling |
| auto shutdown_cb = [&controller, &isp]() { |
| controller = nullptr; |
| isp = nullptr; |
| }; |
| |
| controller.set_error_handler([&loop, &shutdown_cb](zx_status_t status) { |
| FX_PLOGS(ERROR, status) << "camera-factory-cli Controller channel closing due to error."; |
| shutdown_cb(); |
| loop.Quit(); |
| }); |
| |
| isp.set_error_handler([&loop, &shutdown_cb](zx_status_t status) { |
| FX_PLOGS(ERROR, status) << "camera-factory-cli Isp channel closing due to error."; |
| shutdown_cb(); |
| loop.Quit(); |
| }); |
| |
| // Execute command. |
| status = RunCommand(controller, isp, command_result.value(), command_line.positional_args(), |
| exit_on_success_cb, exit_on_failure_cb); |
| |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "camera-factory-cli loop never started"; |
| shutdown_cb(); |
| return EXIT_FAILURE; |
| } |
| |
| // Wait for exit callback. |
| FX_LOGS(INFO) << "camera-factory-cli loop started."; |
| loop.Run(); |
| FX_LOGS(INFO) << "camera-factory-cli loop quit after '" << command << "' ran."; |
| |
| shutdown_cb(); |
| |
| return EXIT_SUCCESS; |
| } |