From 4035df90879ef9abb2ec352f6fb456db5f20b567 Mon Sep 17 00:00:00 2001 From: loosrob <79396812+loosrob@users.noreply.github.com> Date: Wed, 16 Mar 2022 20:54:00 +0100 Subject: [PATCH] New plugin - Daikin Airco first version of plugin, must functionality present --- daikinairco/README.md | 16 + daikinairco/daikin.jpg | Bin 0 -> 45544 bytes daikinairco/integrationplugindaikinairco.json | 131 ++++++ daikinairco/integrationplugindaikinairco.py | 387 ++++++++++++++++++ daikinairco/meta.json | 12 + daikinairco/requirements.txt | 61 +++ 6 files changed, 607 insertions(+) create mode 100644 daikinairco/README.md create mode 100644 daikinairco/daikin.jpg create mode 100644 daikinairco/integrationplugindaikinairco.json create mode 100644 daikinairco/integrationplugindaikinairco.py create mode 100644 daikinairco/meta.json create mode 100644 daikinairco/requirements.txt diff --git a/daikinairco/README.md b/daikinairco/README.md new file mode 100644 index 00000000..51185b27 --- /dev/null +++ b/daikinairco/README.md @@ -0,0 +1,16 @@ +# Daikin Airco + +This plugin allows discovery and simple control via Nymea of Daikin airconditioning devices. + +## Supported Things + +* Devices with the Daikin Residential Controller + +## Requirements + +* nymea and the airconditioning device must be in the same local area network. +* The package "nymea-plugin-daikinairco" must be installed. + +## More + + [Daikin Residential Controller](https://www.daikin.eu/en_us/product-group/control-systems/daikin-online-controller.html) diff --git a/daikinairco/daikin.jpg b/daikinairco/daikin.jpg new file mode 100644 index 0000000000000000000000000000000000000000..289fc375279c5cd06f3bc2d3c33cca52c60dbf91 GIT binary patch literal 45544 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 literal 0 HcmV?d00001 diff --git a/daikinairco/integrationplugindaikinairco.json b/daikinairco/integrationplugindaikinairco.json new file mode 100644 index 00000000..c40b6b0b --- /dev/null +++ b/daikinairco/integrationplugindaikinairco.json @@ -0,0 +1,131 @@ +{ + "displayName": "Daikin Airco", + "name": "Daikinairco", + "id": "b5cf475e-707d-46a5-9684-296fb1a14886", + "vendors": [ + { + "id": "88f6421c-cc7b-4006-b077-421fc384939c", + "displayName": "Daikin", + "name": "Daikin", + "thingClasses": [ + { + "id": "e6d0bfe6-5923-4c29-840d-ef8b0a17fad9", + "name": "airco", + "displayName": "Daikin Airco", + "createMethods": ["discovery"], + "interfaces": ["power", "thermostat", "wirelessconnectable"], + "paramTypes": [ + { + "id": "758cad5c-994a-4136-b83d-80fe2f07346c", + "name": "deviceId", + "displayName": "Device ID", + "type" : "QString", + "readOnly": true + }, + { + "id": "1fefe7df-ef8e-4434-bb51-0c521553f750", + "name": "deviceName", + "displayName": "Device name", + "type" : "QString", + "readOnly": true + } + ], + "settingsTypes": [ + + ], + "stateTypes": [ + { + "id": "71c5b083-6c95-44f0-b54c-b98f0ac459d8", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": false, + "type": "bool", + "cached": false + }, + { + "id": "bd0ff439-6ab5-414a-ba6e-9ecef0f7c3f4", + "name": "url", + "displayName": "Device Address", + "displayNameEvent": "Device IP changed", + "defaultValue": "0.0.0.0", + "type" : "QString" + }, + { + "displayName": "Power", + "id": "6bb42aa0-2280-408d-89b5-400410abdd73", + "name": "power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "id": "2261311a-4302-4a03-bd67-50e92044fe59", + "name": "targetTemperature", + "displayName": "Target temperature", + "displayNameEvent": "Target temperature changed", + "displayNameAction": "Set target temperature", + "type": "double", + "unit": "DegreeCelsius", + "minValue": "10", + "maxValue": "30", + "defaultValue": "20", + "writable": true + }, + { + "id": "a2b7a3aa-641c-45b8-a13b-e845324969ca", + "name": "temperature", + "displayName": "Home temperature", + "displayNameEvent": "Home temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": "0" + }, + { + "id": "b99b7182-51e6-456c-b7bc-f39ea2397500", + "name": "outsideTemperature", + "displayName": "Outside temperature", + "displayNameEvent": "Outside temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": "0" + }, + { + "id": "9b614a01-4984-4e73-8cbc-95389c484c3b", + "name": "humidity", + "displayName": "Humidity", + "displayNameEvent": "Humidity changed", + "type": "double", + "unit": "Percentage", + "minValue": "0", + "maxValue": "100", + "defaultValue": "0" + }, + { + "id": "59da03a6-1b61-4777-91e1-51e518beecbc", + "name": "Mode", + "displayName": "Mode", + "displayNameEvent": "Mode changed", + "displayNameAction": "Set mode", + "type": "QString", + "writable": true, + "possibleValues": [ + "Automatic", + "Cooling", + "Heating", + "Dehumidifier", + "Fan" + ], + "defaultValue": "Automatic" + } + ], + "actionTypes": [ + + ] + } + ] + } + ] +} diff --git a/daikinairco/integrationplugindaikinairco.py b/daikinairco/integrationplugindaikinairco.py new file mode 100644 index 00000000..e65a755d --- /dev/null +++ b/daikinairco/integrationplugindaikinairco.py @@ -0,0 +1,387 @@ +import nymea +import time +import threading +import json +import requests +import random +from socket import * +import sys + +pollTimer = None +pollFrequency = 30 + +def discoverThings(info): + logger.log("Discovery started for", info.thingClassId) + discoveredIps = findIps() + + for i in range(0, len(discoveredIps)): + deviceIp = discoveredIps[i] + # get basic info /common/basic_info + # response example: + # ret=OK,type=aircon,reg=eu,dst=1,ver=1_14_58,rev=83B3526,pow=0,err=0,location=0, + # name=name-in-%hex,icon=0,method=polling,port=30050,id=uuid, + # pw=,lpw_flag=0,adp_kind=3,pv=3.30,cpv=3,cpv_minor=20,led=1,en_setzone=1,mac=mac,adp_mode=run,en_hol=0, + # ssid1=ssid,radio1=-43,ssid=DaikinAP14937,grp_name=,en_grp=0 + + # get model info /aircon/get_model_info --> (how) can we use this? + # response example: + # ret=OK,model=0FC3,type=N,pv=3.30,cpv=3,cpv_minor=20,mid=NA,humd=0,s_humd=0,acled=0,land=0,elec=1,temp=1, + # temp_rng=0,m_dtct=1,ac_dst=--,disp_dry=0,dmnd=1,en_scdltmr=1,en_frate=1,en_fdir=1,s_fdir=3,en_rtemp_a=0, + # en_spmode=5,en_ipw_sep=1,en_mompow=1,hmlmt_l=10.0 + + aUrl = 'http://' + deviceIp + '/common/basic_info' + headers = {'Accept': '*/*'} + rr = requests.get(aUrl, headers=headers) + pollResponse = rr.text + if rr.status_code == requests.codes.ok: + systemType = 'none' + splitResponse = pollResponse.split(",") + for j in range(0, len(splitResponse)): + splitItem = splitResponse[j].split("=") + if splitItem[0] == 'type': + systemType = splitItem[1] + elif splitItem[0] == 'id': + systemId = splitItem[1] + elif splitItem[0] == 'name': + hexArray = splitItem[1].split("%") + deviceName = "" + for k in range(0, len(hexArray)): + deviceName+=bytes.fromhex(hexArray[k]).decode('utf-8') + if systemType == 'aircon': + logger.log("Device with IP " + deviceIp + " is a Daikin airco unit.") + logger.log("Device ID:", systemId) + logger.log("Device Name:", deviceName) + # check if device already known + exists = False + for thing in myThings(): + logger.log("Comparing to existing units: is %s an airco unit?" % (thing.name)) + if thing.thingClassId == aircoThingClassId: + logger.log("Yes, %s is an airco unit." % (thing.name)) + if thing.paramValue(aircoThingDeviceIdParamTypeId) == systemId: + logger.log("Already have unit with serial number %s in the system: %s" % (systemId, thing.name)) + exists = True + else: + logger.log("Thing %s doesn't match with found unit with serial number %s" % (thing.name, systemId)) + if exists == False: # unit doesn't exist yet, so add it + thingDescriptor = nymea.ThingDescriptor(aircoThingClassId, deviceName) + thingDescriptor.params = [ + nymea.Param(aircoThingDeviceIdParamTypeId, systemId), + nymea.Param(aircoThingDeviceNameParamTypeId, deviceName) + ] + info.addDescriptor(thingDescriptor) + else: # unit already exists, so show it to allow reconfiguration + thingDescriptor = nymea.ThingDescriptor(aircoThingClassId, deviceName, thingId=thing.id) + thingDescriptor.params = [ + nymea.Param(aircoThingDeviceIdParamTypeId, systemId), + nymea.Param(aircoThingDeviceNameParamTypeId, deviceName) + ] + info.addDescriptor(thingDescriptor) + else: + logger.log("Device with IP " + deviceIp + " does not appear to be a supported Daikin airco unit.") + else: + logger.log("Device with IP " + deviceIp + " does not appear to be a supported Daikin airco unit.") + info.finish(nymea.ThingErrorNoError) + return + +def findIps(): + discoveredIps = [] + + # Create a UDP socket + sock = socket(AF_INET, SOCK_DGRAM) + sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + sock.settimeout(20) + + server_address = ('255.255.255.255', 30050) + message = 'DAIKIN_UDP/common/basic_info' + + try: + while True: + # Send data + logger.log('sending: ' + message) + sent = sock.sendto(message.encode(), server_address) + + # Receive response + logger.log('waiting to receive') + data, server = sock.recvfrom(4096) + if data.decode('UTF-8')[0:18] == 'ret=OK,type=aircon': + print('Received confirmation; server ip: ' + str(server[0]) ) + discoveredIps.append(str(server[0])) + break + else: + print('Verification failed') + + print('Trying again...') + finally: + sock.close() + return discoveredIps + +def setupThing(info): + searchSystemId = info.thing.paramValue(aircoThingDeviceIdParamTypeId) + logger.log("setupThing called for", info.thing.name, searchSystemId) + + discoveredIps = findIps() + found = False + + for i in range(0, len(discoveredIps)): + deviceIp = discoveredIps[i] + aUrl = 'http://' + deviceIp + '/common/basic_info' + headers = {'Accept': '*/*'} + rr = requests.get(aUrl, headers=headers) + pollResponse = rr.text + if rr.status_code == requests.codes.ok: + logger.log("Device with IP " + deviceIp + " is a supported Daikin Airco.") + # get device info + splitResponse = pollResponse.split(",") + for j in range(0, len(splitResponse)): + splitItem = splitResponse[j].split("=") + if splitItem[0] == 'id': + systemId = splitItem[1] + logger.log("Device ID:", systemId) + # check if this is the device with the serial number we're looking for + if systemId == searchSystemId: + logger.log("Device with IP " + deviceIp + " is the existing device.") + found = True + info.thing.setStateValue(aircoUrlStateTypeId, deviceIp) + else: + logger.log("Device with IP " + deviceIp + " does not appear to be a supported Daikin Airco.") + if found == True: + info.thing.setStateValue(aircoConnectedStateTypeId, True) + pollAirco(info.thing) + info.finish(nymea.ThingErrorNoError) + else: + info.thing.setStateValue(aircoConnectedStateTypeId, False) + info.finish(nymea.ThingErrorHardwareFailure, "Error connecting to the device in the network.") + + logger.log("Airco added:", info.thing.name) + + # If no poll timer is set up yet, start it now + logger.log("Creating pollService") + global pollTimer + global pollFrequency + if pollTimer == None: + logger.log("Starting timer @ setupThing") + pollTimer = nymea.PluginTimer(pollFrequency, pollService) + logger.log("timer interval @ setupThing", pollTimer.interval) + else: + logger.log("Timer already exists @ setupThing") + + info.finish(nymea.ThingErrorNoError) + return + +def pollAirco(info): + global pollFrequency + deviceIp = info.stateValue(aircoUrlStateTypeId) + logger.log("polling airco", deviceIp, info.name) + airco = info + headers = {'Accept': '*/*'} + # get sensor info + # response example: + # ret=OK,htemp=25.0,hhum=30,otemp=2.0,err=0,cmpfreq=0,mompow=1 + aUrl = 'http://' + deviceIp + '/aircon/get_sensor_info' + pr = requests.get(aUrl, headers=headers) + pollResponse = pr.text + if pr.status_code == requests.codes.ok: + airco.setStateValue(aircoConnectedStateTypeId, True) + splitResponse = pollResponse.split(",") + for i in range(0, len(splitResponse)): + splitItem = splitResponse[i].split("=") + if splitItem[0] == 'htemp': + homeTemp = float(splitItem[1]) + airco.setStateValue(aircoTemperatureStateTypeId, homeTemp) + elif splitItem[0] == 'hhum': + homeHumidity = float(splitItem[1]) + airco.setStateValue(aircoHumidityStateTypeId, homeHumidity) + elif splitItem[0] == 'otemp': + outTemp = float(splitItem[1]) + airco.setStateValue(aircoOutsideTemperatureStateTypeId, outTemp) + else: + airco.setStateValue(aircoConnectedStateTypeId, False) + + # get control info + # response example: + # ret=OK,pow=0,mode=4,adv=,stemp=21.0,shum=0,dt1=22.0,dt2=M,dt3=20.0,dt4=21.0,dt5=21.0,dt7=22.0, + # dh1=0,dh2=0,dh3=0,dh4=0,dh5=0,dh7=0,dhh=50,b_mode=4,b_stemp=21.0,b_shum=0,alert=255, + # f_rate=A,f_dir=0,b_f_rate=A,b_f_dir=0,dfr1=A,dfr2=A,dfr3=A,dfr4=A,dfr5=A,dfr6=A,dfr7=A,dfrh=5, + # dfd1=0,dfd2=0,dfd3=0,dfd4=0,dfd5=0,dfd6=0,dfd7=0,dfdh=0,dmnd_run=0,en_demand=1 + aUrl = 'http://' + deviceIp + '/aircon/get_control_info' + pr = requests.get(aUrl, headers=headers) + pollResponse = pr.text + if pr.status_code == requests.codes.ok: + airco.setStateValue(aircoConnectedStateTypeId, True) + splitResponse = pollResponse.split(",") + for i in range(0, len(splitResponse)): + splitItem = splitResponse[i].split("=") + # get power state + if splitItem[0] == 'pow': + power = int(splitItem[1]) + logger.log("Power", power) + airco.setStateValue(aircoPowerStateTypeId, power) + elif splitItem[0] == 'mode': + mode = int(splitItem[1]) + if mode == 2: # 2 = dehumidifier mode + logger.log("Airco mode: dehumidifier", mode) + airco.setStateValue(aircoModeStateTypeId, "Dehumidifier") + elif mode == 3: # 3 = cooling mode + logger.log("Airco mode: cooling", mode) + airco.setStateValue(aircoModeStateTypeId, "Cooling") + elif mode == 4: # 4 = heating mode + logger.log("Airco mode: heating", mode) + airco.setStateValue(aircoModeStateTypeId, "Heating") + elif mode == 6: # 6 = fan mode + logger.log("Airco mode: fan", mode) + airco.setStateValue(aircoModeStateTypeId, "Fan") + else: # 0-1-7 = automatic mode + logger.log("Airco mode: automatic", mode) + airco.setStateValue(aircoModeStateTypeId, "Automatic") + elif splitItem[0] == 'stemp': + try: + targetTemp = float(splitItem[1]) + logger.log("Target temperature", targetTemp) + airco.setStateValue(aircoTargetTemperatureStateTypeId, targetTemp) + except: + logger.log("Target temperature parameter is not a number:", splitItem[1]) + for j in range(0, len(splitResponse)): + splitItem = splitResponse[j].split("=") + if splitItem[0] == 'dt1': + targetTemp = float(splitItem[1]) + logger.log("Using target temperature from automatic mode", targetTemp) + airco.setStateValue(aircoTargetTemperatureStateTypeId, targetTemp) + else: + airco.setStateValue(aircoConnectedStateTypeId, False) + +def pollService(): + logger.log("pollTimer triggered") + global pollTimer + global pollFrequency + pollTimer.interval = pollFrequency + # Poll all airco units we know + for thing in myThings(): + if thing.thingClassId == aircoThingClassId: + pollAirco(thing) + +def executeAction(info): + deviceIp = info.thing.stateValue(aircoUrlStateTypeId) + logger.log("executeAction called for thing", info.thing.name, deviceIp, info.actionTypeId, info.params) + headers = {'Accept': '*/*'} + aUrl = 'http://' + deviceIp + '/aircon/get_control_info' + pr = requests.get(aUrl, headers=headers) + # get control info, to obtain target temperature per mode, to set the correct target temperature (stemp) when changing mode + pollResponse = pr.text + logger.log("response", pr.text) + if pr.status_code == requests.codes.ok: + info.thing.setStateValue(aircoConnectedStateTypeId, True) + splitResponse = pollResponse.split(",") + for i in range(0, len(splitResponse)): + splitItem = splitResponse[i].split("=") + if splitItem[0] == 'pow': + power = int(splitItem[1]) + elif splitItem[0] == 'mode': + mode = int(splitItem[1]) + elif splitItem[0] == 'stemp': + targetTemp = splitItem[1] + elif splitItem[0] == 'shum': + targetHum = splitItem[1] + elif splitItem[0] == 'f_rate': + f_rate = splitItem[1] + elif splitItem[0] == 'f_dir': + f_dir = splitItem[1] + elif splitItem[0] == 'dt1': + dt1 = splitItem[1] + elif splitItem[0] == 'dt2': + dt2 = splitItem[1] + elif splitItem[0] == 'dt3': + dt3 = splitItem[1] + elif splitItem[0] == 'dt4': + dt4 = splitItem[1] + elif splitItem[0] == 'dt5': + dt5 = splitItem[1] + elif splitItem[0] == 'dt7': + dt7 = splitItem[1] + elif splitItem[0] == 'dh1': + dh1 = splitItem[1] + elif splitItem[0] == 'dh2': + dh2 = splitItem[1] + elif splitItem[0] == 'dh3': + dh3 = splitItem[1] + elif splitItem[0] == 'dh4': + dh4 = splitItem[1] + elif splitItem[0] == 'dh5': + dh5 = splitItem[1] + elif splitItem[0] == 'dh7': + dh7 = splitItem[1] + else: + info.thing.setStateValue(aircoConnectedStateTypeId, False) + info.finish(nymea.ThingErrorNoError) + + if info.actionTypeId == aircoPowerActionTypeId: + power = info.paramValue(aircoPowerActionPowerParamTypeId) + if power == True: + power = 1 + elif power == False: + power = 0 + logger.log("new power value", power) + elif info.actionTypeId == aircoTargetTemperatureActionTypeId: + targetTemp = str(info.paramValue(aircoTargetTemperatureActionTargetTemperatureParamTypeId)) + logger.log("new target temperature", targetTemp) + elif info.actionTypeId == aircoModeActionTypeId: + targetMode = info.paramValue(aircoModeActionModeParamTypeId) + if targetMode == "Automatic": + mode = 0 + targetTemp = dt1 + targetHum = dh1 + elif targetMode == "Cooling": + mode = 3 + targetTemp = dt3 + targetHum = dh3 + elif targetMode == "Heating": + mode = 4 + targetTemp = dt4 + targetHum = dh4 + elif targetMode == "Dehumidifier": + mode = 2 + targetTemp = dt2 + targetHum = dh2 + elif targetMode == "Fan": + mode = 6 + logger.log("new target mode:", targetMode, mode) + + # mandatory parameters: pow, mode, stemp, shum, f_rate, f_dir (f_rate & f_dir can't be set from the plugin yet, so we always use the values obtained above) + param = "pow="+str(power)+"&mode="+str(mode)+"&stemp="+targetTemp+"&shum="+targetHum+"&f_rate="+f_rate+"&f_dir="+f_dir + logger.log("parameters", param) + aUrl = "http://" + deviceIp + "/aircon/set_control_info?"+param + pr = requests.post(aUrl, headers=headers) + time.sleep(0.5) + pollAirco(info.thing) + info.finish(nymea.ThingErrorNoError) + return + + # Control Info Examples + # Switched Off + # ret=OK,pow=0,mode=7,adv=,stemp=24.0,shum=0,dt1=24.0,dt2=M,dt3=25.0,dt4=25.0,dt5=25.0,dt7=24.0,dh1=0,dh2=50,dh3=0,dh4=0,dh5=0,dh7=0, + # dhh=50,b_mode=7,b_stemp=24.0,b_shum=0,alert=255,f_rate=4,f_dir=0,b_f_rate=4,b_f_dir=0,dfr1=4,dfr2=5,dfr3=7,dfr4=5,dfr5=5,dfr6=5, + # dfr7=4,dfrh=5,dfd1=0,dfd2=0,dfd3=3,dfd4=0,dfd5=0,dfd6=0,dfd7=0,dfdh=0 + # Auto 25C ( CONFORT AIR ) ( INTELLIGENT EYE ) + # ret=OK,pow=1,mode=7,adv=,stemp=25.0,shum=0,dt1=25.0,dt2=M,dt3=22.0,dt4=25.0,dt5=25.0,dt7=25.0,dh1=0,dh2=50,dh3=0,dh4=0,dh5=0,dh7=0, + # dhh=50,b_mode=7,b_stemp=25.0,b_shum=0,alert=255,f_rate=A,f_dir=0,b_f_rate=4,b_f_dir=0,dfr1=4,dfr2=5,dfr3=4,dfr4=5,dfr5=5,dfr6=5, + # dfr7=4,dfrh=5,dfd1=0,dfd2=0,dfd3=0,dfd4=0,dfd5=0,dfd6=0,dfd7=0,dfdh=0 + # ret=OK,pow=1&dh2=50&dfd4=0&b_stemp=25.0&alert=255&f_dir=0&b_shum=0&dh4=0&dfd3=0&dh3=0&dfd2=0&dfr2=5&dfr7=B&dfr4=5&dfd7=0& + # dfrh=5&dt3=25.0&dfdh=0&adv=&dh5=0&dh1=0&dfr6=5&dt5=25.0&dfr1=B&stemp=25.0&shum=0&dfd6=0&f_rate=A&b_f_dir=0&dt1=25.0&dhh=50& + # dfd1=0&dfr3=5&dh7=0&mode=1&dfd5=0&b_mode=7&dt4=25.0&b_f_rate=A&dt7=25.0&dt2=M&dfr5=5 + # Hot 25c ( AIR silence ) + # ret=OK,pow=1,mode=4,adv=,stemp=25.0,shum=0,dt1=25.0,dt2=M,dt3=22.0,dt4=25.0,dt5=25.0,dt7=25.0,dh1=0,dh2=50,dh3=0,dh4=0,dh5=0,dh7=0, + # dhh=50,b_mode=4,b_stemp=25.0,b_shum=0,alert=255,f_rate=B,f_dir=0,b_f_rate=B,b_f_dir=0,dfr1=B,dfr2=B,dfr3=B,dfr4=B,dfr5=B,dfr6=B, + # dfr7=B,dfrh=5,dfd1=0,dfd2=0,dfd3=0,dfd4=0,dfd5=0,dfd6=0,dfd7=0,dfdh=0 + +def deinit(): + global pollTimer + # If we started a poll timer, cancel it on shutdown. + if pollTimer is not None: + pollTimer = None + +def thingRemoved(thing): + global pollTimer + logger.log("removeThing called for", thing.name) + # Clean up all data related to this thing + if len(myThings()) == 0 and pollTimer is not None: + pollTimer = None \ No newline at end of file diff --git a/daikinairco/meta.json b/daikinairco/meta.json new file mode 100644 index 00000000..ef6256f5 --- /dev/null +++ b/daikinairco/meta.json @@ -0,0 +1,12 @@ +{ + "title": "Daikin Airco", + "tagline": "Discover and control Daikin airconditioning devices", + "icon": "daikin.jpg", + "stability": "testing", + "offline": true, + "technologies": [ + "network" + ], + "categories": [ + ] +} diff --git a/daikinairco/requirements.txt b/daikinairco/requirements.txt new file mode 100644 index 00000000..2a65daa0 --- /dev/null +++ b/daikinairco/requirements.txt @@ -0,0 +1,61 @@ +testresources==2.0.1 \ + --hash=sha256:67a361c3a2412231963b91ab04192209aa91a1aa052f0ab87245dbea889d1282 \ + --hash=sha256:ee9d1982154a1e212d4e4bac6b610800bfb558e4fb853572a827bc14a96e4417 +requests==2.25.1 \ + --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ + --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e +urllib3==1.26.3 \ + --hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \ + --hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73 +chardet==4.0.0 \ + --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ + --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 +lazr.uri==1.0.5 \ + --hash=sha256:f36e7e40d5f8f2cf20ff2c81784a14a546e6c19c216d40a6617ebe0c96c92c49 \ + --hash=sha256:71f2faf04b148cf63d78da08ee5d8d6a7a7dbda8c9016b389a16f790d080c06f +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +wadllib==1.3.5 \ + --hash=sha256:84fecbaec2fef5ae2d7717a8115d271f18c6b5441eac861c58be8ca57f63c1d3 \ + --hash=sha256:67d3102b40eefdd6c3007cfbcc4c07f6948fec0666ba5c17d703eab21f054692 +lazr.restfulclient==0.14.3 \ + --hash=sha256:9f28bbb7c00374159376bd4ce36b4dacde7c6b86a0af625aa5e3ae214651a690 \ + --hash=sha256:2320e6d132c9a5148895e85be03274bc9305e4605439b03541ee3a618e00fb94 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +idna==2.5 \ + --hash=sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab \ + --hash=sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70 +certifi==2020.12.5 \ + --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \ + --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 +setuptools==54.1.1 \ + --hash=sha256:1ce82798848a978696465866bb3aaab356003c42d6143e1111fcf069ac838274 \ + --hash=sha256:75c5c4479f4961f1ffdb597c98aa4e4077e6813685025e8bdebf7598aa84e859 +pbr==5.5.1 \ + --hash=sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9 \ + --hash=sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00 +distro==1.5.0 \ + --hash=sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92 \ + --hash=sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +urllib3==1.26.3 \ + --hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \ + --hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73 +chardet==4.0.0 \ + --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ + --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 +httplib2==0.19.0 \ + --hash=sha256:749c32603f9bf16c1277f59531d502e8f1c2ca19901ae653b49c4ed698f0820e \ + --hash=sha256:e0d428dad43c72dbce7d163b7753ffc7a39c097e6788ef10f4198db69b92f08e +ifaddr==0.1.7 \ + --hash=sha256:1f9e8a6ca6f16db5a37d3356f07b6e52344f6f9f7e806d618537731669eb1a94 \ + --hash=sha256:d1f603952f0a71c9ab4e705754511e4e03b02565bc4cec7188ad6415ff534cd3