-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add support for Broadlink BG1 devices #33399
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
Closed
Closed
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,8 +50,9 @@ | |
| SP1_TYPES = ["sp1"] | ||
| SP2_TYPES = ["sp2", "honeywell_sp2", "sp3", "spmini2", "spminiplus"] | ||
| MP1_TYPES = ["mp1"] | ||
| BG1_TYPES = ["bg1"] | ||
|
|
||
| SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES | ||
| SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES + BG1_TYPES | ||
|
|
||
| SWITCH_SCHEMA = vol.Schema( | ||
| { | ||
|
|
@@ -88,7 +89,6 @@ | |
|
|
||
| def setup_platform(hass, config, add_entities, discovery_info=None): | ||
| """Set up the Broadlink switches.""" | ||
|
|
||
| devices = config.get(CONF_SWITCHES) | ||
| slots = config.get("slots", {}) | ||
| ip_addr = config.get(CONF_HOST) | ||
|
|
@@ -138,6 +138,24 @@ def _get_mp1_slot_name(switch_friendly_name, slot): | |
| retry_times, | ||
| ) | ||
| switches.append(slot) | ||
| elif switch_type in BG1_TYPES: | ||
| broadlink_device = broadlink.bg1((ip_addr, 80), mac_addr, None) | ||
| switches = [] | ||
| parent_device = BroadlinkBG1Switch(broadlink_device, retry_times) | ||
| switches.append( | ||
| BroadlinkBG1Slot( | ||
| friendly_name + " left", broadlink_device, 1, parent_device, retry_times | ||
| ) | ||
| ) | ||
| switches.append( | ||
| BroadlinkBG1Slot( | ||
| friendly_name + " right", | ||
| broadlink_device, | ||
| 2, | ||
| parent_device, | ||
| retry_times, | ||
| ) | ||
| ) | ||
|
|
||
| broadlink_device.timeout = config.get(CONF_TIMEOUT) | ||
| try: | ||
|
|
@@ -398,3 +416,135 @@ def _auth(self, retry): | |
| if not auth and retry > 0: | ||
| return self._auth(retry - 1) | ||
| return auth | ||
|
|
||
|
|
||
| class BroadlinkBG1Slot(BroadlinkRMSwitch): | ||
| """Representation of a slot of Broadlink switch.""" | ||
|
|
||
| def __init__(self, friendly_name, device, slot, parent_device, retry_times): | ||
| """Initialize the slot of switch.""" | ||
| super().__init__(friendly_name, friendly_name, device, None, None, retry_times) | ||
| self._command_on = 1 | ||
| self._command_off = 0 | ||
| self._slot = slot | ||
| self._parent_device = parent_device | ||
|
|
||
| @property | ||
| def assumed_state(self): | ||
| """Return true if unable to access real state of entity.""" | ||
| return False | ||
|
|
||
| def turn_on(self, **kwargs): | ||
| """Turn the device on.""" | ||
| self._turn_on_off(True) | ||
|
|
||
| def turn_off(self, **kwargs): | ||
| """Turn the device off.""" | ||
| self._turn_on_off(False) | ||
|
|
||
| def _turn_on_off(self, state): | ||
| if self._slot == 1: | ||
| res = self._device.set_state(pwr1=int(state)) | ||
| else: | ||
| res = self._device.set_state(pwr2=int(state)) | ||
| if res: | ||
| _LOGGER.debug("Setting device state: %s", res) | ||
| self._state = int(state) | ||
| self._parent_device.set_outlet_status(self._slot, int(state)) | ||
| self.schedule_update_ha_state() | ||
| else: | ||
| _LOGGER.warning("No response from switch") | ||
|
|
||
| def _sendpacket(self, packet, retry): | ||
| """Send packet to device.""" | ||
| try: | ||
| self._device.set_power(self._slot, packet) | ||
| except (socket.timeout, ValueError) as error: | ||
| if retry < 1: | ||
| _LOGGER.error("Error during sending a packet: %s", error) | ||
| self._is_available = False | ||
| return False | ||
| if not self._auth(self._retry_times): | ||
| return False | ||
| return self._sendpacket(packet, max(0, retry - 1)) | ||
| self._is_available = True | ||
| return True | ||
|
|
||
| @property | ||
| def should_poll(self): | ||
| """Return the polling state.""" | ||
| return True | ||
|
|
||
| @property | ||
| def slot(self): | ||
| """Return the slot.""" | ||
| return self._slot | ||
|
|
||
| def update(self): | ||
| """Trigger update for all switches on the parent device.""" | ||
| self._parent_device.update() | ||
| self._state = self._parent_device.get_outlet_status(self._slot) | ||
| if self._state is None: | ||
| self._is_available = False | ||
| else: | ||
| self._is_available = True | ||
|
|
||
|
|
||
| class BroadlinkBG1Switch: | ||
| """Representation of a Broadlink BG1 switch - To fetch states of all slots.""" | ||
|
|
||
| def __init__(self, device, retry_times): | ||
| """Initialize the switch.""" | ||
| self._device = device | ||
| self._states = None | ||
| self._retry_times = retry_times | ||
|
|
||
| def get_outlet_status(self, slot): | ||
| """Get status of outlet from cached status list.""" | ||
| if self._states is None: | ||
| return None | ||
| return self._states[f"s{slot}"] | ||
|
|
||
| def set_outlet_status(self, slot, status): | ||
| """Get status of outlet from cached status list.""" | ||
| if self._states is not None: | ||
| self._states[f"s{slot}"] = status | ||
|
|
||
| @Throttle(TIME_BETWEEN_UPDATES) | ||
| def update(self): | ||
| """Fetch new state data for this device.""" | ||
| _LOGGER.debug("Polling:") | ||
| self._update(self._retry_times) | ||
|
|
||
| def _update(self, retry): | ||
| """Update the state of the device.""" | ||
| states = None | ||
| try: | ||
| resp = self._device.get_state() | ||
| if resp is not None: | ||
| states = {"s1": resp["pwr1"], "s2": resp["pwr2"]} # Left # Right | ||
| _LOGGER.debug(states) | ||
| except (socket.timeout, ValueError) as error: | ||
|
Comment on lines
+522
to
+527
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above, this logic was duplicated from existing code. |
||
| _LOGGER.debug("Polling timeout, trying again: %s", retry) | ||
| if retry < 1: | ||
| _LOGGER.error("Error during updating the state: %s", error) | ||
| self._states = None | ||
| return | ||
| if not self._auth(self._retry_times): | ||
| _LOGGER.error("Auth failed: %s", error) | ||
| self._states = None | ||
| return | ||
| return self._update(max(0, retry - 1)) | ||
| if states is None and retry > 0: | ||
| return self._update(max(0, retry - 1)) | ||
| self._states = states | ||
|
|
||
| def _auth(self, retry): | ||
| """Authenticate the device.""" | ||
| try: | ||
| auth = self._device.auth() | ||
| except OSError: | ||
| auth = False | ||
| if not auth and retry > 0: | ||
| return self._auth(retry - 1) | ||
| return auth | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is problematic. You are not handling authentication errors and a wide variety of socket errors. If authentication fails once, the device will stop working until you restart Home Assistant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was duplicated straight from the existing Switch and Slot implementations, so if this is an issue then those need fixing as well? In fact, looking (briefly) again I don't think this method is even used in this class :|
However, I am missing exception handling and retry in the
_turn_on_off()method which does basically the same duty 🤦 - I'll fix that.Re auth, the nearest thing I can see from the current implementation is that the
authmethod just catches the genericOSError. It would be easy enough to changesocket.timeoutto catchOSErrorinstead, which should cover everything, and the polledupdateshould handle re-authenticating. Would that work?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this problem is real and it is everywhere. I am working on a solution that uses helper functions to handle exceptions. In the end it will be easier for you. I'll let you know when I finish.