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 initial support for Tornado 16X SQ air conditioner (0x4e2a) #430

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4fd2b16
Add GH Actions
KTibow Nov 5, 2020
ebaac1f
Fix branches
KTibow Nov 5, 2020
97077d3
Conditionals
KTibow Nov 5, 2020
25856e3
Update flake8.yaml
KTibow Nov 5, 2020
d09f501
Go back
KTibow Nov 5, 2020
9482804
Update flake8.yaml
KTibow Nov 5, 2020
153b96f
Update flake8.yaml
KTibow Nov 5, 2020
922eb45
Run black
KTibow Nov 5, 2020
75e483c
Clean up get_energy() (#471)
felipediel Nov 7, 2020
1799a8c
Merge branch 'master' into patch-2
mjg59 Nov 7, 2020
29345a1
Merge pull request #470 from KTibow/patch-2
mjg59 Nov 7, 2020
5fcea48
Rebase from main.
enosh Sep 22, 2020
e16fe18
Simplify `set_advanced` to just one request and create `set_partial` …
enosh Nov 28, 2020
cfd2942
Fix modes heating,drying,fan when unit is powered off, in `set_partial`.
enosh Nov 28, 2020
ee3bb1f
Fix some bugs in set_advanced.
enosh Nov 29, 2020
9913097
Better ifs. Fix flake8 issues.
enosh Nov 29, 2020
e245964
Rename `torando` class to `xsq`.
enosh Nov 29, 2020
09527ae
Round target_temp.
enosh Nov 29, 2020
e999a79
Rename class to `sq1`, add clean and mildew support, use f-strings, _…
enosh Dec 1, 2020
8f8c113
Handle checksums correctly!
enosh Dec 1, 2020
692986e
Remove set_partial, integate to set_state (renamed from set_advanced)…
enosh Dec 9, 2020
db50704
Implement an encode func.
enosh Dec 9, 2020
e6d151a
replace some complicated ifs with {}.get
enosh Dec 9, 2020
573de73
avoid struct in encode, more dict use instead of ifs
enosh Dec 9, 2020
e459581
typos, flake8 issues
enosh Dec 9, 2020
d0899e7
First attempt at IntEmun for mode, speed and swing. Simplify _encode.
enosh Dec 10, 2020
85ef56d
Replace a bunch of type checks with creating a new instance.
enosh Dec 10, 2020
bb13719
Move packet creation to a new _send_command and to _enocde.
enosh Dec 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/flake8.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Python flake8

on:
push:
branches: [ main, master, dev, development ]
pull_request:
branches: [ main, master, dev, development ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 wemake-python-styleguide
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. ignore magic numbers and use double quotes and ignore numbers with zeroes before them.
# and ignore lowercase hex numbers and ignore isort incorrect imports
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=90 --ignore=WPS432,WPS339,WPS341,I --inline-quotes double --statistics
151 changes: 73 additions & 78 deletions broadlink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Generator, List, Union, Tuple

from .alarm import S1C
from .climate import hysen
from .climate import hysen, tornado
from .cover import dooya
from .device import device, scan
from .exceptions import exception
Expand All @@ -20,88 +20,88 @@
0x2716: (sp2, "NEO PRO", "Ankuoo"),
0x2717: (sp2, "NEO", "Ankuoo"),
0x2719: (sp2, "SP2-compatible", "Honeywell"),
0x271a: (sp2, "SP2-compatible", "Honeywell"),
0x271A: (sp2, "SP2-compatible", "Honeywell"),
0x2720: (sp2, "SP mini", "Broadlink"),
0x2728: (sp2, "SP2-compatible", "URANT"),
0x2733: (sp2, "SP3", "Broadlink"),
0x2736: (sp2, "SP mini+", "Broadlink"),
0x273e: (sp2, "SP mini", "Broadlink"),
0x273E: (sp2, "SP mini", "Broadlink"),
0x7530: (sp2, "SP2", "Broadlink (OEM)"),
0x7539: (sp2, "SP2-IL", "Broadlink (OEM)"),
0x753e: (sp2, "SP mini 3", "Broadlink"),
0x753E: (sp2, "SP mini 3", "Broadlink"),
0x7540: (sp2, "MP2", "Broadlink"),
0X7544: (sp2, "SP2-CL", "Broadlink"),
0x7544: (sp2, "SP2-CL", "Broadlink"),
0x7546: (sp2, "SP2-UK/BR/IN", "Broadlink (OEM)"),
0x7547: (sp2, "SC1", "Broadlink"),
0x7918: (sp2, "SP2", "Broadlink (OEM)"),
0x7919: (sp2, "SP2-compatible", "Honeywell"),
0x791a: (sp2, "SP2-compatible", "Honeywell"),
0x7d00: (sp2, "SP3-EU", "Broadlink (OEM)"),
0x7d0d: (sp2, "SP mini 3", "Broadlink (OEM)"),
0x791A: (sp2, "SP2-compatible", "Honeywell"),
0x7D00: (sp2, "SP3-EU", "Broadlink (OEM)"),
0x7D0D: (sp2, "SP mini 3", "Broadlink (OEM)"),
0x9479: (sp2, "SP3S-US", "Broadlink"),
0x947a: (sp2, "SP3S-EU", "Broadlink"),
0x756c: (sp4, "SP4M", "Broadlink"),
0x947A: (sp2, "SP3S-EU", "Broadlink"),
0x756C: (sp4, "SP4M", "Broadlink"),
0x7579: (sp4, "SP4L-EU", "Broadlink"),
0x7583: (sp4, "SP mini 3", "Broadlink"),
0x7d11: (sp4, "SP mini 3", "Broadlink"),
0x648b: (sp4b, "SP4M-US", "Broadlink"),
0x7D11: (sp4, "SP mini 3", "Broadlink"),
0x648B: (sp4b, "SP4M-US", "Broadlink"),
0x2712: (rm, "RM pro/pro+", "Broadlink"),
0x272a: (rm, "RM pro", "Broadlink"),
0x272A: (rm, "RM pro", "Broadlink"),
0x2737: (rm, "RM mini 3", "Broadlink"),
0x273d: (rm, "RM pro", "Broadlink"),
0x277c: (rm, "RM home", "Broadlink"),
0x273D: (rm, "RM pro", "Broadlink"),
0x277C: (rm, "RM home", "Broadlink"),
0x2783: (rm, "RM home", "Broadlink"),
0x2787: (rm, "RM pro", "Broadlink"),
0x278b: (rm, "RM plus", "Broadlink"),
0x278f: (rm, "RM mini", "Broadlink"),
0x278B: (rm, "RM plus", "Broadlink"),
0x278F: (rm, "RM mini", "Broadlink"),
0x2797: (rm, "RM pro+", "Broadlink"),
0x279d: (rm, "RM pro+", "Broadlink"),
0x27a1: (rm, "RM plus", "Broadlink"),
0x27a6: (rm, "RM plus", "Broadlink"),
0x27a9: (rm, "RM pro+", "Broadlink"),
0x27c2: (rm, "RM mini 3", "Broadlink"),
0x27c3: (rm, "RM pro+", "Broadlink"),
0x27c7: (rm, "RM mini 3", "Broadlink"),
0x27cc: (rm, "RM mini 3", "Broadlink"),
0x27cd: (rm, "RM mini 3", "Broadlink"),
0x27d0: (rm, "RM mini 3", "Broadlink"),
0x27d1: (rm, "RM mini 3", "Broadlink"),
0x27de: (rm, "RM mini 3", "Broadlink"),
0x51da: (rm4, "RM4 mini", "Broadlink"),
0x5f36: (rm4, "RM mini 3", "Broadlink"),
0x279D: (rm, "RM pro+", "Broadlink"),
0x27A1: (rm, "RM plus", "Broadlink"),
0x27A6: (rm, "RM plus", "Broadlink"),
0x27A9: (rm, "RM pro+", "Broadlink"),
0x27C2: (rm, "RM mini 3", "Broadlink"),
0x27C3: (rm, "RM pro+", "Broadlink"),
0x27C7: (rm, "RM mini 3", "Broadlink"),
0x27CC: (rm, "RM mini 3", "Broadlink"),
0x27CD: (rm, "RM mini 3", "Broadlink"),
0x27D0: (rm, "RM mini 3", "Broadlink"),
0x27D1: (rm, "RM mini 3", "Broadlink"),
0x27DE: (rm, "RM mini 3", "Broadlink"),
0x51DA: (rm4, "RM4 mini", "Broadlink"),
0x5F36: (rm4, "RM mini 3", "Broadlink"),
0x6026: (rm4, "RM4 pro", "Broadlink"),
0x6070: (rm4, "RM4C mini", "Broadlink"),
0x610e: (rm4, "RM4 mini", "Broadlink"),
0x610f: (rm4, "RM4C mini", "Broadlink"),
0x61a2: (rm4, "RM4 pro", "Broadlink"),
0x62bc: (rm4, "RM4 mini", "Broadlink"),
0x62be: (rm4, "RM4C mini", "Broadlink"),
0x648d: (rm4, "RM4 mini", "Broadlink"),
0x649b: (rm4, "RM4 pro", "Broadlink"),
0x653a: (rm4, "RM4 mini", "Broadlink"),
0x610E: (rm4, "RM4 mini", "Broadlink"),
0x610F: (rm4, "RM4C mini", "Broadlink"),
0x61A2: (rm4, "RM4 pro", "Broadlink"),
0x62BC: (rm4, "RM4 mini", "Broadlink"),
0x62BE: (rm4, "RM4C mini", "Broadlink"),
0x648D: (rm4, "RM4 mini", "Broadlink"),
0x649B: (rm4, "RM4 pro", "Broadlink"),
0x653A: (rm4, "RM4 mini", "Broadlink"),
0x2714: (a1, "e-Sensor", "Broadlink"),
0x4eb5: (mp1, "MP1-1K4S", "Broadlink"),
0x4ef7: (mp1, "MP1-1K4S", "Broadlink (OEM)"),
0x4f1b: (mp1, "MP1-1K3S2U", "Broadlink (OEM)"),
0x4f65: (mp1, "MP1-1K3S2U", "Broadlink"),
0x4EB5: (mp1, "MP1-1K4S", "Broadlink"),
0x4EF7: (mp1, "MP1-1K4S", "Broadlink (OEM)"),
0x4F1B: (mp1, "MP1-1K3S2U", "Broadlink (OEM)"),
0x4F65: (mp1, "MP1-1K3S2U", "Broadlink"),
0x5043: (lb1, "SB800TD", "Broadlink (OEM)"),
0x504e: (lb1, "LB1", "Broadlink"),
0x60c7: (lb1, "LB1", "Broadlink"),
0x60c8: (lb1, "LB1", "Broadlink"),
0x504E: (lb1, "LB1", "Broadlink"),
0x60C7: (lb1, "LB1", "Broadlink"),
0x60C8: (lb1, "LB1", "Broadlink"),
0x6112: (lb1, "LB1", "Broadlink"),
0x2722: (S1C, "S2KIT", "Broadlink"),
0x4ead: (hysen, "HY02B05H", "Hysen"),
0x4e4d: (dooya, "DT360E-45/20", "Dooya"),
0x51e3: (bg1, "BG800/BG900", "BG Electrical"),
0X4E2A: (tornado, "TOP SQ X", "Tornado"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should consider naming the class with the model instead of the brand. Eventually Tornado will launch another device and we will have to create another class, and tornado_new is weird.

How about top or sqx? We need to make the type short so we can keep the code clean eg here.

Copy link
Author

Choose a reason for hiding this comment

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

I have a "SMART-16X SQ (WIFI)" (the numbers change based on size). Reading through the catalogue there's also a TOP SQ WIFI __X series (which at some point I assumed were identical). Based on the app description both should work, but that is untested. So I renamed the class to xsq, does that work?

Copy link
Collaborator

Choose a reason for hiding this comment

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

How does the model look like in the official app? If it looks specific (numbers included), the PIDs are different. If it looks generic, they are probably using the same PID for all these _X models.

Copy link
Author

Choose a reason for hiding this comment

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

It's utterly generic, the most specific it gets is "Air-conditioner".

Copy link
Collaborator

Choose a reason for hiding this comment

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

@thewh1teagle What is your model?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should use sq or sq1.

Copy link
Author

Choose a reason for hiding this comment

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

Sounds good, do you have a preference?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think sq1 will look better if we need to create a sq2 in the future. We can use sq1 for the class, SQ1 for the type and SQ for the model name.

Copy link

@thewh1teagle thewh1teagle Dec 5, 2020

Choose a reason for hiding this comment

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

@felipediel Sorry for being late, my model is SMART-12X SQ (WIFI)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thank you!

0x4EAD: (hysen, "HY02B05H", "Hysen"),
0x4E4D: (dooya, "DT360E-45/20", "Dooya"),
0x51E3: (bg1, "BG800/BG900", "BG Electrical"),
}


def gendevice(
dev_type: int,
host: Tuple[str, int],
mac: Union[bytes, str],
name: str = None,
is_locked: bool = None,
dev_type: int,
host: Tuple[str, int],
mac: Union[bytes, str],
name: str = None,
is_locked: bool = None,
) -> device:
"""Generate a device."""
try:
Expand All @@ -122,10 +122,10 @@ def gendevice(


def hello(
host: str,
port: int = 80,
timeout: int = 10,
local_ip_address: str = None,
host: str,
port: int = 80,
timeout: int = 10,
local_ip_address: str = None,
) -> device:
"""Direct device discovery.

Expand All @@ -138,31 +138,27 @@ def hello(


def discover(
timeout: int = 10,
local_ip_address: str = None,
discover_ip_address: str = '255.255.255.255',
discover_ip_port: int = 80,
timeout: int = 10,
local_ip_address: str = None,
discover_ip_address: str = "255.255.255.255",
discover_ip_port: int = 80,
) -> List[device]:
"""Discover devices connected to the local network."""
responses = scan(
timeout, local_ip_address, discover_ip_address, discover_ip_port
)
responses = scan(timeout, local_ip_address, discover_ip_address, discover_ip_port)
return [gendevice(*resp) for resp in responses]


def xdiscover(
timeout: int = 10,
local_ip_address: str = None,
discover_ip_address: str = '255.255.255.255',
discover_ip_port: int = 80,
timeout: int = 10,
local_ip_address: str = None,
discover_ip_address: str = "255.255.255.255",
discover_ip_port: int = 80,
) -> Generator[device, None, None]:
"""Discover devices connected to the local network.

This function returns a generator that yields devices instantly.
"""
responses = scan(
timeout, local_ip_address, discover_ip_address, discover_ip_port
)
responses = scan(timeout, local_ip_address, discover_ip_address, discover_ip_port)
for resp in responses:
yield gendevice(*resp)

Expand Down Expand Up @@ -191,13 +187,12 @@ def setup(ssid: str, password: str, security_mode: int) -> None:
payload[0x85] = pass_length # Character length of password
payload[0x86] = security_mode # Type of encryption

checksum = sum(payload, 0xbeaf) & 0xffff
payload[0x20] = checksum & 0xff # Checksum 1 position
checksum = sum(payload, 0xBEAF) & 0xFFFF
payload[0x20] = checksum & 0xFF # Checksum 1 position
payload[0x21] = checksum >> 8 # Checksum 2 position

sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Internet # UDP
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(payload, ('255.255.255.255', 80))
sock.sendto(payload, ("255.255.255.255", 80))
sock.close()
28 changes: 14 additions & 14 deletions broadlink/alarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,42 @@ class S1C(device):
"""Controls a Broadlink S1C."""

_SENSORS_TYPES = {
0x31: 'Door Sensor', # 49 as hex
0x91: 'Key Fob', # 145 as hex, as serial on fob corpse
0x21: 'Motion Sensor' # 33 as hex
0x31: "Door Sensor", # 49 as hex
0x91: "Key Fob", # 145 as hex, as serial on fob corpse
0x21: "Motion Sensor", # 33 as hex
}

def __init__(self, *args, **kwargs) -> None:
"""Initialize the controller."""
device.__init__(self, *args, **kwargs)
self.type = 'S1C'
self.type = "S1C"

def get_sensors_status(self) -> dict:
"""Return the state of the sensors."""
packet = bytearray(16)
packet[0] = 0x06 # 0x06 - get sensors info, 0x07 - probably add sensors
response = self.send_packet(0x6a, packet)
response = self.send_packet(0x6A, packet)
check_error(response[0x22:0x24])
payload = self.decrypt(response[0x38:])
if not payload:
return None
count = payload[0x4]
sensor_data = payload[0x6:]
sensors = [
bytearray(sensor_data[i * 83:(i + 1) * 83])
bytearray(sensor_data[i * 83 : (i + 1) * 83])
for i in range(len(sensor_data) // 83)
]
return {
'count': count,
'sensors': [
"count": count,
"sensors": [
{
'status': sensor[0],
'name': sensor[4:26].decode().strip('\x00'),
'type': self._SENSORS_TYPES.get(sensor[3], 'Unknown'),
'order': sensor[1],
'serial': sensor[26:30].hex(),
"status": sensor[0],
"name": sensor[4:26].decode().strip("\x00"),
"type": self._SENSORS_TYPES.get(sensor[3], "Unknown"),
"order": sensor[1],
"serial": sensor[26:30].hex(),
}
for sensor in sensors
if any(sensor[26:30])
]
],
}
Loading