Skip to content
Snippets Groups Projects
Commit 9678b6d7 authored by jkerdreu's avatar jkerdreu
Browse files

Initial support for Tuya devices, still missing metering & RBG Lamp

git-svn-id: https://redmine.imt-atlantique.fr/svn/xaal/code/Python/trunk@2263 b32b6428-25c9-4566-ad07-03861ab6144f
parent 7a493c18
No related branches found
No related tags found
No related merge requests found
xAAL gateway for Tuya devices
=============================
Tuya protocol is the common protocol used in cheap chinese outlets, smart plugs,
and RGB lamps. It use the cloud base "Smart Life" mobile application (Android/IOS).
This protocol isn't really reliable. If you can, avoid the use of Smart Life APP
when this gateway is running. This should be ok, but attributes can be out of
sync. The gateway polls devices state every 30 seconds but due to socket error,
this can be a little longuer.
This gateway use a forked github pytuya lib. The main reason: The official one
doesn't support outlet w/ version 3.3 of the protocol which seems to be the
default now.
from setuptools import setup,find_packages
with open('README.rst') as f:
long_description = f.read()
VERSION = "0.1"
setup(
name='xaal.tuya',
version=VERSION,
license='GPL License',
author='Jerome Kerdreux',
author_email='Jerome.Kerdreux@imt-atlantique.fr',
#url='',
description=('xAAL support for Tuya protocol / Smart Life App' ),
long_description=long_description,
classifiers=[
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules',
],
keywords=['xaal', 'aqara'],
platforms='any',
packages=find_packages(),
include_package_data=True,
dependency_links=[
"git+https://github.com/belzedaar/python-tuya.git#egg=pytuya"
],
install_requires=[
'xaal.lib',
'gevent',
'pytuya',
'tenacity',
]
)
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
from .gw import setup
\ No newline at end of file
from . import gw
from xaal.lib import helpers
helpers.run_package(gw.PACKAGE_NAME,gw.setup)
from xaal.schemas import devices
from xaal.lib import tools,Device
import pytuya
import time
import logging
# Tuya device aren't really network reliable, I get a bunch of connection reset
# This looks like they use a single socket for local network & cloud (SmartLife APP)
# connection. Everytime time I toggle a outlet in SmartLife I get a connection reset
# on the next update status. I tested w/ delays but not really working, so I switched
# to tenacity once again.
from tenacity import *
logger = logging.getLogger(__name__)
# disable pytuya debug
logging.getLogger('pytuya').setLevel(logging.INFO)
def now():
return time.time()
class TuyaDev:
def __init__(self,tuya_id,tuya_key,tuya_ip,base_addr):
self.tuya_id = tuya_id
self.tuya_key = tuya_key
self.tuya_ip = tuya_ip
self.base_addr = base_addr
self.last_update = 0
self.devices = []
self.setup()
self.init_properties()
def setup(self):
logger.warning('Please overide setup()')
def update_status(self):
pass
def init_properties(self):
for dev in self.devices:
dev.vendor_id = 'Tuya'
dev.hw_id = self.tuya_id
dev.info = 'Tuya device: %s @ %s' % (self.tuya_id,self.tuya_ip)
if len(self.devices) > 1:
dev.group_id = self.base_addr + 'ff'
class PowerRelay(TuyaDev):
def setup(self):
dev = devices.powerrelay_toggle(self.base_addr + '00')
dev.methods['on'] = self.on
dev.methods['off'] = self.off
dev.methods['toggle'] = self.toggle
self.devices.append(dev)
self.relay = pytuya.OutletDevice(self.tuya_id,self.tuya_ip,self.tuya_key)
self.relay.set_version(3.3)
@retry(stop=stop_after_attempt(2))
def on(self):
self.relay.turn_on()
self.update_status()
@retry(stop=stop_after_attempt(2))
def off(self):
self.relay.turn_off()
self.update_status()
def toggle(self):
power = self.devices[0].attributes['power']
if power == None:
self.update_status()
power = self.devices[0].attributes['power']
if power == True:
self.off()
elif power == False:
self.on()
@retry(stop=stop_after_attempt(2))
def _update_status(self):
logger.info("Updating %s" % self)
time.sleep(0.1)
self.last_update = now()
power = self.relay.status()['dps']['1']
self.devices[0].attributes['power'] = power
def update_status(self):
try:
self._update_status()
except RetryError as e:
logger.warn(e)
# TODO I don't have this kind of devices right now
class RGBLamp(TuyaDev):pass
class PowerRelayMetering(TuyaDev):pass
from xaal.lib import tools,Device
from . import devices
import atexit
import logging
import time
PACKAGE_NAME = 'xaal.tuya'
logger = logging.getLogger(__name__)
# poll devices only every n seconds
REFRESH_RATE=30
class GW:
def __init__(self,engine):
self.engine = engine
self.devices = {}
atexit.register(self._exit)
self.config()
self.setup()
engine.add_timer(self.update,5)
def config(self):
cfg = tools.load_cfg(PACKAGE_NAME)
if not cfg:
cfg= tools.new_cfg(PACKAGE_NAME)
cfg['devices'] = {}
logger.warn("Created an empty config file")
cfg.write()
self.cfg = cfg
def setup(self):
devices = self.cfg.get('devices',[])
for d in devices:
cfg = devices.get(d,{})
base_addr = cfg.get('base_addr',None)
ip = cfg.get('ip',None)
key = cfg.get('key',None)
type_ = cfg.get('type','powerrelay')
if base_addr == None:
cfg['base_addr'] = base_addr = tools.get_random_uuid()[:-2]
if ip and key:
self.add_device(d,key,ip,base_addr,type_)
else:
logger.warn('Error in config file, wrong devices')
def add_device(self,tuya_id,tuya_key,ip,base_addr,type_):
if type_ == 'powerrelay':
dev = devices.PowerRelay(tuya_id,tuya_key,ip,base_addr)
self.devices[tuya_id] = dev
for d in dev.devices:
self.engine.add_device(d)
dev.update_status()
def update(self):
# Tuya protocol doesn't support update broadcasting
# so we have to poll devices frequently to update the internal state
# this is a pain, but if you don't use Smart Life App, this should never
# occur anyway.
now = time.time()
for dev in self.devices.values():
if now > (dev.last_update + REFRESH_RATE):
dev.update_status()
break # we only update one dev to avoid blocking
def _exit(self):
cfg = tools.load_cfg(PACKAGE_NAME)
if cfg != self.cfg:
logger.info('Saving configuration file')
self.cfg.write()
def setup(eng):
GW(eng)
return True
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment