Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #558, #505: Feature/ Hub Siren and C420 cameras fixes #559

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

marcosngomezi
Copy link

@marcosngomezi marcosngomezi commented Apr 19, 2024

Related to pytapo/issues/105
Addresses #558, #505
Requires pytapo/pull/106

Changes:

  • Refactored alarm_config in camData
  • Multi devices support of siren
    • Backup command for Siren start and stop, since startManualAlarm and stopManualAlarm are not working for C420 cameras
    • Time_left used on devices returning it
  • New TapoHubSirenDuration and TapoHubVolume numbers
  • New TapoHubSirenType select

@marcosngomezi marcosngomezi marked this pull request as ready for review April 19, 2024 01:21
@JurajNyiri
Copy link
Owner

JurajNyiri commented Apr 19, 2024

Hi @marcosngomezi

First, thank you for the great work here!

Could you please test this branch https://github.com/JurajNyiri/pytapo/tree/experiment-multiple-params-getSirenTypeList

Screenshot 2024-04-19 at 18 35 34

My cameras do not support this function at all so I cannot test.

What is the response you get when you run print(tapo.getMost()["getSirenTypeList"])? and what is the response when you run getHubSirenTypeList(self) and getAlarmConfig(self)?

@JurajNyiri JurajNyiri changed the title Feature/ Hub Siren and C420 cameras fixes Add #558: Feature/ Hub Siren and C420 cameras fixes Apr 19, 2024
@JurajNyiri JurajNyiri changed the title Add #558: Feature/ Hub Siren and C420 cameras fixes Add #558, #556: Feature/ Hub Siren and C420 cameras fixes Apr 19, 2024
@marcosngomezi
Copy link
Author

Can't do the test right now, but already tested to send both msg_alarm and siren on a command and ignores the second one or fails depending on the method, I will test it with your branch and report back

@marcosngomezi
Copy link
Author

marcosngomezi commented Apr 22, 2024

I'm back @JurajNyiri
Sent to both devices hub and camera each command:
image
And the response for each was:
image
As commented on the first image getHubSirenTypeList on the camera throws an exception not sure why that error generates an exception while other don't.

As you can see the camera answers more similar to the rest of the ip cameras, is the hub the one that needs different commands.

@marcosngomezi
Copy link
Author

marcosngomezi commented Apr 22, 2024

This PR is not solving #556, gives the same functionality but only for hub siren, not camera, I can look at it and try to solve it in another PR
It is solving #505 with the connectionInformation fix

@marcosngomezi marcosngomezi changed the title Add #558, #556: Feature/ Hub Siren and C420 cameras fixes Add #558, #505: Feature/ Hub Siren and C420 cameras fixes Apr 22, 2024
@JurajNyiri
Copy link
Owner

JurajNyiri commented Apr 22, 2024

So it looks like when getSirenTypeList is used inside getMost with both arguments {"msg_alarm": {}, "siren": {}}, it does not work on a hub, correct? Maybe we could still put it there as a separate entry instead, one with msg_alarm and another one with siren and then identify the correct response... I will think more about it tomorrow.

@marcosngomezi
Copy link
Author

Is what I tried to do on the first commits of the pytapo PR with method aliases, but because the response order is not the same as the request, and doesn't says if its for siren o msg_alarm, I didnt found a trustable way to do it. Maybe a second multiple request inside getMost, but I think that is not possible on the same one.

@JurajNyiri
Copy link
Owner

JurajNyiri commented Apr 25, 2024

@marcosngomezi thank you for testing, I pushed a new commit in the branch https://github.com/JurajNyiri/pytapo/tree/experiment-multiple-params-getSirenTypeList .

Could you please run tapo.getMost() both on camera and hub and post output? No need to put it inside print as this commit is targetting a new approach which should allow us to pair request to response and therefore run the same function mutliple times.

You should see something like

{'request': {'method': 'getConnectionType', 'params': {'network': {'get_connection_type': []}}}, 'response': {'result': {'link_type': 'wifi', 'ssid': 'redacted', 'rssiValue': -46, 'rssi': '4'}, 'method': 'getConnectionType'}}
{'request': {'method': 'getAlarmConfig', 'params': {'msg_alarm': {}}}, 'response': {'result': False, 'method': 'getAlarmConfig'}}
{'request': {'method': 'getAlarmPlan', 'params': {'msg_alarm_plan': {}}}, 'response': {'result': False, 'method': 'getAlarmPlan'}}
{'request': {'method': 'getSirenTypeList', 'params': {'msg_alarm': {}}}, 'response': {'result': False, 'method': 'getSirenTypeList'}}
{'request': {'method': 'getSirenTypeList', 'params': {'siren': {}}}, 'response': {'result': False, 'method': 'getSirenTypeList'}}
{'request': {'method': 'getSirenConfig', 'params': {'siren': {}}}, 'response': {'result': False, 'method': 'getSirenConfig'}}
{'request': {'method': 'getLightTypeList', 'params': {'msg_alarm': {}}}, 'response': {'result': False, 'method': 'getLightTypeList'}}
{'request': {'method': 'getSirenStatus', 'params': {'msg_alarm': {}}}, 'response': {'result': False, 'method': 'getSirenStatus'}}
{'request': {'method': 'getSirenStatus', 'params': {'siren': {}}}, 'response': {'result': False, 'method': 'getSirenStatus'}}

Of course with different values.

Please let me also know if the values are as expected compared to when you call the functions (getHubSirenTypeList, getHubSirenConfig, getHubSirenStatus) directly as in your pr.

@JurajNyiri
Copy link
Owner

I have pushed another PR which should get this to very near prod quality.
@marcosngomezi please grab https://github.com/JurajNyiri/pytapo/tree/experiment-multiple-params-getSirenTypeList and run tapo.getMost() on both camera and hub.

Now you should get output like

getConnectionType
{'link_type': 'wifi', 'ssid': 'redacted', 'rssiValue': -45, 'rssi': '4'}
getAlarmConfig
False
getAlarmPlan
False
getSirenTypeList
[False, False]
getSirenConfig
False
getLightTypeList
False
getSirenStatus
[False, False]

Of course with different values.

Please let me also know if the values are as expected compared to when you call the functions (getHubSirenTypeList, getHubSirenConfig, getHubSirenStatus) directly as in your pr.

@marcosngomezi
Copy link
Author

I'm getting the out of order exception, but I think that I get what you are trying to do, and there should be no need to depend on the order, taking the method from the result and just returning the one (or first) that is not False

@JurajNyiri
Copy link
Owner

That is interesting indeed! I do not get out of order exception. Does that happen only on hub or camera as well? That would block this approach unfortunately.

@marcosngomezi
Copy link
Author

marcosngomezi commented Apr 27, 2024

Just pushed a possible solution without depending on order at all PR, it works for me:

image

Hub getConnectionType {'link_type': 'ethernet'}
Hub getAlarmConfig False
Hub getAlarmPlan False
Hub getSirenTypeList {'siren_type_list': ['Doorbell Ring 1', 'Doorbell Ring 2', 'Doorbell Ring 3', 'Doorbell Ring 4', 'Doorbell Ring 5', 'Doorbell Ring 6', 'Doorbell Ring 7', 'Doorbell Ring 8', 'Doorbell Ring 9', 'Doorbell Ring 10', 'Phone Ring', 'Alarm 1', 'Alarm 2', 'Alarm 3', 'Alarm 4', 'Dripping Tap', 'Alarm 5', 'Connection 1', 'Connection 2']}
Hub getSirenConfig {'siren_type': 'Alarm 3', 'volume': '7', 'duration': 10}
Hub getLightTypeList False
Hub getSirenStatus {'status': 'off', 'time_left': 0}
Camera getConnectionType False
Camera getAlarmConfig {'enabled': 'off', 'alarm_mode': ['siren'], 'siren_type': 'Siren', 'light_type': 'flicker', 'siren_volume': 'high', 'siren_duration': 5}
Camera getAlarmPlan {'enabled': 'off', 'alarm_plan': '0000-0000,127'}
Camera getSirenTypeList {'siren_type_list': ['Siren', 'Tone', 'Red Alert']}
Camera getSirenConfig False
Camera getLightTypeList {'light_type_list': ['flicker']}
Camera getSirenStatus {'status': 'off'}
Get Hub Siren Type List
Hub {'siren_type_list': ['Doorbell Ring 1', 'Doorbell Ring 2', 'Doorbell Ring 3', 'Doorbell Ring 4', 'Doorbell Ring 5', 'Doorbell Ring 6', 'Doorbell Ring 7', 'Doorbell Ring 8', 'Doorbell Ring 9', 'Doorbell Ring 10', 'Phone Ring', 'Alarm 1', 'Alarm 2', 'Alarm 3', 'Alarm 4', 'Dripping Tap', 'Alarm 5', 'Connection 1', 'Connection 2']}

Get Hub Siren Status
Hub {'status': 'off', 'time_left': 0} 

Get Hub Siren Config
Hub {'siren_type': 'Alarm 3', 'volume': '7', 'duration': 10} 

Get Alarm config
Hub [{'method': 'getAlarmPlan', 'error_code': -40106}, {'method': 'getAlarmConfig', 'error_code': -40106}, {'method': 'getLightTypeList', 'error_code': -40106}, {'method': 'getSirenTypeList', 'error_code': -40106}, {'method': 'getSirenStatus', 'error_code': -40106}]
Camera [{'method': 'getAlarmConfig', 'result': {'enabled': 'off', 'alarm_mode': ['siren'], 'siren_type': 'Siren', 'light_type': 'flicker', 'siren_volume': 'high', 'siren_duration': 5}, 'error_code': 0}, {'method': 'getAlarmPlan', 'result': {'enabled': 'off', 'alarm_plan': '0000-0000,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}]

The problem to solve now is for the sets how to know if should be siren or msg_alarm or send both? getConnectionType is not going to be useful because both ip cameras and hub respond to it, maybe with getSirenConfig and getAlarmConfig?

@marcosngomezi
Copy link
Author

Ah just realised you where trying to make a list with the multiple results, instead to just keep one, what do you think about my approach? As I said the problem is that you don't know which method was the successful and need to depend on another way to know which values should use for sets

@marcosngomezi
Copy link
Author

marcosngomezi commented Apr 27, 2024

Looks like only the hub has the order problem, but the camera has a lot of missing result methods:
Hub request - result:

getDeviceInfo getDetectionConfig
getDetectionConfig getVehicleDetectionConfig
getPersonDetectionConfig getPersonDetectionConfig
getVehicleDetectionConfig getBarkDetectionConfig
getBCDConfig getMeowDetectionConfig
getPetDetectionConfig getBCDConfig
getBarkDetectionConfig getPetDetectionConfig
getMeowDetectionConfig getDeviceInfo
getGlassDetectionConfig getGlassDetectionConfig
getTamperDetectionConfig getTamperDetectionConfig
getLensMaskConfig getLensMaskConfig
getLdc getLdc
getLastAlarmInfo getLastAlarmInfo
getLedStatus getLedStatus
getTargetTrackConfig getTargetTrackConfig
getPresetConfig getPresetConfig
getFirmwareUpdateStatus getFirmwareUpdateStatus
getMediaEncrypt getMediaEncrypt
getConnectionType getConnectionType
getAlarmConfig getAlarmConfig
getAlarmPlan getAlarmPlan
getSirenTypeList getSirenTypeList
getSirenTypeList getSirenTypeList
getSirenConfig getSirenConfig
getLightTypeList getLightTypeList
getSirenStatus getSirenStatus
getSirenStatus getSirenStatus
getLightFrequencyInfo getLightFrequencyInfo
getLightFrequencyCapability getLightFrequencyCapability
getChildDeviceList getRotationStatus
getRotationStatus getNightVisionModeConfig
getNightVisionModeConfig getWhitelampStatus
getWhitelampStatus getWhitelampConfig
getWhitelampConfig getMsgPushConfig
getMsgPushConfig getSdCardStatus
getSdCardStatus getCircularRecordingConfig
getCircularRecordingConfig getRecordPlan
getRecordPlan getAudioConfig
getAudioConfig getFirmwareAutoUpgradeConfig
getFirmwareAutoUpgradeConfig getChildDeviceList

Camera:

getDeviceInfo getDeviceInfo
getDetectionConfig getDetectionConfig
getPersonDetectionConfig getPersonDetectionConfig
getVehicleDetectionConfig getVehicleDetectionConfig
getBCDConfig
getPetDetectionConfig getPetDetectionConfig
getBarkDetectionConfig
getMeowDetectionConfig
getGlassDetectionConfig
getTamperDetectionConfig
getLensMaskConfig getLensMaskConfig
getLdc getLdc
getLastAlarmInfo
getLedStatus getLedStatus
getTargetTrackConfig
getPresetConfig
getFirmwareUpdateStatus getFirmwareUpdateStatus
getMediaEncrypt getMediaEncrypt
getConnectionType
getAlarmConfig getAlarmConfig
getAlarmPlan getAlarmPlan
getSirenTypeList getSirenTypeList
getSirenTypeList
getSirenConfig
getLightTypeList getLightTypeList
getSirenStatus getSirenStatus
getSirenStatus
getLightFrequencyInfo getLightFrequencyInfo
getLightFrequencyCapability getLightFrequencyCapability
getChildDeviceList
getRotationStatus getRotationStatus
getNightVisionModeConfig getNightVisionModeConfig
getWhitelampStatus getWhitelampStatus
getWhitelampConfig getWhitelampConfig
getMsgPushConfig getMsgPushConfig
getSdCardStatus getSdCardStatus
getCircularRecordingConfig getCircularRecordingConfig
getRecordPlan getRecordPlan
getAudioConfig getAudioConfig
getFirmwareAutoUpgradeConfig

image

Hub response is also not deterministic with the order, it looks like a newer version doing things in parallel that ensures the method but not the order.

@JurajNyiri
Copy link
Owner

JurajNyiri commented Apr 27, 2024

Thats a great idea, I would say instead of throwing the exception, we could convert it to a list instead. That way HA can then go into the list and find the correct property it is looking for.

I posted a comment on the PR.

@JurajNyiri
Copy link
Owner

I am going to accept PR and do the changes above, then I will push the code for you to test again.

@marcosngomezi
Copy link
Author

marcosngomezi commented Apr 27, 2024

not sure about the list, there is not going to be a way to know which one is first and second anyway, because can be in different order, and it would force tapo-control to always check if it is a dictionary or a list, are you sure that is worth making it a list? for what we have seen there are no devices that respond to both, if some day appears, there should be some extra research to understand why it responds to both, its an strange case to have two sirens on the same device

@JurajNyiri
Copy link
Owner

I have pushed a new commit making everything a list to keep it consistent and removed the exception.

JurajNyiri/pytapo@a5f2213

Could you please test?

As for tapo integration I can refactor it so that it can accept these values as a list and look for the property it needs in the loop and as soon as it finds it break the loop.

@JurajNyiri
Copy link
Owner

Actually give me a second, I will try to make it more robust so that the number of requests always equals the number of responses, even if response fails...

@JurajNyiri
Copy link
Owner

@marcosngomezi pushed another commit. Let me know if it works please.

getConnectionType
[{'link_type': 'wifi', 'ssid': 'redacted', 'rssiValue': -44, 'rssi': '4'}]
getAlarmConfig
[False]
getAlarmPlan
[False]
getSirenTypeList
[False, False]
getSirenConfig
[False]
getLightTypeList
[False]
getSirenStatus
[False, False]

@JurajNyiri
Copy link
Owner

JurajNyiri commented Apr 27, 2024

As for this PR with the new codebase of pytapo (assuming it will work).

What I think we can afford to do is inside getCamData(hass, controller): just change everything starting with data[ to data[...function...][0] except for getSirenTypeList and getSirenStatus. As we control pytapo code / version there is no need for universal loop or length checking on the rest.

As for the 2 that are there twice, both of these are not in there yet in the main branch, so for these what we can do is create a for loop and find the first expected value for them and save accordingly.

@marcosngomezi
Copy link
Author

marcosngomezi commented Apr 27, 2024

Its working:

Get most
Hub getConnectionType [{'link_type': 'ethernet'}]
Hub getAlarmConfig [False]
Hub getAlarmPlan [False]
Hub getSirenTypeList [{'siren_type_list': ['Doorbell Ring 1', 'Doorbell Ring 2', 'Doorbell Ring 3', 'Doorbell Ring 4', 'Doorbell Ring 5', 'Doorbell Ring 6', 'Doorbell Ring 7', 'Doorbell Ring 8', 'Doorbell Ring 9', 'Doorbell Ring 10', 'Phone Ring', 'Alarm 1', 'Alarm 2', 'Alarm 3', 'Alarm 4', 'Dripping Tap', 'Alarm 5', 'Connection 1', 'Connection 2']}, False]
Hub getSirenConfig [{'siren_type': 'Alarm 3', 'volume': '7', 'duration': 10}]
Hub getLightTypeList [False]
Hub getSirenStatus [{'status': 'off', 'time_left': 0}, False]
Camera getConnectionType [False]
Camera getAlarmConfig [{'enabled': 'off', 'alarm_mode': ['siren'], 'siren_type': 'Siren', 'light_type': 'flicker', 'siren_volume': 'high', 'siren_duration': 5}]
Camera getAlarmPlan [{'enabled': 'off', 'alarm_plan': '0000-0000,127'}]
Camera getSirenTypeList [{'siren_type_list': ['Siren', 'Tone', 'Red Alert']}, False]
Camera getSirenConfig [False]
Camera getLightTypeList [{'light_type_list': ['flicker']}]
Camera getSirenStatus [{'status': 'off'}, False]

@marcosngomezi
Copy link
Author

Would you use same siren and config components for both types of sirens or keep it as siren and hub siren components in the integration? makes sense to have just one now I suppose

@JurajNyiri
Copy link
Owner

I think we can use the same, as the response is the same from the point of HA there is no difference, both have key "siren_type_list"

@JurajNyiri
Copy link
Owner

@marcosngomezi I have just pushed new version to pytapo https://pypi.org/manage/project/pytapo/release/3.3.20/

@JurajNyiri
Copy link
Owner

Let me know if you are OK with updating the rest like described above for breaking changes, if not I can do the changes on main. We should also now use the types reported by camera instead of hardcoding them inside the code.

@JurajNyiri
Copy link
Owner

Please make sure to test also if the integration uses the cached values, so that we do not re-request data that have been already retrieved via getMost. You should see a lot of "Found cached capability {check_function}, creating {cls.name}"

else:
self._attr_state = camData["hubSiren"]["volume"]

#TODO max value for c420 is 300 while on hub is 599, to validate on other cameras
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you validate the max value on your camera/s?
method: "setAlarmConfig"
params: {"msg_alarm": { "siren_duration": 5 }}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we set 599 on camera?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throws error

Copy link
Owner

@JurajNyiri JurajNyiri May 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit confused, there is no siren_duration anywhere in the code of pytapo.

I ran it anyways on C200 and C520WS:

print(tapo.executeFunction("setAlarmConfig", {"msg_alarm": {"siren_duration": 5}}))

Traceback (most recent call last):
  File "/Users/jnyiri/repos/pytapo/test2.py", line 21, in <module>
    print(tapo.executeFunction("setAlarmConfig", {"msg_alarm": {"siren_duration": 5}}))
  File "/Users/jnyiri/repos/pytapo/pytapo/__init__.py", line 510, in executeFunction
    raise Exception(
Exception: Error: Function not supported, Response: {"method": "setAlarmConfig", "result": {}, "error_code": -40210}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the value, we could set this to self._attr_native_max_value = 300 if hass.data[DOMAIN][entry.entry_id]["isParent"] is False.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the getAlarmConfig I get the next response from cameras:
[{'enabled': 'off', 'alarm_mode': ['siren'], 'siren_type': 'Siren', 'light_type': 'flicker', 'siren_volume': 'normal', 'siren_duration': 300}]
And I can change any value with the setAlarmConfig, you dont have siren_duration in your cameras?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from another comment I see that is probably alarm_duration in some cameras, in that case could you test max value for print(tapo.executeFunction("setAlarmConfig", {"msg_alarm": {"alarm_duration": 5}}))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -349,6 +356,7 @@ async def async_select_option(self, option: str) -> None:
await self._coordinator.async_request_refresh()


# TODO c420 alarm_mode has alarm_mode siren, check compatibility of method
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that siren was considered on pytapo but not here, probably not working correctly, to check

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate? I am not sure what you mean. How can I help?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the C420 is used the string siren in place of sound, in this select updateTapo method is not being considered the "siren" value, I will check on my camera and came back

@marcosngomezi
Copy link
Author

Hi there @JurajNyiri not yet finished, numbers are remaining and some TODOs, but some questions and need of validation on other cameras rised.

@JurajNyiri
Copy link
Owner

JurajNyiri commented May 4, 2024

Hey @marcosngomezi thank you for all your updates. Let me know if I can help in any way with this PR.

I will test it out once you say its ready and go through the code in detail.

Comment on lines +98 to +118
self._controller.performRequest,
{
"method": "multipleRequest",
"params": {
"requests": [
{
"method": "do",
"params": {
"msg_alarm": {
"manual_msg_alarm": {"action": "start"}
}
},
},
{
"method": "setSirenStatus",
"params": {"msg_alarm": {"status": "on"}},
},
]
},
},
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move this to pytapo so that the lib does not call performRequest directly anywhere.

Comment on lines 937 to 941
#TODO validate: getLastAlarmInfo returns last alarm config that is not necessarily last config seted, sound like little bug, but cant test on c420 or hub because they dont support getLastAlarmInfo


try:
alarmData = data["getLastAlarmInfo"]["msg_alarm"]["chn1_msg_alarm_info"]
alarmData = data["getAlarmConfig"][0]
Copy link
Owner

@JurajNyiri JurajNyiri May 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C520WS / C200

pytapo.getAlarm:

C520WS:

{'enabled': 'off', 'light_alarm_enabled': 'on', 'alarm_mode': ['sound', 'light'], 'alarm_type': '0', 'light_type': '1', 'sound_alarm_enabled': 'on', 'alarm_volume': 'high', 'alarm_duration': '0'}

C200:

{'.name': 'chn1_msg_alarm_info', '.type': 'info', 'enabled': 'off', 'alarm_type': '0', 'light_type': '1', 'alarm_mode': ['sound', 'light'], 'sound_alarm_enabled': 'off', 'light_alarm_enabled': 'off'}

pytapo.getAlarmConfig:

[{'method': 'getAlarmConfig', 'result': {}, 'error_code': -40210}, {'method': 'getAlarmPlan', 'result': {}, 'error_code': -40210}, {'method': 'getSirenTypeList', 'result': {}, 'error_code': -40210}, {'method': 'getLightTypeList', 'result': {}, 'error_code': -40210}, {'method': 'getSirenStatus', 'result': {}, 'error_code': -40210}]

This change will break cameras.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then we could get the getLastAlarmInfo one as third option if previous ones didnt work, because is the least accurate but for compatibility with those cases, currently is the first option to be considered

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 946 to 952
alarmData = data["getAlarmConfig"]
alarm = alarmData["enabled"]
alarm_mode = alarmData["alarm_mode"]
alarm = data["getSirenStatus"][0]["status"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change will break cameras, see above.

It will need to be handled separately.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@JurajNyiri
Copy link
Owner

Hi @marcosngomezi I had to push a new update to fix unrelated issue, please rebase this if you get the chance and see my comments above.

@marcosngomezi marcosngomezi force-pushed the feature/hub-siren-and-c420fixes branch from 01ee477 to 206bf69 Compare June 1, 2024 23:26
@marcosngomezi
Copy link
Author

Hi there @JurajNyiri , sorry for the delay, trying to finish this PR, I think is ready for a review, also have tested it and is working as expected.
There are two "issues" that I think are not related but maybe you know better:

  • Config entry * has already been setup! Sound like for some reason is running twice the creation? or is it normal when restarting HA?
    image
  • Select and switch and select reports to be taking over 10 seconds, not sure if normal or there is something to improve.
    image

@JurajNyiri
Copy link
Owner

Thank you!

Config entry * has already been setup!

I saw some users with this issue but have never been able to track it down or replicate it. Usually I told users to remove all entries in jsons for tapo_control and that solved it. Might be some misconfiguration, somehow, via some edge case...

Select and switch and select reports to be taking over 10 seconds

Some cameras are slower, some of mine take a rather long time to respond, not much we can do.

Comment on lines +143 to +162
result = await self._hass.async_add_executor_job(
self._controller.executeFunction,
"multipleRequest",
{
"requests": [
{
"method": "do",
"params": {
"msg_alarm": {
"manual_msg_alarm": {"action": "stop"}
}
},
},
{
"method": "setSirenStatus",
"params": {"msg_alarm": {"status": "off"}},
},
]
},
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will focus on testing this, ideally we should move it to pytapo function so that we do not need executeFunction call directly.

Copy link
Owner

@JurajNyiri JurajNyiri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your updates!

Comments below in review addressed in aae7b35

Please merge, clean up and let me know if it works, if all works as expected I will do final testing with C200 for all the features.

and "chn1_msg_alarm_info" in data["getLastAlarmInfo"][0]["msg_alarm"]
and data["getLastAlarmInfo"][0]["msg_alarm"]["chn1_msg_alarm_info"] != False
):
alarmData = data["getLastAlarmInfo"]["msg_alarm"]["chn1_msg_alarm_info"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing [0]

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2024-06-28 15:13:14.298 ERROR (MainThread) [custom_components.tapo_control] getLastAlarmInfo unexpected error err=TypeError('list indices must be integers or slices, not str'), type(err)=<class 'TypeError'>

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alarmData = data["getLastAlarmInfo"][0]["msg_alarm"]["chn1_msg_alarm_info"]

LOGGER.error(f"getLastAlarmInfo unexpected error {err=}, {type(err)=}")

try:
alarmStatus = data["getSirenStatus"][0]["status"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be (and is on my camera) [False, False].

This results in

2024-06-28 15:20:31.453 ERROR (MainThread) [custom_components.tapo_control] getSirenStatus unexpected error err=TypeError("'bool' object is not subscriptable"), type(err)=<class 'TypeError'>

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (
            data["getSirenStatus"][0] is not False
            and "status" in data["getSirenStatus"][0]
        ):
            alarmStatus = data["getSirenStatus"][0]["status"]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be initialized as false then

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that data["getSirenStatus"][0] itself is False, so when we try to read status off of it it fails as boolean does not have dict entries

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I mean in the cases that is missing, i will default the status to false, as we wouldnt know, but probably is false


if alarmConfig != None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is not None

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand the comment, if we got alarmConfig from some of the previous sources, we search for typelist, but if none of them worked makes no sense to search it

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check my commit, it's linter complaining, just a better practice, instead of != do if alarmConfig is not None:

if alarmConfig != None:
try:
alarmSirenTypeList = data["getSirenTypeList"][0]["siren_type_list"]
except Exception:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unable to connect to Tapo: Cameras Control controller: cannot access local variable 'err' where it is not associated with a value

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except Exception as err:

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After fixing the err var

2024-06-28 15:26:49.357 ERROR (MainThread) [custom_components.tapo_control] getSirenTypeList unexpected error err=TypeError("'bool' object is not subscriptable"), type(err)=<class 'TypeError'>

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (
                data["getSirenTypeList"][0] is not False
                and "siren_type_list" in data["getSirenTypeList"][0]
            ):

Comment on lines -32 to 30
entry, hass, TapoSiren, "getAlarm", config_entry
entry, hass, TapoSiren, "getSirenTypeList", config_entry
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-06-28 at 17 28 18

Siren became unavailable after fixes described in review and this PR on my camera

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understanded that getSirenTypeList was the common factor between cameras with siren, getAlarm is no available on C420s, we have to do some kind of OR for both methods then

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, we might need a custom logic then... or possibly just call it twice, once for getAlarm and once for getSirenTypeList. It might create 2 entities for some cameras though we do not know of, so maybe if getAlarm failed to create, do getSirenTypeList

Comment on lines -90 to -92
result = await self._hass.async_add_executor_job(
self._controller.startManualAlarm,
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After changing it back to getAlarm and triggering, it no longer sounds without this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird, the first command in the multipleRequest, is the same that is being sended in the startManualAlarm method https://github.com/JurajNyiri/pytapo/blob/bd5c6d579d618d1e47a9049873b7e57c9d74ffb0/pytapo/__init__.py#L1628
Could you confirm that

performRequest(
            {
                "method": "do",
                "msg_alarm": {"manual_msg_alarm": {"action": "start"}},
            })

works, but

performRequest,(
                {
                    "method": "multipleRequest",
                    "params": {
                        "requests": [
                            {
                                "method": "do",
                                "params": {
                                    "msg_alarm": {
                                        "manual_msg_alarm": {"action": "start"}
                                    }
                                },
                            },
                        ]
                    },
                })

dont?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, the one via multipleRequest does not work. This call was found long time ago from other chinese cameras - not Tapo cameras, so it might be something they copied in but did not officially support and it was never supported via the app in the beginning when it was found. Might explain the inconsistencies we are facing here.

Comment on lines -109 to -112
async def async_turn_off(self, **kwargs) -> None:
result = await self._hass.async_add_executor_job(
self._controller.stopManualAlarm,
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stop also no longer works with my camera with these changes

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing to check than with the start

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.


tapo.performRequest(
    {
        "method": "do",
        "msg_alarm": {"manual_msg_alarm": {"action": "stop"}},
    }
)

works.

tapo.performRequest(
    {
        "method": "multipleRequest",
        "params": {
            "requests": [
                {
                    "method": "do",
                    "msg_alarm": {"manual_msg_alarm": {"action": "stop"}},
                },
            ]
        },
    }
)

doesn't.

result = await self._hass.async_add_executor_job(
self._controller.stopManualAlarm,
)
async def async_turn_off(self, send: bool | None = None, **kwargs) -> None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, the send is always None, so this never executes. Why was it added?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its added because my cameras return the time_left param on the response(line126 and 84)and there is no need to send the stop command to the camera, is just for updating the HA status, maybe there is a better way to do, I only saw it being called on the _turn_off_after method but now I get that it is part of the entity interface, if send is None it enters to the else right?, there should be no error

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When user manually switches off the siren via the siren switch it always goes to else (at least for my camera) which does not execute any command on the camera therefore the siren is not stopped.

@JurajNyiri
Copy link
Owner

@marcosngomezi if you haven't yet, check my commit and that branch, just in case you missed my main comment on the review, might save you some time as I addressed most of the above, we just now need to make it work on both cameras as we discussed in the comments now.

@marcosngomezi
Copy link
Author

Yes I saw it, lets resolve the pending comments and I will do the merge, remaining fixs and testing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants