JurajNyiri / pytapo

Python library for communication with Tapo Cameras
MIT License
277 stars 58 forks source link

Fix: KeyError in handling 'getPresetConfig' #83

Closed Exelord closed 9 months ago

Exelord commented 9 months ago

This will fix issue with C420 cameras where getPresetConfig is not present.

Fixes: https://github.com/JurajNyiri/HomeAssistant-Tapo-Control/issues/455

Exelord commented 9 months ago

Hi,

This is not hardcoding. Is is fixing the invalid property access. Sure, the root might will require a fix as well:

{
  result: {
    responses: [
      {
        result: {
          method: "getChildDeviceList",
          error_code: 0,
          result: {
            start_index: 0,
            child_device_list: [
              {
                device_id: "XXXX",
                parent_device_id: "XXXX",
                device_model: "C420",
                device_name: "C420 1.0",
                alias: "Camera 1",
                avatar: "camera c420",
                sw_ver: "1.2.14 Build 20230829 rel.65193",
                hw_ver: "1.0",
                mac: "XXXXXX",
                device_type: "SMART.IPCAMERA",
                category: "camera",
                hw_id: "C5B155806880C301179651EA0F9ECF88",
                oem_id: "FC5FF5303F725AF5CDE2115728A0AA86",
                system_time: 1700224375,
                led_status: "off",
                updating: False,
                online: True,
                firmware_status: "OK",
                power: "BATTERY",
                battery_percent: 75,
                battery_charging: "NO",
                rssi: -36,
                dev_name: "Tapo Smart Camera",
                ipaddr: "XXXX",
                short_addr: 0,
                ext_addr: "XXXXX",
                status: "configured",
                onboarding_timestamp: 1693486347,
                battery_voltage: 0,
                battery_temperature: 21,
                battery_installed: 1,
                low_battery: False,
                power_save_mode: "off",
                power_save_status: "off",
                uptime: 0,
                cam_uptime: 0,
                subg_cam_rssi: 0,
                subg_hub_rssi: 0,
                region: "EU",
              },
              {
                device_id: "XXXX",
                parent_device_id: "XXXXX",
                device_model: "C420",
                device_name: "C420 1.0",
                alias: "Camera 2",
                avatar: "camera c420",
                sw_ver: "1.2.14 Build 20230829 rel.65193",
                hw_ver: "1.0",
                mac: "XXXXX",
                device_type: "SMART.IPCAMERA",
                category: "camera",
                hw_id: "C5B155806880C301179651EA0F9ECF88",
                oem_id: "FC5FF5303F725AF5CDE2115728A0AA86",
                system_time: 1700224375,
                led_status: "off",
                updating: False,
                online: True,
                firmware_status: "OK",
                power: "BATTERY",
                battery_percent: 75,
                battery_charging: "NO",
                rssi: -60,
                dev_name: "Tapo Smart Camera",
                ipaddr: "XXXXXX",
                short_addr: 0,
                ext_addr: "XXXXXXXX",
                status: "configured",
                onboarding_timestamp: 1693556741,
                battery_voltage: 0,
                battery_temperature: 20,
                battery_installed: 1,
                low_battery: False,
                power_save_mode: "off",
                power_save_status: "off",
                uptime: 0,
                cam_uptime: 0,
                subg_cam_rssi: 0,
                subg_hub_rssi: 0,
                region: "EU",
              },
            ],
            sum: 2,
          },
        },
        method: "getAudioConfig",
        error_code: 0,
      },
    ],
  },
  error_code: 0,
};
JurajNyiri commented 9 months ago

I understand your fix but it can be done better and more universally so that we cover any other keys as well, the key should always exist due to:

        returnData = {}
        # todo finish on child
        i = 0
        for result in results["result"]["responses"]:
            if (
                "error_code" in result and result["error_code"] == 0
            ) and "result" in result:
                returnData[result["method"]] = result["result"]
            else:
                if "method" in result:
                    returnData[result["method"]] = False
                else:  # some cameras are not returning method for error messages
                    returnData[requestData["params"]["requests"][i]["method"]] = False
            i += 1

above. You will need to initiate the pytapo library with a childID parameter set and then post the result of the results variable.

childID = "" #device_id you redacted above
tapo = Tapo(host, "admin", password_cloud, childID=childID)

Depending on the result of this, we will see how did the camera respond and then adjust the code above so that returnData["getPresetConfig"] always exists and is False if it is not supported.

Exelord commented 9 months ago

OK, this is a log from child device

{
  error_code: 0,
  result: {
    responses: [
      {
        method: "getDeviceInfo",
        result: {
          device_info: {
            basic_info: {
              device_model: "C420",
              device_name: "C420 1.0",
              device_alias: "Camera 1",
              avatar: "camera c420",
              sw_version: "1.2.14 Build 20230829 rel.65193",
              hw_version: "1.0",
              mac: "48-22-54-85-78-F7",
              device_type: "SMART.IPCAMERA",
              dev_id: "802181B40592B8CEB22F73D5C69398AC21015636",
              oem_id: "FC5FF5303F725AF5CDE2115728A0AA86",
              system_time: 1700225614,
              led_status: "off",
              updating: False,
              online: True,
              status: "configured",
              resolution: "2560*1440",
              camera_switch: "on",
              firmware_status: "OK",
              parent_device_id: "802D281A666D037A534574ECD058892720D58D73",
              parent_link_type: "wifi",
              power: "BATTERY",
              battery_percent: 75,
              battery_charging: "NO",
              battery_overheated: False,
              low_battery: False,
              power_save_mode: "off",
              c_opt: [0, 1],
              a_type: 3,
              last_activity_timestamp: 1699278137,
              rssi: -36,
            },
          },
        },
        error_code: 0,
      },
      {
        method: "getDetectionConfig",
        result: {
          motion_detection: {
            motion_det: {
              enabled: "off",
              sensitivity: "high",
              digital_sensitivity: "100",
            },
            region_info: [],
          },
        },
        error_code: 0,
      },
      {
        method: "getPersonDetectionConfig",
        result: {
          people_detection: {
            detection: { enabled: "on", sensitivity: "100" },
          },
        },
        error_code: 0,
      },
      {
        method: "getVehicleDetectionConfig",
        result: {
          vehicle_detection: {
            detection: { enabled: "off", sensitivity: "60" },
          },
        },
        error_code: 0,
      },
      { err_code: -10008, err_msg: "Unsupported API call." },
      {
        method: "getPetDetectionConfig",
        result: {
          pet_detection: { detection: { enabled: "off", sensitivity: "60" } },
        },
        error_code: 0,
      },
      { err_code: -10008, err_msg: "Unsupported API call." },
      { err_code: -10008, err_msg: "Unsupported API call." },
      { err_code: -10008, err_msg: "Unsupported API call." },
      { err_code: -10008, err_msg: "Unsupported API call." },
      {
        method: "getLensMaskConfig",
        result: { lens_mask: { lens_mask_info: { enabled: "on" } } },
        error_code: 0,
      },
      {
        method: "getLdc",
        result: { image: { switch: { ldc: "off" } } },
        error_code: 0,
      },
      { err_code: -10008, err_msg: "Unsupported API call." },
      {
        method: "getLedStatus",
        result: { led: { config: { enabled: "off" } } },
        error_code: 0,
      },
      { err_code: -10008, err_msg: "Unsupported API call." },
      { err_code: -10008, err_msg: "Unsupported API call." },
      {
        method: "getFirmwareUpdateStatus",
        result: {
          cloud_config: {
            upgrade_status: { state: "normal", lastUpgradingSuccess: True },
          },
        },
        error_code: 0,
      },
      {
        method: "getMediaEncrypt",
        result: { cet: { media_encrypt: { enabled: "on" } } },
        error_code: 0,
      },
      { err_code: -10008, err_msg: "Unsupported API call." },
      {
        method: "getAlarmConfig",
        result: {
          enabled: "on",
          alarm_mode: ["siren", "light"],
          siren_type: "Siren",
          light_type: "flicker",
          siren_volume: "high",
          siren_duration: 5,
        },
        error_code: 0,
      },
      {
        method: "getAlarmPlan",
        result: { enabled: "off", alarm_plan: "0000-0700,127" },
        error_code: 0,
      },
      {
        method: "getSirenTypeList",
        result: { siren_type_list: ["Siren", "Tone", "Red Alert"] },
        error_code: 0,
      },
      {
        method: "getLightTypeList",
        result: { light_type_list: ["flicker"] },
        error_code: 0,
      },
      { method: "getSirenStatus", result: { status: "off" }, error_code: 0 },
      {
        method: "getLightFrequencyInfo",
        result: { image: { common: { light_freq_mode: "50" } } },
        error_code: 0,
      },
      {
        method: "getLightFrequencyCapability",
        result: { image: { light_frequency_capability: ["50", "60"] } },
        error_code: 0,
      },
      { err_code: -10008, err_msg: "Unsupported API call." },
      {
        method: "getRotationStatus",
        result: { image: { switch: { flip_type: "off" } } },
        error_code: 0,
      },
      {
        method: "getNightVisionModeConfig",
        result: { image: { switch: { night_vision_mode: "md_night_vision" } } },
        error_code: 0,
      },
      {
        method: "getWhitelampStatus",
        result: { status: 0, rest_time: 0 },
        error_code: 0,
      },
      {
        method: "getWhitelampConfig",
        result: {
          image: {
            switch: { wtl_intensity_level: "3", wtl_force_time: "300" },
          },
        },
        error_code: 0,
      },
      {
        method: "getMsgPushConfig",
        result: {
          msg_push: {
            chn1_msg_push_info: {
              notification_enabled: "on",
              rich_notification_enabled: "off",
            },
          },
        },
        error_code: 0,
      },
      {
        method: "getSdCardStatus",
        result: {
          harddisk_manage: {
            hd_info: [
              {
                hd_info_1: {
                  disk_name: "1",
                  loop_record_status: "1",
                  rw_attr: "rw",
                  total_space: "7.35 GB",
                  write_protect: "0",
                  type: "local",
                  status: "normal",
                  detect_status: "normal",
                  percent: "100",
                  free_space: "7.32 GB",
                  video_total_space: "6.86 GB",
                  video_free_space: "6.83 GB",
                },
              },
            ],
          },
        },
        error_code: 0,
      },
      {
        method: "getCircularRecordingConfig",
        result: { harddisk_manage: { harddisk: { loop: "on" } } },
        error_code: 0,
      },
      {
        method: "getRecordPlan",
        result: {
          record_plan: {
            chn1_channel: {
              enabled: "on",
              sunday: '["0000-2400:2"]',
              monday: '["0000-2400:2"]',
              tuesday: '["0000-2400:2"]',
              wednesday: '["0000-2400:2"]',
              thursday: '["0000-2400:2"]',
              friday: '["0000-2400:2"]',
              saturday: '["0000-2400:2"]',
            },
          },
        },
        error_code: 0,
      },
      { err_code: -10008, err_msg: "Unsupported API call." },
      { err_code: -10008, err_msg: "Unsupported API call." },
    ],
  },
};

But the issue is happening on the hub itself

Exelord commented 9 months ago

This may help as well happening on the child:

Traceback (most recent call last):
  File "/Users/maciej/Developer/tapo/start.py", line 171, in <module>
    tapo.presets = tapo.processPresetsResponse(returnData["getPresetConfig"])
  File "/opt/homebrew/lib/python3.10/site-packages/pytapo/__init__.py", line 1265, in processPresetsResponse
    for key, id in enumerate(response["preset"]["preset"]["id"])
TypeError: 'bool' object is not subscriptable
JurajNyiri commented 9 months ago

Thank you, this is helpful. Usually the camera responds like this:

...
         {
            "method":"getPetDetectionConfig",
            "result":{

            },
            "error_code":-40210
         }
...

if a feature is not supported.

JurajNyiri commented 9 months ago

Fixed in pytapo 3.3.16, thank you.

Exelord commented 9 months ago

Thank you Juraj!

unfortunately this is still an issue preventing the connection:

https://github.com/JurajNyiri/pytapo/pull/83#issuecomment-1816389728

JurajNyiri commented 9 months ago

@Exelord That error is in the custom file on your machine "/Users/maciej/Developer/tapo/start.py" .

Code works as expected, returnData["getPresetConfig"] is False.

Exelord commented 9 months ago

No it doesn’t. I have the same exact issue on home assistant. With the same log, on latest version.

it points on my local file as I used it to patch and play actual code.

JurajNyiri commented 9 months ago

It is a completely different issue in HA and line. And you posted logs for your local custom file.

https://github.com/JurajNyiri/HomeAssistant-Tapo-Control/issues/455#issuecomment-1817247985 follow instructions there please.