From 6f8ad8e80acf758c4ea3dd2e639f8a1918e01351 Mon Sep 17 00:00:00 2001 From: CJDumbleton <55947036+CJDumbleton@users.noreply.github.com> Date: Tue, 21 Feb 2023 11:22:09 +0000 Subject: [PATCH 01/31] Add support for non IR devices: Bot, Contact Sensor, Curtain and Meter (#21) * Update README.md * Added support for Bot, Curtain, Meter and Contact Sensor * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update switchbot.py * Update switchbot.py * Update switchbot.py * Update switchbot.py * Update README.md * Update README.md * Update switchbot.py --------- Co-authored-by: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> --- README.md | 57 +++++++++++++--- pyscript/apps/switchbot.py | 136 ++++++++++++++++++++++++++++++++++--- 2 files changed, 171 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 159ef24..b1eb382 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,10 @@ For more info click [here][switchbot-api-repo] - [Installation](#installation) - [How To Use](#how-to-use) - [Refresh Devices](#switchbot-refresh-devices) + - [Get Status](#switchbot-get-status) - [Turn On](#switchbot-turn-on) - [Turn Off](#switchbot-turn-off) + - [Curtain](#switchbot-curtain-control) - [HVAC](#switchbot-ir-hvac-control) - [Light](#switchbot-ir-light-control) - [Generic Command](#switchbot-generic-command) @@ -42,7 +44,11 @@ For more info click [here][switchbot-api-repo] cd /config git clone https://github.com/SiriosDev/SwitchBot-API-Script-Caller.git ``` -2. **Include [`pyscript/switchbot.yaml`](./pyscript/switchbot.yaml) in your `pyscript/config.yaml` under the `switchbot` section** +2. **Check if you have a `pyscript/config.yaml` file. If not, create one and then add the following in your main top-level configuration.yaml file.** + ``` yaml + pyscript: !include pyscript/config.yaml + ``` +3. **Include [`pyscript/switchbot.yaml`](./pyscript/switchbot.yaml) in your `pyscript/config.yaml` under the `switchbot` section** ```yaml # /config/pyscript/config.yaml allow_all_imports: true @@ -52,7 +58,7 @@ For more info click [here][switchbot-api-repo] switchbot: !include /config/SwitchBot-API-Script-Caller/pyscript/switchbot.yaml # (...) ``` -3. **Set the authentication secrets in `secrets.yaml` homeassistant file** +4. **Set the authentication secrets in `secrets.yaml` homeassistant file** - Random Value (`switchbot_nonc`) (I suggest using an UUID generator, but any unique alphanumeric string is fine) ```yaml # secrets.yaml @@ -63,7 +69,7 @@ For more info click [here][switchbot-api-repo] # Random Value: you can use a UUID generator, but any unique alphanumeric string is OK switchbot_nonc: xxxxxxxxxx ``` -4. **Link the files in the `pyscript` directory** +5. **Link the files in the `pyscript` directory** ```sh # use `mkdir -p /config/pyscript/apps/` if the directory doesn't exist cd /config/pyscript/apps/ @@ -79,10 +85,10 @@ cd SwitchBot-API-Script-Caller git pull ``` ⚠️ **See changelog before updating.** -The project is still in developpement and breaking changes may occurs. +The project is still in development and breaking changes may occurs. ### Installation Notes -- In order to see the `Developper options` in the Switchbot app (version ≥6.14), click repetively on the version number in the App's settings. +- In order to see the `Developer options` in the Switchbot app (version ≥6.14), click repetively on the version number in the App's settings.
Click here for detailed procedure @@ -103,13 +109,17 @@ It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devi - [SwitchBot Turn OFF (`pyscript.switchbot_turn_off`)](#switchbot-turn-off) - [SwitchBot IR HVAC Control (`pyscript.switchbot_hvac`)](#switchbot-ir-hvac-control) - [SwitchBot IR Light Control (`pyscript.switchbot_ir_light_control`)](#switchbot-ir-light-control) +- [SwitchBot Curtain Command (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) +- [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) - [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) ### 🔸SwitchBot Refresh Devices -_Create Home Assistant `switch` entity for each IR Device connected with your SwitchBot Hubs. Devices are stored as `switch.switchbot_remote_`._ -_`` correspond to the name of the device in the SwitchBot app._ -_if `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ -_The entities can then be used for sending commands using other functions of this pyscript. ⚠️ Not working stand alone ⚠️_ +_Creates Home Assistant `switch` entity for each IR Device and Bot connected to your SwitchBot Hub. IR devices and Bots are stored as `switch.switchbot_remote_`._ +_Creates Home Assistant `cover` entity for each Curtain, `binary_sensor` entity for each Contact Sensor and `sensor` entity for each Meter connected to your Switchbot Hub. These devices are stored as `.switchbot_remote_` eg `cover.switchbot_remote_bedroom_curtains`_ + +_`` corresponds to the name of the device in the SwitchBot app._ +_If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ +_The entities can then be used for sending commands or getting status using other functions of this pyscript. ⚠️ Not working stand alone ⚠️_ _In case the device doesn't exist in the future, you will be notified on your devices._ Parameters: ***None*** @@ -163,7 +173,7 @@ _Allows you to send any request to the API. (See [documentation][generic-cmd-lin - `device` - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - `command:` - - One of the command supported by the device. (see [documentation][generic-cmd-link]) + - One of the commands supported by the device. (see [documentation][generic-cmd-link]) - `parameter:` (optional) - Parameter for the command, if required (e.g. `SetChannel`) - use `default` if not used @@ -171,23 +181,48 @@ _Allows you to send any request to the API. (See [documentation][generic-cmd-lin - `command` for standard commands - `customize` for custom commands +### 🔸Switchbot Curtain Control +_Interface for Curtain (turnOn, turnOff, setPosition)_ +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `command:` + - string value between `turnOn`, `turnOff`, `setPosition` +- `parameter:` (optional) + - string giving the position for the setPosition command. (See the [SwitchbotAPI Curtain command]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#curtain-2)).) + +### 🔸Switchbot Get Status +_Gets the state of Switchbot Bots, Contact Sensors, Curtains and Meters._ +Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)) for the data returned from a status call. + +Parameters: ***None*** + ## Work in Progress The script works fine, but everything is still WIP, including this file. For any problems open an Issue, (soon I will insert a template for that). ## Changelog +### v? (🟢 New Features) +**Add support for non-IR devices** : Bot, Contact Sensor, Curtain and Meter. + +**Add service 'Switchbot Curtains Command'** : Send command to Curtain device. + +**Add time trigger to get the status of Bots, Contact Sensors, Curtains and Meters every 5 minutes.** This will show in the Logbook even if you only have IR devices. However, the API calls will only be made for non-IR devices. + +**Add time trigger to run 'Refresh Devices' at startup.** + + ### 2023.02.19 v0.2.1 (🛠️ Some Fixes) **Fixed ([#15][i15]) `commandType` parameters in `Generic Command`**.
*Suggest updating if you need to control custom remotes created in the mobile app*. - ### 2023.01.16 v0.2.0 (🟢 New Features and 🛠️ Some Fixes) **Add service `SwitchBot IR Light Control`**:
Send command via infrared to light device. + **Corrected some descriptions**. **Reworked the way `Refresh Devices` assigns `Friendly Names`**. diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index 34fab12..b9c744d 100644 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -6,6 +6,8 @@ KEY_DEV_TYPE='remoteType' KEY_DEV_NAME='deviceName' KEY_DEV_ID='deviceId' +KEY_NON_IR_TYPE='deviceType' +KEY_NON_IR_CLOUD='enableCloudService' # "input" from config def auth(token=None, secret=None, nonce=None): @@ -34,17 +36,25 @@ def gen_icon(dev): ico = icons[typ] return 'mdi:'+ico +def gen_icon(non_ir): + '''Generate icon based on device type. Default to a robot icon.''' + ico = 'robot' + icons = {'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer'} + typ = non_ir.get(KEY_NON_IR_TYPE) + if typ in icons: + ico = icons[typ] + return 'mdi:'+ico + def clear_existing(): '''Clear switchbot devices which were saved to avoid zombies.''' - states = state.names(domain=DOMAIN) - prefix = f'{DOMAIN}.{PREFIX}' + states = state.names() for s in states: - if s[0:len(prefix)] == prefix: + if PREFIX in s: log.warning(f"deleting sensor : {s}") state.delete(s) def gen_dev_uid(dev:dict): - '''Generate a Unique ID for Switchbot devices. (devices must have unique names in switchbot app so it works)''' + '''Generate a Unique ID for Switchbot IR devices. (devices must have unique names in switchbot app so it works)''' name = dev.get(KEY_DEV_NAME) if name is not None: name = name.lower() @@ -53,6 +63,26 @@ def gen_dev_uid(dev:dict): return PREFIX+name return PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) +def gen_non_ir_uid(non_ir:dict): + '''Generate a Unique ID for Switchbot non-IR devices. (devices must have unique names in switchbot app so it works)''' + name = non_ir.get(KEY_DEV_NAME) + domain = convert_switchbot_type_to_ha_domain(non_ir.get(KEY_NON_IR_TYPE)) + if name is not None: + name = name.lower() + name = re.sub(r'[^0-9a-z]+', '_', name) + if name not in ['_', '']: + return domain + '.' + PREFIX + name + return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(non_ir.get(KEY_NON_IR_TYPE)).lower())+'_'+str(non_ir.get(KEY_DEV_ID)[-4:]) + +def convert_switchbot_type_to_ha_domain(type): + if type == 'Curtain': + return 'cover' + elif type == 'Contact Sensor': + return 'binary_sensor' + elif type == 'Meter': + return 'sensor' + else: + return 'switch' def gen_dev_name(dev): name = ['Switchbot'] @@ -76,6 +106,21 @@ def extract_device_id(device, _recursived=0): #service.call('notify', 'notify', message=msg) return None +def create_ir_entities(devices): + for dev in devices: + log.warning(f"Adding Switchbot Device {dev.get(KEY_DEV_NAME)} [{dev.get(KEY_DEV_TYPE)}] -> {dev.get(KEY_DEV_ID)}") + dev['friendly_name'] = gen_dev_name(dev) + dev['icon'] = gen_icon(dev) + state.set(f'{DOMAIN}.{gen_dev_uid(dev)}', value=dev.get(KEY_DEV_ID), new_attributes=dev ) + +def create_non_ir_entities(devices): + for non_ir in devices: + if non_ir.get(KEY_NON_IR_CLOUD): # Only load devices that are cloud-connected. + log.warning(f"Adding Switchbot Device {non_ir.get(KEY_DEV_NAME)} [{non_ir.get(KEY_NON_IR_TYPE)}] -> {non_ir.get(KEY_DEV_ID)}") + non_ir['friendly_name'] = gen_dev_name(non_ir) + non_ir['icon'] = gen_icon(non_ir) + state.set(f'{gen_non_ir_uid(non_ir)}', value=non_ir.get(KEY_DEV_ID), new_attributes=non_ir) + switchbot_get_status() # Get an initial status for sensor-like devices. @pyscript_executor @@ -95,12 +140,26 @@ def command_execute(headers, device_id, command, parameter=None, custom=False): data["commandType"] = 'customize' requestHelper(url, data, headers) +def get_status(headers, device_id): + url=f"https://api.switch-bot.com/v1.1/devices/{device_id}/status" + r = requestGetHelper(url, {}, headers) + data = r.json() + status = data['statusCode'] + if status == 100: + return data + elif status == "n/a": + log.warning(f"Status request for {device_id} unauthorized. Http 401 Error. User permission is denied due to invalid token.") + elif status == 190: + log.warning(f"Status request for {device_id}. System error. Device internal error due to device states not synchronized with server.") + return None + #services @service +@time_trigger("startup") def switchbot_refresh_devices(): """yaml name: SwitchBot refresh devices -description: This service list registered (infrared) devices in the "Switchbot Hubs". The devices are saved as "switch.switchbot_remote_" in Home Assistant and can be used for other commands. +description: This service list registered devices in the "Switchbot Hubs". Devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. fields: """ headers_dict=auth(**pyscript.app_config) @@ -108,14 +167,12 @@ def switchbot_refresh_devices(): r = requestGetHelper(url, {}, headers_dict) log.info(str(r.json())) infrared = r.json()['body'].get("infraredRemoteList") - if infrared is None: + non_infrared = r.json()['body'].get("deviceList") + if infrared and non_infrared is None: return None clear_existing() - for dev in infrared: - log.warning(f"Adding Switchbot Device {dev.get(KEY_DEV_NAME)} [{dev.get(KEY_DEV_TYPE)}] -> {dev.get(KEY_DEV_ID)}") - dev['friendly_name'] = gen_dev_name(dev) - dev['icon'] = gen_icon(dev) - state.set(f'{DOMAIN}.{gen_dev_uid(dev)}', value=dev.get(KEY_DEV_ID), new_attributes=dev ) + create_ir_entities(infrared) + create_non_ir_entities(non_infrared) @service def switchbot_hvac(device, temperature, mode, fan_speed, state): @@ -331,3 +388,60 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command headers_dict = auth(**pyscript.app_config) command_execute(headers_dict, deviceId, command, parameter=parameter, custom=(commandType=='customize')) + +@service +def switchbot_curtain_command(device=None, command=None, parameter=None): + """yaml +name: SwitchBot Curtain Command +description: Control Switchbot curtain. +fields: + device: + name: Device + description: the name of the device as in the SwitchBot app + example: cover.switchbot_remote_bedroom_curtains + default: + required: true + selector: + entity: + domain: cover + + command: + name: Command + description: the name of the command + example: turnOff + default: + required: true + selector: + text: + + parameter: + name: Parameters + description: some commands require parameters, such as Position + example: + default: + required: false + selector: + select: + options: + - turnOn + - turnOff + - setPosition + mode: list + """ + deviceId = extract_device_id(device) + headers_dict = auth(**pyscript.app_config) + command_execute(headers_dict, deviceId, command, parameter=parameter) + +# Status checking +# Status requests got every 5 minutes (288 API calls / device / day). +@time_trigger("period(0:00, 300 sec)") +def switchbot_get_status(): + states = state.names() + for s in states: + if (PREFIX in s): + if KEY_NON_IR_TYPE in state.getattr(s).keys(): + deviceId = extract_device_id(s) + headers_dict = auth(**pyscript.app_config) + data = get_status(headers_dict, deviceId) + if data != None: + state.set(s, value=deviceId, new_attributes=data['body']) From 384c24c6b5f1437882fce556394f8adb52ccc749 Mon Sep 17 00:00:00 2001 From: SiriosDev <26876994+SiriosDev@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:47:23 +0100 Subject: [PATCH 02/31] fixing curtain service, and hvac defaults --- README.md | 57 ++++++------ pyscript/apps/switchbot.py | 177 +++++++++++++++++++++++-------------- 2 files changed, 140 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index b1eb382..096e30b 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ cd SwitchBot-API-Script-Caller git pull ``` ⚠️ **See changelog before updating.** -The project is still in development and breaking changes may occurs. +The project is still under development, and breaking changes may **frequently** occur. ### Installation Notes - In order to see the `Developer options` in the Switchbot app (version ≥6.14), click repetively on the version number in the App's settings. @@ -105,41 +105,60 @@ It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devi ### Summary - [SwitchBot Refresh Devices (`pyscript.switchbot_refresh_devices`)](#switchbot-refresh-devices) +- [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) - [SwitchBot Turn ON (`pyscript.switchbot_turn_on`)](#switchbot-turn-on) - [SwitchBot Turn OFF (`pyscript.switchbot_turn_off`)](#switchbot-turn-off) +- [SwitchBot Curtain Command (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) - [SwitchBot IR HVAC Control (`pyscript.switchbot_hvac`)](#switchbot-ir-hvac-control) - [SwitchBot IR Light Control (`pyscript.switchbot_ir_light_control`)](#switchbot-ir-light-control) -- [SwitchBot Curtain Command (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) -- [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) - [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) ### 🔸SwitchBot Refresh Devices -_Creates Home Assistant `switch` entity for each IR Device and Bot connected to your SwitchBot Hub. IR devices and Bots are stored as `switch.switchbot_remote_`._ -_Creates Home Assistant `cover` entity for each Curtain, `binary_sensor` entity for each Contact Sensor and `sensor` entity for each Meter connected to your Switchbot Hub. These devices are stored as `.switchbot_remote_` eg `cover.switchbot_remote_bedroom_curtains`_ +_Creates Home Assistant `switch` entity for each IR Device and Bot connected to your SwitchBot Hub,`cover` entity for each Curtain, `binary_sensor` entity for each Contact Sensor and `sensor` entity for each Meter connected to your Switchbot Hub. These devices are stored as `.switchbot_remote_` eg `cover.switchbot_remote_bedroom_curtains`_ -_`` corresponds to the name of the device in the SwitchBot app._ +_The `` corresponds to the name of the device in the SwitchBot app._ _If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ +_The `` is an internal unique code._ _The entities can then be used for sending commands or getting status using other functions of this pyscript. ⚠️ Not working stand alone ⚠️_ -_In case the device doesn't exist in the future, you will be notified on your devices._ +_If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ + +Parameters: ***None*** + +### 🔸Switchbot Get Status +_Gets the state of Switchbot Bots, Contact Sensors, Curtains and Meters._ +Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)) for the data returned from a status call. Parameters: ***None*** ### 🔸SwitchBot Turn On -_Turn a device ON_ +_Turn a device ON._ Parameters: - `device` - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). ### 🔸SwitchBot Turn Off -_Turn a device OFF_ +_Turn a device OFF._ Parameters: - `device` - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +### 🔸Switchbot Curtain Control +_Interface for Curtain (turnOn, turnOff, setPosition) devices._ +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `command:` + - string value between `turnOn`, `turnOff`, `setPosition` +- `index:` + - int value between `?????` (required for "setPosition" command, otherwise it will be ignored) +- `mode:` + - string value between `Performance`, `Silent`, `Default` (required for "setPosition" command, otherwise it will be ignored) +- `position:` + - int value (in percentage) between `0` and `100` (required for "setPosition" command, otherwise it will be ignored) + ### 🔸SwitchBot IR HVAC Control -_Interface for infrared HVAC (heating, ventilation and air conditioning) device._ +_Interface for infrared HVAC (heating, ventilation and air conditioning) devices._ **Parameters:** - `device` @@ -155,7 +174,7 @@ _Interface for infrared HVAC (heating, ventilation and air conditioning) device. ### 🔸SwitchBot IR Light Control -_Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) device._ +_Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) devices._ **Parameters:** - `device` @@ -181,21 +200,6 @@ _Allows you to send any request to the API. (See [documentation][generic-cmd-lin - `command` for standard commands - `customize` for custom commands -### 🔸Switchbot Curtain Control -_Interface for Curtain (turnOn, turnOff, setPosition)_ -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). -- `command:` - - string value between `turnOn`, `turnOff`, `setPosition` -- `parameter:` (optional) - - string giving the position for the setPosition command. (See the [SwitchbotAPI Curtain command]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#curtain-2)).) - -### 🔸Switchbot Get Status -_Gets the state of Switchbot Bots, Contact Sensors, Curtains and Meters._ -Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)) for the data returned from a status call. - -Parameters: ***None*** - ## Work in Progress The script works fine, but everything is still WIP, including this file. @@ -222,7 +226,6 @@ For any problems open an Issue, (soon I will insert a template for that). **Add service `SwitchBot IR Light Control`**:
Send command via infrared to light device. - **Corrected some descriptions**. **Reworked the way `Refresh Devices` assigns `Friendly Names`**. diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index b9c744d..c8189ef 100644 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -175,7 +175,7 @@ def switchbot_refresh_devices(): create_non_ir_entities(non_infrared) @service -def switchbot_hvac(device, temperature, mode, fan_speed, state): +def switchbot_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): """yaml name: SwitchBot IR HVAC Control description: Control IR HVAC "saved" in "Switchbot Hubs" @@ -190,12 +190,25 @@ def switchbot_hvac(device, temperature, mode, fan_speed, state): entity: domain: switch + state: + name: Power State + description: HVAC Power State. (on by Default) + example: off + default: on + required: true + selector: + select: + options: + - on + - off + mode: list + temperature: name: Temperature - description: HVAC Target Temperature in Celsius (min 16 - Max 30) + description: HVAC Target Temperature in Celsius (min 16 - Max 30). (26 by Default) example: 26 - default: - required: true + default: 26 + required: false selector: number: min: 16 @@ -206,10 +219,10 @@ def switchbot_hvac(device, temperature, mode, fan_speed, state): mode: name: Mode - description: HVAC Mode Selector 1 (auto), 2 (cool), 3 (dry), 4 (fan), 5 (heat) + description: HVAC Mode Selector 1 (auto), 2 (cool), 3 (dry), 4 (fan), 5 (heat). (1 by Default) example: 1 - default: - required: true + default: 1 + required: false selector: number: min: 1 @@ -219,32 +232,25 @@ def switchbot_hvac(device, temperature, mode, fan_speed, state): fan_speed: name: Fan Speed - description: HVAC Fan Speed Selector 1 (auto), 2 (low), 3 (medium), 4 (high) + description: HVAC Fan Speed Selector 1 (auto), 2 (low), 3 (medium), 4 (high). (1 by Default) example: 1 - default: - required: true + default: 1 + required: false selector: number: min: 1 max: 4 step: 1 mode: box - - state: - name: Power State - description: HVAC Power State - example: off - default: - required: true - selector: - select: - options: - - on - - off - mode: list """ deviceId = extract_device_id(device) headers_dict = auth(**pyscript.app_config) + if temperature == None: + temperature = 26 + if mode == None: + mode = 1 + if fan_speed == None: + fan_speed = 1 command_execute(headers_dict, deviceId, 'setAll', parameter=f"{temperature},{mode},{fan_speed},{state}") @service @@ -294,6 +300,87 @@ def switchbot_ir_light_control(device=None, command=None, steps=None): device_id = extract_device_id(device) headers = auth(**pyscript.app_config) command_execute(headers, device_id, command) + + +@service +def switchbot_curtain_command(device=None, command=None, index=None, mode=None, position=None): + """yaml +name: SwitchBot Curtain Command +description: Control Switchbot curtain. +fields: + device: + name: Device + description: Target device + example: cover.switchbot_remote_bedroom_curtains + default: + required: true + selector: + entity: + domain: cover + + command: + name: Command + description: Select a Command + example: turnOff + default: + required: true + selector: + select: + options: + - turnOn + - turnOff + - setPosition + mode: list + + index: + name: Indexs + description: waiting for switchbot team clarifications + example: + default: + required: false + selector: + number: + min: 0 + max: 1 + step: 1 + mode: box + + mode: + name: Modes + description: Select a Mode (required for "setPosition" command) + example: Performance + default: Default + required: false + selector: + select: + options: + - Performance + - Silent + - Default + mode: list + + position: + name: Position + description: Select a Mode (0%-100%) (required for "setPosition" command) + example: 50 + default: 50 + required: false + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: % + mode: slider + """ + + deviceId = extract_device_id(device) + headers_dict = auth(**pyscript.app_config) + modes={"Performance": "0", "Silent": "1", "Default": "ff"} + if command == "turnOn" or command == "turnOff": + command_execute(headers_dict, deviceId, command, parameter=None) + else: + command_execute(headers_dict, deviceId, command, parameter=f"{index},{modes[mode]},{position}") @service def switchbot_turn_on(device=None): @@ -387,50 +474,6 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command deviceId = extract_device_id(device) headers_dict = auth(**pyscript.app_config) command_execute(headers_dict, deviceId, command, parameter=parameter, custom=(commandType=='customize')) - - -@service -def switchbot_curtain_command(device=None, command=None, parameter=None): - """yaml -name: SwitchBot Curtain Command -description: Control Switchbot curtain. -fields: - device: - name: Device - description: the name of the device as in the SwitchBot app - example: cover.switchbot_remote_bedroom_curtains - default: - required: true - selector: - entity: - domain: cover - - command: - name: Command - description: the name of the command - example: turnOff - default: - required: true - selector: - text: - - parameter: - name: Parameters - description: some commands require parameters, such as Position - example: - default: - required: false - selector: - select: - options: - - turnOn - - turnOff - - setPosition - mode: list - """ - deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) - command_execute(headers_dict, deviceId, command, parameter=parameter) # Status checking # Status requests got every 5 minutes (288 API calls / device / day). From f754797d4daa9aa6e295a81f77dcdd8822e2f50e Mon Sep 17 00:00:00 2001 From: SiriosDev <26876994+SiriosDev@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:02:09 +0100 Subject: [PATCH 03/31] removed 300 sec time trigger for testing --- pyscript/apps/switchbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index c8189ef..b3e5146 100644 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -477,7 +477,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command # Status checking # Status requests got every 5 minutes (288 API calls / device / day). -@time_trigger("period(0:00, 300 sec)") +#@time_trigger("period(0:00, 300 sec)") def switchbot_get_status(): states = state.names() for s in states: From dd1ed64f5649300c91d6adf5621b3d5b8df43965 Mon Sep 17 00:00:00 2001 From: SiriosDev <26876994+SiriosDev@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:21:23 +0100 Subject: [PATCH 04/31] missing quotes --- pyscript/apps/switchbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index b3e5146..a6fa282 100644 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -370,7 +370,7 @@ def switchbot_curtain_command(device=None, command=None, index=None, mode=None, min: 0 max: 100 step: 1 - unit_of_measurement: % + unit_of_measurement: "%" mode: slider """ From 1f638d3d8f34c82dfcffd9863478dca936bc6128 Mon Sep 17 00:00:00 2001 From: SiriosDev <26876994+SiriosDev@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:28:45 +0100 Subject: [PATCH 05/31] index little fix --- pyscript/apps/switchbot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index a6fa282..7a10cc6 100644 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -335,8 +335,8 @@ def switchbot_curtain_command(device=None, command=None, index=None, mode=None, index: name: Indexs description: waiting for switchbot team clarifications - example: - default: + example: 0 + default: 0 required: false selector: number: From b59855c919d38895dd74415ce991bbca71a2e81f Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:59:16 +0100 Subject: [PATCH 06/31] . --- README.md | 536 ++++++++++---------- pyscript/apps/switchbot.py | 980 ++++++++++++++++++------------------- 2 files changed, 758 insertions(+), 758 deletions(-) mode change 100644 => 100755 README.md mode change 100644 => 100755 pyscript/apps/switchbot.py diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 096e30b..407d052 --- a/README.md +++ b/README.md @@ -1,268 +1,268 @@ -[![License][licensing-shield]](LICENSE) -All rights to the [API][switchbot-api-repo] belong to [OpenWonderLabs][OpenWonderLabs-lnk]. - -# SwitchBot API Script Caller (v 0.2.1) - -This (Py)Script allows you to control all (WIP) your SwitchBot devices via API calls (1.1). - -‼️‼️At the moment we have implemented all the devices that we can test, so if you want to be a tester please open an [issue][issues] with "test" in the title or if you are also able to develop go ahead and do a [PR][pr]‼️‼️ - -For more info click [here][switchbot-api-repo] - -- [Requirements](#requirements) -- [Installation](#installation) -- [How To Use](#how-to-use) - - [Refresh Devices](#switchbot-refresh-devices) - - [Get Status](#switchbot-get-status) - - [Turn On](#switchbot-turn-on) - - [Turn Off](#switchbot-turn-off) - - [Curtain](#switchbot-curtain-control) - - [HVAC](#switchbot-ir-hvac-control) - - [Light](#switchbot-ir-light-control) - - [Generic Command](#switchbot-generic-command) -- [Work in Progress](#work-in-progress) -- [Changelog](#changelog) - -## Requirements - -- HACS ([docs][hacs-docs]) - - PyScript Integration ([docs][pyscript-docs]) - -## Installation -⚠️ **If you update from one of the following versions there have been breaking changes:** -
-Versions - -- [Unnumbered (2023.01.14)][unnum] - -
- - -### Procedure -1. **Clone this repository in your config folder** - ```sh - cd /config - git clone https://github.com/SiriosDev/SwitchBot-API-Script-Caller.git - ``` -2. **Check if you have a `pyscript/config.yaml` file. If not, create one and then add the following in your main top-level configuration.yaml file.** - ``` yaml - pyscript: !include pyscript/config.yaml - ``` -3. **Include [`pyscript/switchbot.yaml`](./pyscript/switchbot.yaml) in your `pyscript/config.yaml` under the `switchbot` section** - ```yaml - # /config/pyscript/config.yaml - allow_all_imports: true - apps: - # (...) - # ↓↓↓ attention indentation - switchbot: !include /config/SwitchBot-API-Script-Caller/pyscript/switchbot.yaml - # (...) - ``` -4. **Set the authentication secrets in `secrets.yaml` homeassistant file** - - Random Value (`switchbot_nonc`) (I suggest using an UUID generator, but any unique alphanumeric string is fine) - ```yaml - # secrets.yaml - # (...) - # Token and Secret Key : from `Developer Option` in the SwitchBot App (version ≥6.14) - switchbot_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - switchbot_sec: xxxxxxxxxxxx - # Random Value: you can use a UUID generator, but any unique alphanumeric string is OK - switchbot_nonc: xxxxxxxxxx - ``` -5. **Link the files in the `pyscript` directory** - ```sh - # use `mkdir -p /config/pyscript/apps/` if the directory doesn't exist - cd /config/pyscript/apps/ - - # Create a symbolic link to the apps directory named switchbot - ln -s /config/SwitchBot-API-Script-Caller/pyscript/apps/switchbot.py switchbot.py - ``` - -### Further Update -By following this procedure, the script can then be updated with newer version using git. -```sh -cd SwitchBot-API-Script-Caller -git pull -``` -⚠️ **See changelog before updating.** -The project is still under development, and breaking changes may **frequently** occur. - -### Installation Notes -- In order to see the `Developer options` in the Switchbot app (version ≥6.14), click repetively on the version number in the App's settings. -
- Click here for detailed procedure - - ![SwitchBot](https://user-images.githubusercontent.com/26876994/210898538-5d07a304-3446-48e0-b020-69140ba89b45.png) - -
-- A symbolic link is symbolic and represent the exact path you enter, if you move the targeted file or if the target is outside of the container (e.g. when using docker) the link will not work. Make sure that you are using a relative path that is accessible for the host reading the link. -- Ensure that `pyscript` is operational before to install this script. -- Except dirs strictly related to pyscript, all others dir are recommended, so organize them as you like, keeping in mind that changing the contents of the "`clone`", could cause the update via `git pull` to fail. - -## How To Use -This script (for now) provides the following services in home assisant. -It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devices) first in order to be able to use the other features, as it will generate the required Home Assistant entities for your devices. - -### Summary -- [SwitchBot Refresh Devices (`pyscript.switchbot_refresh_devices`)](#switchbot-refresh-devices) -- [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) -- [SwitchBot Turn ON (`pyscript.switchbot_turn_on`)](#switchbot-turn-on) -- [SwitchBot Turn OFF (`pyscript.switchbot_turn_off`)](#switchbot-turn-off) -- [SwitchBot Curtain Command (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) -- [SwitchBot IR HVAC Control (`pyscript.switchbot_hvac`)](#switchbot-ir-hvac-control) -- [SwitchBot IR Light Control (`pyscript.switchbot_ir_light_control`)](#switchbot-ir-light-control) -- [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) - -### 🔸SwitchBot Refresh Devices -_Creates Home Assistant `switch` entity for each IR Device and Bot connected to your SwitchBot Hub,`cover` entity for each Curtain, `binary_sensor` entity for each Contact Sensor and `sensor` entity for each Meter connected to your Switchbot Hub. These devices are stored as `.switchbot_remote_` eg `cover.switchbot_remote_bedroom_curtains`_ - -_The `` corresponds to the name of the device in the SwitchBot app._ -_If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ -_The `` is an internal unique code._ -_The entities can then be used for sending commands or getting status using other functions of this pyscript. ⚠️ Not working stand alone ⚠️_ -_If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ - -Parameters: ***None*** - -### 🔸Switchbot Get Status -_Gets the state of Switchbot Bots, Contact Sensors, Curtains and Meters._ -Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)) for the data returned from a status call. - -Parameters: ***None*** - -### 🔸SwitchBot Turn On -_Turn a device ON._ - -Parameters: -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - -### 🔸SwitchBot Turn Off -_Turn a device OFF._ - -Parameters: -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - -### 🔸Switchbot Curtain Control -_Interface for Curtain (turnOn, turnOff, setPosition) devices._ -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). -- `command:` - - string value between `turnOn`, `turnOff`, `setPosition` -- `index:` - - int value between `?????` (required for "setPosition" command, otherwise it will be ignored) -- `mode:` - - string value between `Performance`, `Silent`, `Default` (required for "setPosition" command, otherwise it will be ignored) -- `position:` - - int value (in percentage) between `0` and `100` (required for "setPosition" command, otherwise it will be ignored) - -### 🔸SwitchBot IR HVAC Control -_Interface for infrared HVAC (heating, ventilation and air conditioning) devices._ - -**Parameters:** -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). -- `temperature:` - - int value from `16` to `30` -- `mode:` - - int value between `1` (auto), `2` (cool), `3` (dry), `4` (fan), `5` (heat) -- `fan_speed:` - - int value between `1` (auto), `2` (low), `3` (medium), `4` (high) -- `state:` - - string value between `on` and `off` - - -### 🔸SwitchBot IR Light Control -_Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) devices._ - -**Parameters:** -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). -- `command:` - - string value between `turnOn`, `turnOff`, `brightnessUp` and `brightnessDown` -- `steps:` - - int value from `1` to `10`, _only works with `brightnessUp/Down`_, iterates the command as many times as selected. - - -### 🔸SwitchBot Generic Command -_Allows you to send any request to the API. (See [documentation][generic-cmd-link])_ - -**Parameters:** -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). -- `command:` - - One of the commands supported by the device. (see [documentation][generic-cmd-link]) -- `parameter:` (optional) - - Parameter for the command, if required (e.g. `SetChannel`) - - use `default` if not used -- `commandType:` - - `command` for standard commands - - `customize` for custom commands - - -## Work in Progress -The script works fine, but everything is still WIP, including this file. -For any problems open an Issue, (soon I will insert a template for that). - -## Changelog -### v? (🟢 New Features) -**Add support for non-IR devices** : Bot, Contact Sensor, Curtain and Meter. - -**Add service 'Switchbot Curtains Command'** : Send command to Curtain device. - -**Add time trigger to get the status of Bots, Contact Sensors, Curtains and Meters every 5 minutes.** This will show in the Logbook even if you only have IR devices. However, the API calls will only be made for non-IR devices. - -**Add time trigger to run 'Refresh Devices' at startup.** - - -### 2023.02.19 v0.2.1 (🛠️ Some Fixes) - -**Fixed ([#15][i15]) `commandType` parameters in `Generic Command`**.
-*Suggest updating if you need to control custom remotes created in the mobile app*. - - -### 2023.01.16 v0.2.0 (🟢 New Features and 🛠️ Some Fixes) -**Add service `SwitchBot IR Light Control`**:
- Send command via infrared to light device. - -**Corrected some descriptions**. - -**Reworked the way `Refresh Devices` assigns `Friendly Names`**. - -**Removed notifications to all channels in case of errors during `Refresh Devices`**. - -**Now HVAC will have a dedicated icon once the dummy switch is created (`Refresh Devices`)**. - -**Renamed `SwitchBot HVAC API Interface` in `SwitchBot IR HVAC Control`**: _it doesn't affect function it's just a visual thing_. - -**Renamed `SwitchBot Generic Command API Interface` in `SwitchBot Generic Command`**: _it doesn't affect function it's just a visual thing_. - - -### 2023.01.14 v0.1.0 (⚠️ Breaking changes) -**Add service `SwitchBot Refresh Devices`**:
-Retrieves your IR devices from the API. Services now requires `device` instead of `deviceId`. No need to copy paste the id manually anymore. - -Previously: - - Services param was `deviceId` - -Now: - - Services Param is `device` (home assistant ID for sensor, e.g. `switch.switchbot_remote_my_light`) - -Make sure to run `SwitchBot Refresh Devices` before configuring anything else. - -[Full Changelog History here][Changelog] - - -[licensing-shield]: https://img.shields.io/github/license/SiriosDev/SwitchBot-API-Script-Caller?style=flat-square -[hacs-docs]: https://hacs.xyz/docs/setup/prerequisites -[pyscript-docs]: https://hacs-pyscript.readthedocs.io/en/latest/installation.html -[switchbot-api-repo]: https://github.com/OpenWonderLabs/SwitchBotAPI -[OpenWonderLabs-lnk]: https://github.com/OpenWonderLabs -[generic-cmd-link]: https://github.com/OpenWonderLabs/SwitchBotAPI#send-device-control-commands -[deviceid-link]: https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-list -[issues]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/issues/new -[pr]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/pulls -[Changelog]: CHANGELOG.md -[i15]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/issues/15 -[unnum]: #20230114-v010-%EF%B8%8F-breaking-changes +[![License][licensing-shield]](LICENSE) +All rights to the [API][switchbot-api-repo] belong to [OpenWonderLabs][OpenWonderLabs-lnk]. + +# SwitchBot API Script Caller (v 0.2.1) + +This (Py)Script allows you to control all (WIP) your SwitchBot devices via API calls (1.1). + +‼️‼️At the moment we have implemented all the devices that we can test, so if you want to be a tester please open an [issue][issues] with "test" in the title or if you are also able to develop go ahead and do a [PR][pr]‼️‼️ + +For more info click [here][switchbot-api-repo] + +- [Requirements](#requirements) +- [Installation](#installation) +- [How To Use](#how-to-use) + - [Refresh Devices](#switchbot-refresh-devices) + - [Get Status](#switchbot-get-status) + - [Turn On](#switchbot-turn-on) + - [Turn Off](#switchbot-turn-off) + - [Curtain](#switchbot-curtain-control) + - [HVAC](#switchbot-ir-hvac-control) + - [Light](#switchbot-ir-light-control) + - [Generic Command](#switchbot-generic-command) +- [Work in Progress](#work-in-progress) +- [Changelog](#changelog) + +## Requirements + +- HACS ([docs][hacs-docs]) + - PyScript Integration ([docs][pyscript-docs]) + +## Installation +⚠️ **If you update from one of the following versions there have been breaking changes:** +
+Versions + +- [Unnumbered (2023.01.14)][unnum] + +
+ + +### Procedure +1. **Clone this repository in your config folder** + ```sh + cd /config + git clone https://github.com/SiriosDev/SwitchBot-API-Script-Caller.git + ``` +2. **Check if you have a `pyscript/config.yaml` file. If not, create one and then add the following in your main top-level configuration.yaml file.** + ``` yaml + pyscript: !include pyscript/config.yaml + ``` +3. **Include [`pyscript/switchbot.yaml`](./pyscript/switchbot.yaml) in your `pyscript/config.yaml` under the `switchbot` section** + ```yaml + # /config/pyscript/config.yaml + allow_all_imports: true + apps: + # (...) + # ↓↓↓ attention indentation + switchbot: !include /config/SwitchBot-API-Script-Caller/pyscript/switchbot.yaml + # (...) + ``` +4. **Set the authentication secrets in `secrets.yaml` homeassistant file** + - Random Value (`switchbot_nonc`) (I suggest using an UUID generator, but any unique alphanumeric string is fine) + ```yaml + # secrets.yaml + # (...) + # Token and Secret Key : from `Developer Option` in the SwitchBot App (version ≥6.14) + switchbot_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + switchbot_sec: xxxxxxxxxxxx + # Random Value: you can use a UUID generator, but any unique alphanumeric string is OK + switchbot_nonc: xxxxxxxxxx + ``` +5. **Link the files in the `pyscript` directory** + ```sh + # use `mkdir -p /config/pyscript/apps/` if the directory doesn't exist + cd /config/pyscript/apps/ + + # Create a symbolic link to the apps directory named switchbot + ln -s /config/SwitchBot-API-Script-Caller/pyscript/apps/switchbot.py switchbot.py + ``` + +### Further Update +By following this procedure, the script can then be updated with newer version using git. +```sh +cd SwitchBot-API-Script-Caller +git pull +``` +⚠️ **See changelog before updating.** +The project is still under development, and breaking changes may **frequently** occur. + +### Installation Notes +- In order to see the `Developer options` in the Switchbot app (version ≥6.14), click repetively on the version number in the App's settings. +
+ Click here for detailed procedure + + ![SwitchBot](https://user-images.githubusercontent.com/26876994/210898538-5d07a304-3446-48e0-b020-69140ba89b45.png) + +
+- A symbolic link is symbolic and represent the exact path you enter, if you move the targeted file or if the target is outside of the container (e.g. when using docker) the link will not work. Make sure that you are using a relative path that is accessible for the host reading the link. +- Ensure that `pyscript` is operational before to install this script. +- Except dirs strictly related to pyscript, all others dir are recommended, so organize them as you like, keeping in mind that changing the contents of the "`clone`", could cause the update via `git pull` to fail. + +## How To Use +This script (for now) provides the following services in home assisant. +It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devices) first in order to be able to use the other features, as it will generate the required Home Assistant entities for your devices. + +### Summary +- [SwitchBot Refresh Devices (`pyscript.switchbot_refresh_devices`)](#switchbot-refresh-devices) +- [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) +- [SwitchBot Turn ON (`pyscript.switchbot_turn_on`)](#switchbot-turn-on) +- [SwitchBot Turn OFF (`pyscript.switchbot_turn_off`)](#switchbot-turn-off) +- [SwitchBot Curtain Command (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) +- [SwitchBot IR HVAC Control (`pyscript.switchbot_hvac`)](#switchbot-ir-hvac-control) +- [SwitchBot IR Light Control (`pyscript.switchbot_ir_light_control`)](#switchbot-ir-light-control) +- [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) + +### 🔸SwitchBot Refresh Devices +_Creates Home Assistant `switch` entity for each IR Device and Bot connected to your SwitchBot Hub,`cover` entity for each Curtain, `binary_sensor` entity for each Contact Sensor and `sensor` entity for each Meter connected to your Switchbot Hub. These devices are stored as `.switchbot_remote_` eg `cover.switchbot_remote_bedroom_curtains`_ + +_The `` corresponds to the name of the device in the SwitchBot app._ +_If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ +_The `` is an internal unique code._ +_The entities can then be used for sending commands or getting status using other functions of this pyscript. ⚠️ Not working stand alone ⚠️_ +_If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ + +Parameters: ***None*** + +### 🔸Switchbot Get Status +_Gets the state of Switchbot Bots, Contact Sensors, Curtains and Meters._ +Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)) for the data returned from a status call. + +Parameters: ***None*** + +### 🔸SwitchBot Turn On +_Turn a device ON._ + +Parameters: +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + +### 🔸SwitchBot Turn Off +_Turn a device OFF._ + +Parameters: +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + +### 🔸Switchbot Curtain Control +_Interface for Curtain (turnOn, turnOff, setPosition) devices._ +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `command:` + - string value between `turnOn`, `turnOff`, `setPosition` +- `index:` + - int value between `?????` (required for "setPosition" command, otherwise it will be ignored) +- `mode:` + - string value between `Performance`, `Silent`, `Default` (required for "setPosition" command, otherwise it will be ignored) +- `position:` + - int value (in percentage) between `0` and `100` (required for "setPosition" command, otherwise it will be ignored) + +### 🔸SwitchBot IR HVAC Control +_Interface for infrared HVAC (heating, ventilation and air conditioning) devices._ + +**Parameters:** +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `temperature:` + - int value from `16` to `30` +- `mode:` + - int value between `1` (auto), `2` (cool), `3` (dry), `4` (fan), `5` (heat) +- `fan_speed:` + - int value between `1` (auto), `2` (low), `3` (medium), `4` (high) +- `state:` + - string value between `on` and `off` + + +### 🔸SwitchBot IR Light Control +_Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) devices._ + +**Parameters:** +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `command:` + - string value between `turnOn`, `turnOff`, `brightnessUp` and `brightnessDown` +- `steps:` + - int value from `1` to `10`, _only works with `brightnessUp/Down`_, iterates the command as many times as selected. + + +### 🔸SwitchBot Generic Command +_Allows you to send any request to the API. (See [documentation][generic-cmd-link])_ + +**Parameters:** +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `command:` + - One of the commands supported by the device. (see [documentation][generic-cmd-link]) +- `parameter:` (optional) + - Parameter for the command, if required (e.g. `SetChannel`) + - use `default` if not used +- `commandType:` + - `command` for standard commands + - `customize` for custom commands + + +## Work in Progress +The script works fine, but everything is still WIP, including this file. +For any problems open an Issue, (soon I will insert a template for that). + +## Changelog +### v? (🟢 New Features) +**Add support for non-IR devices** : Bot, Contact Sensor, Curtain and Meter. + +**Add service 'Switchbot Curtains Command'** : Send command to Curtain device. + +**Add time trigger to get the status of Bots, Contact Sensors, Curtains and Meters every 5 minutes.** This will show in the Logbook even if you only have IR devices. However, the API calls will only be made for non-IR devices. + +**Add time trigger to run 'Refresh Devices' at startup.** + + +### 2023.02.19 v0.2.1 (🛠️ Some Fixes) + +**Fixed ([#15][i15]) `commandType` parameters in `Generic Command`**.
+*Suggest updating if you need to control custom remotes created in the mobile app*. + + +### 2023.01.16 v0.2.0 (🟢 New Features and 🛠️ Some Fixes) +**Add service `SwitchBot IR Light Control`**:
+ Send command via infrared to light device. + +**Corrected some descriptions**. + +**Reworked the way `Refresh Devices` assigns `Friendly Names`**. + +**Removed notifications to all channels in case of errors during `Refresh Devices`**. + +**Now HVAC will have a dedicated icon once the dummy switch is created (`Refresh Devices`)**. + +**Renamed `SwitchBot HVAC API Interface` in `SwitchBot IR HVAC Control`**: _it doesn't affect function it's just a visual thing_. + +**Renamed `SwitchBot Generic Command API Interface` in `SwitchBot Generic Command`**: _it doesn't affect function it's just a visual thing_. + + +### 2023.01.14 v0.1.0 (⚠️ Breaking changes) +**Add service `SwitchBot Refresh Devices`**:
+Retrieves your IR devices from the API. Services now requires `device` instead of `deviceId`. No need to copy paste the id manually anymore. + +Previously: + - Services param was `deviceId` + +Now: + - Services Param is `device` (home assistant ID for sensor, e.g. `switch.switchbot_remote_my_light`) + +Make sure to run `SwitchBot Refresh Devices` before configuring anything else. + +[Full Changelog History here][Changelog] + + +[licensing-shield]: https://img.shields.io/github/license/SiriosDev/SwitchBot-API-Script-Caller?style=flat-square +[hacs-docs]: https://hacs.xyz/docs/setup/prerequisites +[pyscript-docs]: https://hacs-pyscript.readthedocs.io/en/latest/installation.html +[switchbot-api-repo]: https://github.com/OpenWonderLabs/SwitchBotAPI +[OpenWonderLabs-lnk]: https://github.com/OpenWonderLabs +[generic-cmd-link]: https://github.com/OpenWonderLabs/SwitchBotAPI#send-device-control-commands +[deviceid-link]: https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-list +[issues]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/issues/new +[pr]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/pulls +[Changelog]: CHANGELOG.md +[i15]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/issues/15 +[unnum]: #20230114-v010-%EF%B8%8F-breaking-changes diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py old mode 100644 new mode 100755 index 7a10cc6..1c9d6ad --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -1,490 +1,490 @@ -import time, hashlib, hmac, base64, requests -import re, yaml, io - -PREFIX="switchbot_remote_" -DOMAIN="switch" -KEY_DEV_TYPE='remoteType' -KEY_DEV_NAME='deviceName' -KEY_DEV_ID='deviceId' -KEY_NON_IR_TYPE='deviceType' -KEY_NON_IR_CLOUD='enableCloudService' - -# "input" from config -def auth(token=None, secret=None, nonce=None): - - token=str(token) - secret=str(secret) - nonce=str(nonce) - - t = int(round(time.time() * 1000)) - string_to_sign = '{}{}{}'.format(token, t, nonce) - - string_to_sign = bytes(string_to_sign, 'utf-8') - secret = bytes(secret, 'utf-8') - - sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest()) - - h={"Authorization": (str(token)), "t": (str(t)), "sign": (str(sign, 'utf-8')), "nonce": (str(nonce)), "Content-Type": "application/json; charset=utf8"} - return h - -def gen_icon(dev): - '''Generate icon based on device type. Default to a remote icon.''' - ico = 'remote' - icons = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner'} - typ = dev.get(KEY_DEV_TYPE) - if typ in icons: - ico = icons[typ] - return 'mdi:'+ico - -def gen_icon(non_ir): - '''Generate icon based on device type. Default to a robot icon.''' - ico = 'robot' - icons = {'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer'} - typ = non_ir.get(KEY_NON_IR_TYPE) - if typ in icons: - ico = icons[typ] - return 'mdi:'+ico - -def clear_existing(): - '''Clear switchbot devices which were saved to avoid zombies.''' - states = state.names() - for s in states: - if PREFIX in s: - log.warning(f"deleting sensor : {s}") - state.delete(s) - -def gen_dev_uid(dev:dict): - '''Generate a Unique ID for Switchbot IR devices. (devices must have unique names in switchbot app so it works)''' - name = dev.get(KEY_DEV_NAME) - if name is not None: - name = name.lower() - name = re.sub(r'[^0-9a-z]+', '_', name) - if name not in ['_', '']: - return PREFIX+name - return PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) - -def gen_non_ir_uid(non_ir:dict): - '''Generate a Unique ID for Switchbot non-IR devices. (devices must have unique names in switchbot app so it works)''' - name = non_ir.get(KEY_DEV_NAME) - domain = convert_switchbot_type_to_ha_domain(non_ir.get(KEY_NON_IR_TYPE)) - if name is not None: - name = name.lower() - name = re.sub(r'[^0-9a-z]+', '_', name) - if name not in ['_', '']: - return domain + '.' + PREFIX + name - return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(non_ir.get(KEY_NON_IR_TYPE)).lower())+'_'+str(non_ir.get(KEY_DEV_ID)[-4:]) - -def convert_switchbot_type_to_ha_domain(type): - if type == 'Curtain': - return 'cover' - elif type == 'Contact Sensor': - return 'binary_sensor' - elif type == 'Meter': - return 'sensor' - else: - return 'switch' - -def gen_dev_name(dev): - name = ['Switchbot'] - if (dev.get(KEY_DEV_NAME) is not None): - name.append(dev.get(KEY_DEV_NAME)) - #name.append(f"[IR {dev.get(KEY_DEV_TYPE)}]") - return ' '.join(name) - -def extract_device_id(device, _recursived=0): - ''' Retrieve the Switchbot DeviceId. force refresh the devices list if the device doesnt exists.''' - try: - uid = state.get(device) - return uid - except NameError: - if _recursived == 0: - switchbot_refresh_devices() - return extract_device_id(device, _recursived=_recursived+1) - else: - msg=f'Warning: impossible to find {device}. [pyscript SwitchBot]' - service.call('notify', 'persistent_notification', message=msg) - #service.call('notify', 'notify', message=msg) - return None - -def create_ir_entities(devices): - for dev in devices: - log.warning(f"Adding Switchbot Device {dev.get(KEY_DEV_NAME)} [{dev.get(KEY_DEV_TYPE)}] -> {dev.get(KEY_DEV_ID)}") - dev['friendly_name'] = gen_dev_name(dev) - dev['icon'] = gen_icon(dev) - state.set(f'{DOMAIN}.{gen_dev_uid(dev)}', value=dev.get(KEY_DEV_ID), new_attributes=dev ) - -def create_non_ir_entities(devices): - for non_ir in devices: - if non_ir.get(KEY_NON_IR_CLOUD): # Only load devices that are cloud-connected. - log.warning(f"Adding Switchbot Device {non_ir.get(KEY_DEV_NAME)} [{non_ir.get(KEY_NON_IR_TYPE)}] -> {non_ir.get(KEY_DEV_ID)}") - non_ir['friendly_name'] = gen_dev_name(non_ir) - non_ir['icon'] = gen_icon(non_ir) - state.set(f'{gen_non_ir_uid(non_ir)}', value=non_ir.get(KEY_DEV_ID), new_attributes=non_ir) - switchbot_get_status() # Get an initial status for sensor-like devices. - - -@pyscript_executor -def requestHelper(_url,_json,_headers): - x=requests.post(_url,json = _json, headers=_headers) - -@pyscript_executor -def requestGetHelper(_url,_json,_headers): - return requests.get(_url,json = _json, headers=_headers) - -def command_execute(headers, device_id, command, parameter=None, custom=False): - url=f"https://api.switch-bot.com/v1.1/devices/{device_id}/commands" - data= {"command": command, "commandType":"command"} - if parameter is not None: - data["parameter"] = parameter - if custom: - data["commandType"] = 'customize' - requestHelper(url, data, headers) - -def get_status(headers, device_id): - url=f"https://api.switch-bot.com/v1.1/devices/{device_id}/status" - r = requestGetHelper(url, {}, headers) - data = r.json() - status = data['statusCode'] - if status == 100: - return data - elif status == "n/a": - log.warning(f"Status request for {device_id} unauthorized. Http 401 Error. User permission is denied due to invalid token.") - elif status == 190: - log.warning(f"Status request for {device_id}. System error. Device internal error due to device states not synchronized with server.") - return None - -#services -@service -@time_trigger("startup") -def switchbot_refresh_devices(): - """yaml -name: SwitchBot refresh devices -description: This service list registered devices in the "Switchbot Hubs". Devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. -fields: - """ - headers_dict=auth(**pyscript.app_config) - url=f"https://api.switch-bot.com/v1.1/devices" - r = requestGetHelper(url, {}, headers_dict) - log.info(str(r.json())) - infrared = r.json()['body'].get("infraredRemoteList") - non_infrared = r.json()['body'].get("deviceList") - if infrared and non_infrared is None: - return None - clear_existing() - create_ir_entities(infrared) - create_non_ir_entities(non_infrared) - -@service -def switchbot_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): - """yaml -name: SwitchBot IR HVAC Control -description: Control IR HVAC "saved" in "Switchbot Hubs" -fields: - device: - name: Device - description: HVAC Target device - example: switch.switchbot_remote_hvac - default: - required: true - selector: - entity: - domain: switch - - state: - name: Power State - description: HVAC Power State. (on by Default) - example: off - default: on - required: true - selector: - select: - options: - - on - - off - mode: list - - temperature: - name: Temperature - description: HVAC Target Temperature in Celsius (min 16 - Max 30). (26 by Default) - example: 26 - default: 26 - required: false - selector: - number: - min: 16 - max: 30 - step: 1 - unit_of_measurement: "°C" - mode: box - - mode: - name: Mode - description: HVAC Mode Selector 1 (auto), 2 (cool), 3 (dry), 4 (fan), 5 (heat). (1 by Default) - example: 1 - default: 1 - required: false - selector: - number: - min: 1 - max: 5 - step: 1 - mode: box - - fan_speed: - name: Fan Speed - description: HVAC Fan Speed Selector 1 (auto), 2 (low), 3 (medium), 4 (high). (1 by Default) - example: 1 - default: 1 - required: false - selector: - number: - min: 1 - max: 4 - step: 1 - mode: box - """ - deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) - if temperature == None: - temperature = 26 - if mode == None: - mode = 1 - if fan_speed == None: - fan_speed = 1 - command_execute(headers_dict, deviceId, 'setAll', parameter=f"{temperature},{mode},{fan_speed},{state}") - -@service -def switchbot_ir_light_control(device=None, command=None, steps=None): - """yaml -name: SwitchBot IR Light Control -description: Control IR Light "saved" in "Switchbot Hubs" -fields: - device: - name: Device - description: Target device - example: switch.switchbot_remote_light - default: - required: true - selector: - entity: - domain: switch - command: - name: Command - description: Select a Command - example: turnOff - default: - required: true - selector: - select: - options: - - turnOn - - turnOff - - brightnessUp - - brightnessDown - mode: list - steps: - name: Steps - description: How many times to run the command (default 1), only works with brightnessUp/Down - example: turnOff - default: 1 - required: false - selector: - number: - min: 1 - max: 10 - mode: box - """ - if steps == None or command == "turnOn" or command== "turnOff": - steps=1 - for i in range(steps): - device_id = extract_device_id(device) - headers = auth(**pyscript.app_config) - command_execute(headers, device_id, command) - - -@service -def switchbot_curtain_command(device=None, command=None, index=None, mode=None, position=None): - """yaml -name: SwitchBot Curtain Command -description: Control Switchbot curtain. -fields: - device: - name: Device - description: Target device - example: cover.switchbot_remote_bedroom_curtains - default: - required: true - selector: - entity: - domain: cover - - command: - name: Command - description: Select a Command - example: turnOff - default: - required: true - selector: - select: - options: - - turnOn - - turnOff - - setPosition - mode: list - - index: - name: Indexs - description: waiting for switchbot team clarifications - example: 0 - default: 0 - required: false - selector: - number: - min: 0 - max: 1 - step: 1 - mode: box - - mode: - name: Modes - description: Select a Mode (required for "setPosition" command) - example: Performance - default: Default - required: false - selector: - select: - options: - - Performance - - Silent - - Default - mode: list - - position: - name: Position - description: Select a Mode (0%-100%) (required for "setPosition" command) - example: 50 - default: 50 - required: false - selector: - number: - min: 0 - max: 100 - step: 1 - unit_of_measurement: "%" - mode: slider - """ - - deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) - modes={"Performance": "0", "Silent": "1", "Default": "ff"} - if command == "turnOn" or command == "turnOff": - command_execute(headers_dict, deviceId, command, parameter=None) - else: - command_execute(headers_dict, deviceId, command, parameter=f"{index},{modes[mode]},{position}") - -@service -def switchbot_turn_on(device=None): - - """yaml -name: SwitchBot Turn Device ON -description: Turn Switchbot controlled device ON -fields: - device: - name: Device - description: Target device - example: switch.switchbot_remote_light - default: - required: true - selector: - entity: - domain: switch - """ - deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) - command_execute(headers_dict, deviceId, "turnOn") - -@service -def switchbot_turn_off(device=None): - """yaml -name: SwitchBot Turn Device OFF -description: Turn Switchbot controlled device OFF -fields: - device: - name: Device - description: Target device - example: switch.switchbot_remote_light - default: - required: true - selector: - entity: - domain: switch - """ - deviceId = extract_device_id(device) - headers_dict=auth(**pyscript.app_config) - command_execute(headers_dict, deviceId, "turnOff") - - -@service -def switchbot_generic_command(device=None, command=None, parameter=None, commandType=None): - """yaml -name: SwitchBot Generic Command -description: Control Switchbot Device through custom command(refer to https://github.com/OpenWonderLabs/SwitchBotAPI) -fields: - device: - name: Device - description: Target device (get req. to api) - example: switch.switchbot_remote_light - default: - required: true - selector: - entity: - domain: switch - - command: - name: Command - description: the name of the command - example: turnOff - default: - required: true - selector: - text: - - parameter: - name: Parameters - description: some commands require parameters, such as SetChannel - example: - default: - required: false - selector: - text: - - commandType: - name: Command Type - description: for customized buttons, this needs to be set to customzie - example: command - default: command - required: true - selector: - select: - options: - - command - - customize - - """ - deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) - command_execute(headers_dict, deviceId, command, parameter=parameter, custom=(commandType=='customize')) - -# Status checking -# Status requests got every 5 minutes (288 API calls / device / day). -#@time_trigger("period(0:00, 300 sec)") -def switchbot_get_status(): - states = state.names() - for s in states: - if (PREFIX in s): - if KEY_NON_IR_TYPE in state.getattr(s).keys(): - deviceId = extract_device_id(s) - headers_dict = auth(**pyscript.app_config) - data = get_status(headers_dict, deviceId) - if data != None: - state.set(s, value=deviceId, new_attributes=data['body']) +import time, hashlib, hmac, base64, requests +import re, yaml, io + +PREFIX="switchbot_remote_" +DOMAIN="switch" +KEY_DEV_TYPE='remoteType' +KEY_DEV_NAME='deviceName' +KEY_DEV_ID='deviceId' +KEY_NON_IR_TYPE='deviceType' +KEY_NON_IR_CLOUD='enableCloudService' + +# "input" from config +def auth(token=None, secret=None, nonce=None): + + token=str(token) + secret=str(secret) + nonce=str(nonce) + + t = int(round(time.time() * 1000)) + string_to_sign = '{}{}{}'.format(token, t, nonce) + + string_to_sign = bytes(string_to_sign, 'utf-8') + secret = bytes(secret, 'utf-8') + + sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest()) + + h={"Authorization": (str(token)), "t": (str(t)), "sign": (str(sign, 'utf-8')), "nonce": (str(nonce)), "Content-Type": "application/json; charset=utf8"} + return h + +def gen_icon(dev): + '''Generate icon based on device type. Default to a remote icon.''' + ico = 'remote' + icons = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner'} + typ = dev.get(KEY_DEV_TYPE) + if typ in icons: + ico = icons[typ] + return 'mdi:'+ico + +def gen_icon(non_ir): + '''Generate icon based on device type. Default to a robot icon.''' + ico = 'robot' + icons = {'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer'} + typ = non_ir.get(KEY_NON_IR_TYPE) + if typ in icons: + ico = icons[typ] + return 'mdi:'+ico + +def clear_existing(): + '''Clear switchbot devices which were saved to avoid zombies.''' + states = state.names() + for s in states: + if PREFIX in s: + log.warning(f"deleting sensor : {s}") + state.delete(s) + +def gen_dev_uid(dev:dict): + '''Generate a Unique ID for Switchbot IR devices. (devices must have unique names in switchbot app so it works)''' + name = dev.get(KEY_DEV_NAME) + if name is not None: + name = name.lower() + name = re.sub(r'[^0-9a-z]+', '_', name) + if name not in ['_', '']: + return PREFIX+name + return PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) + +def gen_non_ir_uid(non_ir:dict): + '''Generate a Unique ID for Switchbot non-IR devices. (devices must have unique names in switchbot app so it works)''' + name = non_ir.get(KEY_DEV_NAME) + domain = convert_switchbot_type_to_ha_domain(non_ir.get(KEY_NON_IR_TYPE)) + if name is not None: + name = name.lower() + name = re.sub(r'[^0-9a-z]+', '_', name) + if name not in ['_', '']: + return domain + '.' + PREFIX + name + return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(non_ir.get(KEY_NON_IR_TYPE)).lower())+'_'+str(non_ir.get(KEY_DEV_ID)[-4:]) + +def convert_switchbot_type_to_ha_domain(type): + if type == 'Curtain': + return 'cover' + elif type == 'Contact Sensor': + return 'binary_sensor' + elif type == 'Meter': + return 'sensor' + else: + return 'switch' + +def gen_dev_name(dev): + name = ['Switchbot'] + if (dev.get(KEY_DEV_NAME) is not None): + name.append(dev.get(KEY_DEV_NAME)) + #name.append(f"[IR {dev.get(KEY_DEV_TYPE)}]") + return ' '.join(name) + +def extract_device_id(device, _recursived=0): + ''' Retrieve the Switchbot DeviceId. force refresh the devices list if the device doesnt exists.''' + try: + uid = state.get(device) + return uid + except NameError: + if _recursived == 0: + switchbot_refresh_devices() + return extract_device_id(device, _recursived=_recursived+1) + else: + msg=f'Warning: impossible to find {device}. [pyscript SwitchBot]' + service.call('notify', 'persistent_notification', message=msg) + #service.call('notify', 'notify', message=msg) + return None + +def create_ir_entities(devices): + for dev in devices: + log.warning(f"Adding Switchbot Device {dev.get(KEY_DEV_NAME)} [{dev.get(KEY_DEV_TYPE)}] -> {dev.get(KEY_DEV_ID)}") + dev['friendly_name'] = gen_dev_name(dev) + dev['icon'] = gen_icon(dev) + state.set(f'{DOMAIN}.{gen_dev_uid(dev)}', value=dev.get(KEY_DEV_ID), new_attributes=dev ) + +def create_non_ir_entities(devices): + for non_ir in devices: + if non_ir.get(KEY_NON_IR_CLOUD): # Only load devices that are cloud-connected. + log.warning(f"Adding Switchbot Device {non_ir.get(KEY_DEV_NAME)} [{non_ir.get(KEY_NON_IR_TYPE)}] -> {non_ir.get(KEY_DEV_ID)}") + non_ir['friendly_name'] = gen_dev_name(non_ir) + non_ir['icon'] = gen_icon(non_ir) + state.set(f'{gen_non_ir_uid(non_ir)}', value=non_ir.get(KEY_DEV_ID), new_attributes=non_ir) + switchbot_get_status() # Get an initial status for sensor-like devices. + + +@pyscript_executor +def requestHelper(_url,_json,_headers): + x=requests.post(_url,json = _json, headers=_headers) + +@pyscript_executor +def requestGetHelper(_url,_json,_headers): + return requests.get(_url,json = _json, headers=_headers) + +def command_execute(headers, device_id, command, parameter=None, custom=False): + url=f"https://api.switch-bot.com/v1.1/devices/{device_id}/commands" + data= {"command": command, "commandType":"command"} + if parameter is not None: + data["parameter"] = parameter + if custom: + data["commandType"] = 'customize' + requestHelper(url, data, headers) + +def get_status(headers, device_id): + url=f"https://api.switch-bot.com/v1.1/devices/{device_id}/status" + r = requestGetHelper(url, {}, headers) + data = r.json() + status = data['statusCode'] + if status == 100: + return data + elif status == "n/a": + log.warning(f"Status request for {device_id} unauthorized. Http 401 Error. User permission is denied due to invalid token.") + elif status == 190: + log.warning(f"Status request for {device_id}. System error. Device internal error due to device states not synchronized with server.") + return None + +#services +@service +@time_trigger("startup") +def switchbot_refresh_devices(): + """yaml +name: SwitchBot refresh devices +description: This service list registered devices in the "Switchbot Hubs". Devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. +fields: + """ + headers_dict=auth(**pyscript.app_config) + url=f"https://api.switch-bot.com/v1.1/devices" + r = requestGetHelper(url, {}, headers_dict) + log.info(str(r.json())) + infrared = r.json()['body'].get("infraredRemoteList") + non_infrared = r.json()['body'].get("deviceList") + if infrared and non_infrared is None: + return None + clear_existing() + create_ir_entities(infrared) + create_non_ir_entities(non_infrared) + +@service +def switchbot_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): + """yaml +name: SwitchBot IR HVAC Control +description: Control IR HVAC "saved" in "Switchbot Hubs" +fields: + device: + name: Device + description: HVAC Target device + example: switch.switchbot_remote_hvac + default: + required: true + selector: + entity: + domain: switch + + state: + name: Power State + description: HVAC Power State. (on by Default) + example: off + default: on + required: true + selector: + select: + options: + - on + - off + mode: list + + temperature: + name: Temperature + description: HVAC Target Temperature in Celsius (min 16 - Max 30). (26 by Default) + example: 26 + default: 26 + required: false + selector: + number: + min: 16 + max: 30 + step: 1 + unit_of_measurement: "°C" + mode: box + + mode: + name: Mode + description: HVAC Mode Selector 1 (auto), 2 (cool), 3 (dry), 4 (fan), 5 (heat). (1 by Default) + example: 1 + default: 1 + required: false + selector: + number: + min: 1 + max: 5 + step: 1 + mode: box + + fan_speed: + name: Fan Speed + description: HVAC Fan Speed Selector 1 (auto), 2 (low), 3 (medium), 4 (high). (1 by Default) + example: 1 + default: 1 + required: false + selector: + number: + min: 1 + max: 4 + step: 1 + mode: box + """ + deviceId = extract_device_id(device) + headers_dict = auth(**pyscript.app_config) + if temperature == None: + temperature = 26 + if mode == None: + mode = 1 + if fan_speed == None: + fan_speed = 1 + command_execute(headers_dict, deviceId, 'setAll', parameter=f"{temperature},{mode},{fan_speed},{state}") + +@service +def switchbot_ir_light_control(device=None, command=None, steps=None): + """yaml +name: SwitchBot IR Light Control +description: Control IR Light "saved" in "Switchbot Hubs" +fields: + device: + name: Device + description: Target device + example: switch.switchbot_remote_light + default: + required: true + selector: + entity: + domain: switch + command: + name: Command + description: Select a Command + example: turnOff + default: + required: true + selector: + select: + options: + - turnOn + - turnOff + - brightnessUp + - brightnessDown + mode: list + steps: + name: Steps + description: How many times to run the command (default 1), only works with brightnessUp/Down + example: turnOff + default: 1 + required: false + selector: + number: + min: 1 + max: 10 + mode: box + """ + if steps == None or command == "turnOn" or command== "turnOff": + steps=1 + for i in range(steps): + device_id = extract_device_id(device) + headers = auth(**pyscript.app_config) + command_execute(headers, device_id, command) + + +@service +def switchbot_curtain_command(device=None, command=None, index=None, mode=None, position=None): + """yaml +name: SwitchBot Curtain Command +description: Control Switchbot curtain. +fields: + device: + name: Device + description: Target device + example: cover.switchbot_remote_bedroom_curtains + default: + required: true + selector: + entity: + domain: cover + + command: + name: Command + description: Select a Command + example: turnOff + default: + required: true + selector: + select: + options: + - turnOn + - turnOff + - setPosition + mode: list + + index: + name: Indexs + description: waiting for switchbot team clarifications + example: 0 + default: 0 + required: false + selector: + number: + min: 0 + max: 1 + step: 1 + mode: box + + mode: + name: Modes + description: Select a Mode (required for "setPosition" command) + example: Performance + default: Default + required: false + selector: + select: + options: + - Performance + - Silent + - Default + mode: list + + position: + name: Position + description: Select a Mode (0%-100%) (required for "setPosition" command) + example: 50 + default: 50 + required: false + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider + """ + + deviceId = extract_device_id(device) + headers_dict = auth(**pyscript.app_config) + modes={"Performance": "0", "Silent": "1", "Default": "ff"} + if command == "turnOn" or command == "turnOff": + command_execute(headers_dict, deviceId, command, parameter=None) + else: + command_execute(headers_dict, deviceId, command, parameter=f"{index},{modes[mode]},{position}") + +@service +def switchbot_turn_on(device=None): + + """yaml +name: SwitchBot Turn Device ON +description: Turn Switchbot controlled device ON +fields: + device: + name: Device + description: Target device + example: switch.switchbot_remote_light + default: + required: true + selector: + entity: + domain: switch + """ + deviceId = extract_device_id(device) + headers_dict = auth(**pyscript.app_config) + command_execute(headers_dict, deviceId, "turnOn") + +@service +def switchbot_turn_off(device=None): + """yaml +name: SwitchBot Turn Device OFF +description: Turn Switchbot controlled device OFF +fields: + device: + name: Device + description: Target device + example: switch.switchbot_remote_light + default: + required: true + selector: + entity: + domain: switch + """ + deviceId = extract_device_id(device) + headers_dict=auth(**pyscript.app_config) + command_execute(headers_dict, deviceId, "turnOff") + + +@service +def switchbot_generic_command(device=None, command=None, parameter=None, commandType=None): + """yaml +name: SwitchBot Generic Command +description: Control Switchbot Device through custom command(refer to https://github.com/OpenWonderLabs/SwitchBotAPI) +fields: + device: + name: Device + description: Target device (get req. to api) + example: switch.switchbot_remote_light + default: + required: true + selector: + entity: + domain: switch + + command: + name: Command + description: the name of the command + example: turnOff + default: + required: true + selector: + text: + + parameter: + name: Parameters + description: some commands require parameters, such as SetChannel + example: + default: + required: false + selector: + text: + + commandType: + name: Command Type + description: for customized buttons, this needs to be set to customzie + example: command + default: command + required: true + selector: + select: + options: + - command + - customize + + """ + deviceId = extract_device_id(device) + headers_dict = auth(**pyscript.app_config) + command_execute(headers_dict, deviceId, command, parameter=parameter, custom=(commandType=='customize')) + +# Status checking +# Status requests got every 5 minutes (288 API calls / device / day). +#@time_trigger("period(0:00, 300 sec)") +def switchbot_get_status(): + states = state.names() + for s in states: + if (PREFIX in s): + if KEY_NON_IR_TYPE in state.getattr(s).keys(): + deviceId = extract_device_id(s) + headers_dict = auth(**pyscript.app_config) + data = get_status(headers_dict, deviceId) + if data != None: + state.set(s, value=deviceId, new_attributes=data['body']) From 8e98cf0c6905c797a56b252a7c53bb93c8ee8467 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Tue, 21 Feb 2023 22:10:10 +0100 Subject: [PATCH 07/31] rewrited: hvac, some desc. added only bot command --- pyscript/apps/switchbot.py | 150 +++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 46 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index 1c9d6ad..5e3e4b6 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -175,14 +175,14 @@ def switchbot_refresh_devices(): create_non_ir_entities(non_infrared) @service -def switchbot_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): +def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): """yaml name: SwitchBot IR HVAC Control -description: Control IR HVAC "saved" in "Switchbot Hubs" +description: Control IR HVAC connected to your SwitchBot account. fields: device: name: Device - description: HVAC Target device + description: Target device example: switch.switchbot_remote_hvac default: required: true @@ -192,7 +192,7 @@ def switchbot_hvac(device=None, temperature=None, mode=None, fan_speed=None, sta state: name: Power State - description: HVAC Power State. (on by Default) + description: Select a State ("on" by Default) example: off default: on required: true @@ -205,7 +205,7 @@ def switchbot_hvac(device=None, temperature=None, mode=None, fan_speed=None, sta temperature: name: Temperature - description: HVAC Target Temperature in Celsius (min 16 - Max 30). (26 by Default) + description: Select a target temperature (required for "on" state) (min 16 - Max 30) ("26" by Default) example: 26 default: 26 required: false @@ -219,45 +219,52 @@ def switchbot_hvac(device=None, temperature=None, mode=None, fan_speed=None, sta mode: name: Mode - description: HVAC Mode Selector 1 (auto), 2 (cool), 3 (dry), 4 (fan), 5 (heat). (1 by Default) - example: 1 - default: 1 + description: Select a mode (required for "on" state) ("Auto" by Default) + example: Auto + default: Auto required: false selector: - number: - min: 1 - max: 5 - step: 1 - mode: box + select: + options: + - Auto + - Cool + - Heat + - Dry + - Fan + mode: list fan_speed: name: Fan Speed - description: HVAC Fan Speed Selector 1 (auto), 2 (low), 3 (medium), 4 (high). (1 by Default) - example: 1 - default: 1 + description: Select a Fan Speed (required for "on" state) ("Auto" by Default) + example: Auto + default: Auto required: false selector: - number: - min: 1 - max: 4 - step: 1 - mode: box + select: + options: + - Auto + - Low + - Medium + - High + mode: list """ deviceId = extract_device_id(device) headers_dict = auth(**pyscript.app_config) - if temperature == None: + if temperature == None or state == "off": temperature = 26 - if mode == None: - mode = 1 - if fan_speed == None: - fan_speed = 1 - command_execute(headers_dict, deviceId, 'setAll', parameter=f"{temperature},{mode},{fan_speed},{state}") + if mode == None or state == "off": + mode = "Auto" + if fan_speed == None or state == "off": + fan_speed = "Auto" + modes={"Auto": "1","Cool": "2","Dry": "3","Fan": "4","Heat": "5"} + speeds={"Auto": "1","Low": "2","Medium": "3","High": "4"} + command_execute(headers_dict, deviceId, 'setAll', parameter=f"{temperature},{modes[mode]},{speeds[fan_speed]},{state}") @service -def switchbot_ir_light_control(device=None, command=None, steps=None): +def switchbot_ir_light(device=None, command=None, steps=None): """yaml name: SwitchBot IR Light Control -description: Control IR Light "saved" in "Switchbot Hubs" +description: Control IR Light connected to your SwitchBot account. fields: device: name: Device @@ -284,8 +291,8 @@ def switchbot_ir_light_control(device=None, command=None, steps=None): mode: list steps: name: Steps - description: How many times to run the command (default 1), only works with brightnessUp/Down - example: turnOff + description: Number of Steps (required for "brightnessUp" and "brightnessDown" command) ("1" by Default) + example: 1 default: 1 required: false selector: @@ -294,19 +301,19 @@ def switchbot_ir_light_control(device=None, command=None, steps=None): max: 10 mode: box """ + deviceId = extract_device_id(device) + headers = auth(**pyscript.app_config) if steps == None or command == "turnOn" or command== "turnOff": steps=1 for i in range(steps): - device_id = extract_device_id(device) - headers = auth(**pyscript.app_config) - command_execute(headers, device_id, command) + command_execute(headers, deviceId, command) @service -def switchbot_curtain_command(device=None, command=None, index=None, mode=None, position=None): +def switchbot_curtain(device=None, command=None, index=None, mode=None, position=None): """yaml name: SwitchBot Curtain Command -description: Control Switchbot curtain. +description: Control Switchbot Curtain connected to your SwitchBot account. fields: device: name: Device @@ -333,8 +340,8 @@ def switchbot_curtain_command(device=None, command=None, index=None, mode=None, mode: list index: - name: Indexs - description: waiting for switchbot team clarifications + name: Index [WIP] + description: waiting for switchbot team clarifications ("0" by Default) example: 0 default: 0 required: false @@ -346,8 +353,8 @@ def switchbot_curtain_command(device=None, command=None, index=None, mode=None, mode: box mode: - name: Modes - description: Select a Mode (required for "setPosition" command) + name: Mode + description: Select a Mode (required for "setPosition" command) ("Default" by Default) example: Performance default: Default required: false @@ -361,7 +368,7 @@ def switchbot_curtain_command(device=None, command=None, index=None, mode=None, position: name: Position - description: Select a Mode (0%-100%) (required for "setPosition" command) + description: Select a Mode (0%-100%) (required for "setPosition" command) ("50" by Default) example: 50 default: 50 required: false @@ -371,17 +378,68 @@ def switchbot_curtain_command(device=None, command=None, index=None, mode=None, max: 100 step: 1 unit_of_measurement: "%" - mode: slider + mode: box """ deviceId = extract_device_id(device) headers_dict = auth(**pyscript.app_config) modes={"Performance": "0", "Silent": "1", "Default": "ff"} + if position == None: + position = "50" + if index == None: + index = "0" if command == "turnOn" or command == "turnOff": command_execute(headers_dict, deviceId, command, parameter=None) else: command_execute(headers_dict, deviceId, command, parameter=f"{index},{modes[mode]},{position}") +@service +def switchbot_bot(device=None, command=None, repetition=None): + """yaml +name: SwitchBot Turn Device OFF +description: Turn Switchbot controlled device OFF +fields: + device: + name: Device + description: Target device + example: switch.switchbot_remote_bot + default: + required: true + selector: + entity: + domain: switch + command: + name: Command + description: Select a Command + example: turnOff + default: + required: true + selector: + select: + options: + - turnOn + - turnOff + - press + mode: list + repetition: + name: Repetition + description: Number of Repetition (required for "press" command) ("1" by Default) + example: 1 + default: 1 + required: false + selector: + number: + min: 1 + max: 10 + mode: box + """ + deviceId = extract_device_id(device) + headers_dict=auth(**pyscript.app_config) + if repetition == None or not (command == "press"): + repetition = 1 + for i in range(repetition): + command_execute(headers_dict, deviceId, command) + @service def switchbot_turn_on(device=None): @@ -432,7 +490,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command fields: device: name: Device - description: Target device (get req. to api) + description: Target device example: switch.switchbot_remote_light default: required: true @@ -442,7 +500,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command command: name: Command - description: the name of the command + description: The name of the command example: turnOff default: required: true @@ -451,7 +509,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command parameter: name: Parameters - description: some commands require parameters, such as SetChannel + description: Some commands require parameters, such as SetChannel example: default: required: false @@ -460,7 +518,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command commandType: name: Command Type - description: for customized buttons, this needs to be set to customzie + description: For customized buttons, this needs to be set to customzie example: command default: command required: true From 5ded4b761c09c9dca3fc7dfff065501fb4c317b8 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Tue, 21 Feb 2023 22:19:20 +0100 Subject: [PATCH 08/31] desc fix --- pyscript/apps/switchbot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index 5e3e4b6..ffb19ad 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -312,7 +312,7 @@ def switchbot_ir_light(device=None, command=None, steps=None): @service def switchbot_curtain(device=None, command=None, index=None, mode=None, position=None): """yaml -name: SwitchBot Curtain Command +name: SwitchBot Curtain Control description: Control Switchbot Curtain connected to your SwitchBot account. fields: device: @@ -396,8 +396,8 @@ def switchbot_curtain(device=None, command=None, index=None, mode=None, position @service def switchbot_bot(device=None, command=None, repetition=None): """yaml -name: SwitchBot Turn Device OFF -description: Turn Switchbot controlled device OFF +name: SwitchBot Bot Control +description: Control Switchbot Bot connected to your SwitchBot account. fields: device: name: Device From feea46e8b2769c887137650262cf0e9ce3f990eb Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Tue, 21 Feb 2023 23:44:08 +0100 Subject: [PATCH 09/31] fixed dummy entity generation --- README.md | 27 +++++++++++++----- pyscript/apps/switchbot.py | 56 ++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 407d052..a30ded1 100755 --- a/README.md +++ b/README.md @@ -108,13 +108,14 @@ It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devi - [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) - [SwitchBot Turn ON (`pyscript.switchbot_turn_on`)](#switchbot-turn-on) - [SwitchBot Turn OFF (`pyscript.switchbot_turn_off`)](#switchbot-turn-off) -- [SwitchBot Curtain Command (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) +- [SwitchBot Bot Control (`pyscript.switchbot_curtain_command`)](#switchbot-bot-control) +- [SwitchBot Curtain Control (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) - [SwitchBot IR HVAC Control (`pyscript.switchbot_hvac`)](#switchbot-ir-hvac-control) - [SwitchBot IR Light Control (`pyscript.switchbot_ir_light_control`)](#switchbot-ir-light-control) - [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) ### 🔸SwitchBot Refresh Devices -_Creates Home Assistant `switch` entity for each IR Device and Bot connected to your SwitchBot Hub,`cover` entity for each Curtain, `binary_sensor` entity for each Contact Sensor and `sensor` entity for each Meter connected to your Switchbot Hub. These devices are stored as `.switchbot_remote_` eg `cover.switchbot_remote_bedroom_curtains`_ +_Creates Home Assistant entity with the best type-domain association, otherwise with domain `switch`. These devices are stored as `.switchbot_remote_` eg: A SwitchBot Curtain™ will become `cover.switchbot_remote_bedroom_curtains`_ _The `` corresponds to the name of the device in the SwitchBot app._ _If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ @@ -144,6 +145,18 @@ Parameters: - `device` - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + +### 🔸SwitchBot Bot Control +_Interface for "Classic" Bot (turnOn, turnOff, press) devices._ + +**Parameters:** +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `command:` + - string value between `turnOn`, `turnOff`, `press` +- `repetition:` + - int value from `1` to `10`, _only works with `press`_, iterates the command as many times as selected. + ### 🔸Switchbot Curtain Control _Interface for Curtain (turnOn, turnOff, setPosition) devices._ - `device` @@ -151,7 +164,7 @@ _Interface for Curtain (turnOn, turnOff, setPosition) devices._ - `command:` - string value between `turnOn`, `turnOff`, `setPosition` - `index:` - - int value between `?????` (required for "setPosition" command, otherwise it will be ignored) + - [wip] int value between `?????` (required for "setPosition" command, otherwise it will be ignored) - `mode:` - string value between `Performance`, `Silent`, `Default` (required for "setPosition" command, otherwise it will be ignored) - `position:` @@ -163,14 +176,14 @@ _Interface for infrared HVAC (heating, ventilation and air conditioning) devices **Parameters:** - `device` - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `state:` + - string value between `on` and `off` - `temperature:` - int value from `16` to `30` - `mode:` - - int value between `1` (auto), `2` (cool), `3` (dry), `4` (fan), `5` (heat) + - string value between `Auto`, `Cool`, `Dry`, `Fan`, `Heat` - `fan_speed:` - - int value between `1` (auto), `2` (low), `3` (medium), `4` (high) -- `state:` - - string value between `on` and `off` + - int value between `Auto`, `Low`, `Medium`, `High` ### 🔸SwitchBot IR Light Control diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index ffb19ad..fcacf20 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -2,7 +2,6 @@ import re, yaml, io PREFIX="switchbot_remote_" -DOMAIN="switch" KEY_DEV_TYPE='remoteType' KEY_DEV_NAME='deviceName' KEY_DEV_ID='deviceId' @@ -30,20 +29,16 @@ def auth(token=None, secret=None, nonce=None): def gen_icon(dev): '''Generate icon based on device type. Default to a remote icon.''' ico = 'remote' - icons = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner'} - typ = dev.get(KEY_DEV_TYPE) + icons = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner', 'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer', 'MeterPlus': 'thermometer'} + typ = dev.get(KEY_DEV_TYPE, None) + if typ == None: + typ = dev.get(KEY_NON_IR_TYPE) + if typ in icons: ico = icons[typ] return 'mdi:'+ico -def gen_icon(non_ir): - '''Generate icon based on device type. Default to a robot icon.''' - ico = 'robot' - icons = {'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer'} - typ = non_ir.get(KEY_NON_IR_TYPE) - if typ in icons: - ico = icons[typ] - return 'mdi:'+ico + def clear_existing(): '''Clear switchbot devices which were saved to avoid zombies.''' @@ -56,31 +51,23 @@ def clear_existing(): def gen_dev_uid(dev:dict): '''Generate a Unique ID for Switchbot IR devices. (devices must have unique names in switchbot app so it works)''' name = dev.get(KEY_DEV_NAME) - if name is not None: - name = name.lower() - name = re.sub(r'[^0-9a-z]+', '_', name) - if name not in ['_', '']: - return PREFIX+name - return PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) - -def gen_non_ir_uid(non_ir:dict): - '''Generate a Unique ID for Switchbot non-IR devices. (devices must have unique names in switchbot app so it works)''' - name = non_ir.get(KEY_DEV_NAME) - domain = convert_switchbot_type_to_ha_domain(non_ir.get(KEY_NON_IR_TYPE)) + domain = type_to_domain(dev.get(KEY_DEV_TYPE)) + if domain == None: + domain = type_to_domain(dev.get(KEY_NON_IR_TYPE)) if name is not None: name = name.lower() name = re.sub(r'[^0-9a-z]+', '_', name) if name not in ['_', '']: return domain + '.' + PREFIX + name - return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(non_ir.get(KEY_NON_IR_TYPE)).lower())+'_'+str(non_ir.get(KEY_DEV_ID)[-4:]) - -def convert_switchbot_type_to_ha_domain(type): - if type == 'Curtain': - return 'cover' - elif type == 'Contact Sensor': - return 'binary_sensor' - elif type == 'Meter': - return 'sensor' + return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) + + +def type_to_domain(type): + TTD = {'Curtain': 'cover', 'Contact Sensor': 'binary_sensor', 'Meter':'sensor', 'MeterPlus':'sensor', "Air Conditioner": "climate", 'Light': 'ligth'} + if type in TTD: + return TTD[type] + elif type == None: + return None else: return 'switch' @@ -111,7 +98,7 @@ def create_ir_entities(devices): log.warning(f"Adding Switchbot Device {dev.get(KEY_DEV_NAME)} [{dev.get(KEY_DEV_TYPE)}] -> {dev.get(KEY_DEV_ID)}") dev['friendly_name'] = gen_dev_name(dev) dev['icon'] = gen_icon(dev) - state.set(f'{DOMAIN}.{gen_dev_uid(dev)}', value=dev.get(KEY_DEV_ID), new_attributes=dev ) + state.set(f'{gen_dev_uid(dev)}', value=dev.get(KEY_DEV_ID), new_attributes=dev) def create_non_ir_entities(devices): for non_ir in devices: @@ -119,7 +106,7 @@ def create_non_ir_entities(devices): log.warning(f"Adding Switchbot Device {non_ir.get(KEY_DEV_NAME)} [{non_ir.get(KEY_NON_IR_TYPE)}] -> {non_ir.get(KEY_DEV_ID)}") non_ir['friendly_name'] = gen_dev_name(non_ir) non_ir['icon'] = gen_icon(non_ir) - state.set(f'{gen_non_ir_uid(non_ir)}', value=non_ir.get(KEY_DEV_ID), new_attributes=non_ir) + state.set(f'{gen_dev_uid(non_ir)}', value=non_ir.get(KEY_DEV_ID), new_attributes=non_ir) switchbot_get_status() # Get an initial status for sensor-like devices. @@ -544,5 +531,8 @@ def switchbot_get_status(): deviceId = extract_device_id(s) headers_dict = auth(**pyscript.app_config) data = get_status(headers_dict, deviceId) + temp = state.getattr(s) + data['body']['friendly_name'] = temp['friendly_name'] + data['body']['icon'] = temp['icon'] if data != None: state.set(s, value=deviceId, new_attributes=data['body']) From 213160ada0db55d1b96d75aca82755d87d100d35 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Wed, 22 Feb 2023 00:16:16 +0100 Subject: [PATCH 10/31] fix index --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a30ded1..ceca9ba 100755 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ For more info click [here][switchbot-api-repo] - [Get Status](#switchbot-get-status) - [Turn On](#switchbot-turn-on) - [Turn Off](#switchbot-turn-off) + - [Bot](#switchbot-bot-control) - [Curtain](#switchbot-curtain-control) - [HVAC](#switchbot-ir-hvac-control) - [Light](#switchbot-ir-light-control) From fe36190da7e4fda09b5488cc8fe90abf0feee4d5 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:44:09 +0100 Subject: [PATCH 11/31] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ceca9ba..b106304 100755 --- a/README.md +++ b/README.md @@ -121,7 +121,8 @@ _Creates Home Assistant entity with the best type-domain association, otherwise _The `` corresponds to the name of the device in the SwitchBot app._ _If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ _The `` is an internal unique code._ -_The entities can then be used for sending commands or getting status using other functions of this pyscript. ⚠️ Not working stand alone ⚠️_ +***⚠️The entities can then be used for sending commands or getting status using other functions of this pyscript.⚠️***
+***⚠️ Not working stand alone ⚠️***
_If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ Parameters: ***None*** From 19e145d52906a0a74d0d25073eedf5e61bca3e8d Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Wed, 22 Feb 2023 21:50:51 +0100 Subject: [PATCH 12/31] corrected some desc.s --- pyscript/apps/switchbot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index fcacf20..69353d5 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -146,7 +146,7 @@ def get_status(headers, device_id): def switchbot_refresh_devices(): """yaml name: SwitchBot refresh devices -description: This service list registered devices in the "Switchbot Hubs". Devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. +description: This service lists registered devices in the "Switchbot Hub". Devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. fields: """ headers_dict=auth(**pyscript.app_config) @@ -410,7 +410,7 @@ def switchbot_bot(device=None, command=None, repetition=None): mode: list repetition: name: Repetition - description: Number of Repetition (required for "press" command) ("1" by Default) + description: Number of repetitions (required for "press" command) ("1" by Default) example: 1 default: 1 required: false @@ -505,7 +505,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command commandType: name: Command Type - description: For customized buttons, this needs to be set to customzie + description: For customized buttons, this needs to be set to customize example: command default: command required: true From 1b44f97bd7a13f24d6c51f8124b1ffed0b0c2fc9 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Thu, 23 Feb 2023 00:51:16 +0100 Subject: [PATCH 13/31] yaml fix --- pyscript/apps/switchbot.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index 69353d5..7235e8d 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -1,6 +1,8 @@ import time, hashlib, hmac, base64, requests import re, yaml, io +TTD = {'Curtain': 'cover', 'Contact Sensor': 'binary_sensor', 'Meter':'sensor', 'MeterPlus':'sensor', "Air Conditioner": "climate", 'Light': 'light', 'Bot': 'switch'} +ICONS = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner', 'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer', 'MeterPlus': 'thermometer'} PREFIX="switchbot_remote_" KEY_DEV_TYPE='remoteType' KEY_DEV_NAME='deviceName' @@ -29,7 +31,7 @@ def auth(token=None, secret=None, nonce=None): def gen_icon(dev): '''Generate icon based on device type. Default to a remote icon.''' ico = 'remote' - icons = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner', 'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer', 'MeterPlus': 'thermometer'} + icons = ICONS typ = dev.get(KEY_DEV_TYPE, None) if typ == None: typ = dev.get(KEY_NON_IR_TYPE) @@ -63,7 +65,6 @@ def gen_dev_uid(dev:dict): def type_to_domain(type): - TTD = {'Curtain': 'cover', 'Contact Sensor': 'binary_sensor', 'Meter':'sensor', 'MeterPlus':'sensor', "Air Conditioner": "climate", 'Light': 'ligth'} if type in TTD: return TTD[type] elif type == None: @@ -107,7 +108,6 @@ def create_non_ir_entities(devices): non_ir['friendly_name'] = gen_dev_name(non_ir) non_ir['icon'] = gen_icon(non_ir) state.set(f'{gen_dev_uid(non_ir)}', value=non_ir.get(KEY_DEV_ID), new_attributes=non_ir) - switchbot_get_status() # Get an initial status for sensor-like devices. @pyscript_executor @@ -133,7 +133,7 @@ def get_status(headers, device_id): data = r.json() status = data['statusCode'] if status == 100: - return data + return data['body'] elif status == "n/a": log.warning(f"Status request for {device_id} unauthorized. Http 401 Error. User permission is denied due to invalid token.") elif status == 190: @@ -160,6 +160,7 @@ def switchbot_refresh_devices(): clear_existing() create_ir_entities(infrared) create_non_ir_entities(non_infrared) + switchbot_get_status() # Get an initial status for sensor-like devices. @service def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): @@ -170,12 +171,12 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, device: name: Device description: Target device - example: switch.switchbot_remote_hvac + example: climate.switchbot_remote_hvac default: required: true selector: entity: - domain: switch + domain: climate state: name: Power State @@ -261,7 +262,7 @@ def switchbot_ir_light(device=None, command=None, steps=None): required: true selector: entity: - domain: switch + domain: light command: name: Command description: Select a Command @@ -442,7 +443,6 @@ def switchbot_turn_on(device=None): required: true selector: entity: - domain: switch """ deviceId = extract_device_id(device) headers_dict = auth(**pyscript.app_config) @@ -462,7 +462,6 @@ def switchbot_turn_off(device=None): required: true selector: entity: - domain: switch """ deviceId = extract_device_id(device) headers_dict=auth(**pyscript.app_config) @@ -483,7 +482,6 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command required: true selector: entity: - domain: switch command: name: Command @@ -532,7 +530,7 @@ def switchbot_get_status(): headers_dict = auth(**pyscript.app_config) data = get_status(headers_dict, deviceId) temp = state.getattr(s) - data['body']['friendly_name'] = temp['friendly_name'] - data['body']['icon'] = temp['icon'] + data['friendly_name'] = temp['friendly_name'] + data['icon'] = temp['icon'] if data != None: - state.set(s, value=deviceId, new_attributes=data['body']) + state.set(s, value=deviceId, new_attributes=data) From d48ce02847054fbd9c61e05c5b90c46c932395ae Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Thu, 23 Feb 2023 01:18:52 +0100 Subject: [PATCH 14/31] new get status services --- pyscript/apps/switchbot.py | 115 ++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 34 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index 7235e8d..b43e395 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -4,11 +4,11 @@ TTD = {'Curtain': 'cover', 'Contact Sensor': 'binary_sensor', 'Meter':'sensor', 'MeterPlus':'sensor', "Air Conditioner": "climate", 'Light': 'light', 'Bot': 'switch'} ICONS = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner', 'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer', 'MeterPlus': 'thermometer'} PREFIX="switchbot_remote_" -KEY_DEV_TYPE='remoteType' KEY_DEV_NAME='deviceName' KEY_DEV_ID='deviceId' -KEY_NON_IR_TYPE='deviceType' -KEY_NON_IR_CLOUD='enableCloudService' +KEY_DEV_IR_TYPE='remoteType' +KEY_DEV_TYPE='deviceType' +KEY_CLOUD='enableCloudService' # "input" from config def auth(token=None, secret=None, nonce=None): @@ -32,16 +32,14 @@ def gen_icon(dev): '''Generate icon based on device type. Default to a remote icon.''' ico = 'remote' icons = ICONS - typ = dev.get(KEY_DEV_TYPE, None) + typ = dev.get(KEY_DEV_IR_TYPE, None) if typ == None: - typ = dev.get(KEY_NON_IR_TYPE) + typ = dev.get(KEY_DEV_TYPE) if typ in icons: ico = icons[typ] return 'mdi:'+ico - - def clear_existing(): '''Clear switchbot devices which were saved to avoid zombies.''' states = state.names() @@ -53,16 +51,15 @@ def clear_existing(): def gen_dev_uid(dev:dict): '''Generate a Unique ID for Switchbot IR devices. (devices must have unique names in switchbot app so it works)''' name = dev.get(KEY_DEV_NAME) - domain = type_to_domain(dev.get(KEY_DEV_TYPE)) + domain = type_to_domain(dev.get(KEY_DEV_IR_TYPE)) if domain == None: - domain = type_to_domain(dev.get(KEY_NON_IR_TYPE)) + domain = type_to_domain(dev.get(KEY_DEV_TYPE)) if name is not None: name = name.lower() name = re.sub(r'[^0-9a-z]+', '_', name) if name not in ['_', '']: return domain + '.' + PREFIX + name - return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) - + return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_IR_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) def type_to_domain(type): if type in TTD: @@ -76,7 +73,7 @@ def gen_dev_name(dev): name = ['Switchbot'] if (dev.get(KEY_DEV_NAME) is not None): name.append(dev.get(KEY_DEV_NAME)) - #name.append(f"[IR {dev.get(KEY_DEV_TYPE)}]") + #name.append(f"[IR {dev.get(KEY_DEV_IR_TYPE)}]") return ' '.join(name) def extract_device_id(device, _recursived=0): @@ -96,15 +93,15 @@ def extract_device_id(device, _recursived=0): def create_ir_entities(devices): for dev in devices: - log.warning(f"Adding Switchbot Device {dev.get(KEY_DEV_NAME)} [{dev.get(KEY_DEV_TYPE)}] -> {dev.get(KEY_DEV_ID)}") + log.warning(f"Adding Switchbot Device {dev.get(KEY_DEV_NAME)} [{dev.get(KEY_DEV_IR_TYPE)}] -> {dev.get(KEY_DEV_ID)}") dev['friendly_name'] = gen_dev_name(dev) dev['icon'] = gen_icon(dev) state.set(f'{gen_dev_uid(dev)}', value=dev.get(KEY_DEV_ID), new_attributes=dev) def create_non_ir_entities(devices): for non_ir in devices: - if non_ir.get(KEY_NON_IR_CLOUD): # Only load devices that are cloud-connected. - log.warning(f"Adding Switchbot Device {non_ir.get(KEY_DEV_NAME)} [{non_ir.get(KEY_NON_IR_TYPE)}] -> {non_ir.get(KEY_DEV_ID)}") + if non_ir.get(KEY_CLOUD): # Only load devices that are cloud-connected. + log.warning(f"Adding Switchbot Device {non_ir.get(KEY_DEV_NAME)} [{non_ir.get(KEY_DEV_TYPE)}] -> {non_ir.get(KEY_DEV_ID)}") non_ir['friendly_name'] = gen_dev_name(non_ir) non_ir['icon'] = gen_icon(non_ir) state.set(f'{gen_dev_uid(non_ir)}', value=non_ir.get(KEY_DEV_ID), new_attributes=non_ir) @@ -117,6 +114,7 @@ def requestHelper(_url,_json,_headers): @pyscript_executor def requestGetHelper(_url,_json,_headers): return requests.get(_url,json = _json, headers=_headers) + def command_execute(headers, device_id, command, parameter=None, custom=False): url=f"https://api.switch-bot.com/v1.1/devices/{device_id}/commands" @@ -126,6 +124,7 @@ def command_execute(headers, device_id, command, parameter=None, custom=False): if custom: data["commandType"] = 'customize' requestHelper(url, data, headers) + def get_status(headers, device_id): url=f"https://api.switch-bot.com/v1.1/devices/{device_id}/status" @@ -140,6 +139,42 @@ def get_status(headers, device_id): log.warning(f"Status request for {device_id}. System error. Device internal error due to device states not synchronized with server.") return None + + + + +# Status checking +# Status requests got every 5 minutes (288 API calls / device / day). +#@time_trigger("period(0:00, 300 sec)") +def h_switchbot_get_status(devices=None): + if devices == None: + states = state.names() + for s in states: + if (PREFIX in s): + if KEY_DEV_TYPE in state.getattr(s).keys(): + deviceId = extract_device_id(s) + headers_dict = auth(**pyscript.app_config) + data = get_status(headers_dict, deviceId) + temp = state.getattr(s) + data['friendly_name'] = temp['friendly_name'] + data['icon'] = temp['icon'] + if data != None: + state.set(s, value=deviceId, new_attributes=data) + else: + for s in devices: + if (PREFIX in s): + if KEY_DEV_TYPE in state.getattr(s).keys(): + deviceId = extract_device_id(s) + headers_dict = auth(**pyscript.app_config) + data = get_status(headers_dict, deviceId) + temp = state.getattr(s) + data['friendly_name'] = temp['friendly_name'] + data['icon'] = temp['icon'] + if data != None: + state.set(s, value=deviceId, new_attributes=data) + + + #services @service @time_trigger("startup") @@ -160,7 +195,36 @@ def switchbot_refresh_devices(): clear_existing() create_ir_entities(infrared) create_non_ir_entities(non_infrared) - switchbot_get_status() # Get an initial status for sensor-like devices. + h_switchbot_get_status() # Get an initial status for sensor-like devices. + + +@service +def switchbot_get_status(): + """yaml +name: SwitchBot get status +description: This service sets as attributes all values related to all devices, eg. a meter will have temperature and humidity values. +fields: + """ + h_switchbot_get_status() # Get an initial status for sensor-like devices. + +@service +def switchbot_get_single_status(devices=None): + + """yaml +name: SwitchBot Turn Device ON +description: Turn Switchbot controlled device ON +fields: + devices: + name: Devices + description: Target device + example: switch.switchbot_remote_light + default: + required: true + selector: + entity: + multiple: true + """ + h_switchbot_get_status(devices) # Get an initial status for sensor-like devices. @service def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): @@ -516,21 +580,4 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command """ deviceId = extract_device_id(device) headers_dict = auth(**pyscript.app_config) - command_execute(headers_dict, deviceId, command, parameter=parameter, custom=(commandType=='customize')) - -# Status checking -# Status requests got every 5 minutes (288 API calls / device / day). -#@time_trigger("period(0:00, 300 sec)") -def switchbot_get_status(): - states = state.names() - for s in states: - if (PREFIX in s): - if KEY_NON_IR_TYPE in state.getattr(s).keys(): - deviceId = extract_device_id(s) - headers_dict = auth(**pyscript.app_config) - data = get_status(headers_dict, deviceId) - temp = state.getattr(s) - data['friendly_name'] = temp['friendly_name'] - data['icon'] = temp['icon'] - if data != None: - state.set(s, value=deviceId, new_attributes=data) + command_execute(headers_dict, deviceId, command, parameter=parameter, custom=(commandType=='customize')) \ No newline at end of file From 7b40093e5e61da5b530bc437220e208adb7c9b81 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:48:49 +0100 Subject: [PATCH 15/31] corrected descs, changed some functions logics --- pyscript/apps/switchbot.py | 75 ++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index b43e395..ac2a793 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -11,11 +11,11 @@ KEY_CLOUD='enableCloudService' # "input" from config -def auth(token=None, secret=None, nonce=None): - - token=str(token) - secret=str(secret) - nonce=str(nonce) +def auth(conf_dict=None): + + token=str(conf_dict['token']) + secret=str(conf_dict['secret']) + nonce=str(conf_dict['nonce']) t = int(round(time.time() * 1000)) string_to_sign = '{}{}{}'.format(token, t, nonce) @@ -153,7 +153,7 @@ def h_switchbot_get_status(devices=None): if (PREFIX in s): if KEY_DEV_TYPE in state.getattr(s).keys(): deviceId = extract_device_id(s) - headers_dict = auth(**pyscript.app_config) + headers_dict = auth(pyscript.app_config) data = get_status(headers_dict, deviceId) temp = state.getattr(s) data['friendly_name'] = temp['friendly_name'] @@ -165,7 +165,7 @@ def h_switchbot_get_status(devices=None): if (PREFIX in s): if KEY_DEV_TYPE in state.getattr(s).keys(): deviceId = extract_device_id(s) - headers_dict = auth(**pyscript.app_config) + headers_dict = auth(pyscript.app_config) data = get_status(headers_dict, deviceId) temp = state.getattr(s) data['friendly_name'] = temp['friendly_name'] @@ -180,11 +180,11 @@ def h_switchbot_get_status(devices=None): @time_trigger("startup") def switchbot_refresh_devices(): """yaml -name: SwitchBot refresh devices -description: This service lists registered devices in the "Switchbot Hub". Devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. -fields: - """ - headers_dict=auth(**pyscript.app_config) +name: SwitchBot Refresh Devices +description: This service lists the devices registered in all the "Switchbot Hub" connected to your account. The devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. (this service consume 1 API call *executions) +fields: + """ + headers_dict=auth(pyscript.app_config) url=f"https://api.switch-bot.com/v1.1/devices" r = requestGetHelper(url, {}, headers_dict) log.info(str(r.json())) @@ -201,18 +201,17 @@ def switchbot_refresh_devices(): @service def switchbot_get_status(): """yaml -name: SwitchBot get status -description: This service sets as attributes all values related to all devices, eg. a meter will have temperature and humidity values. +name: SwitchBot Get Status +description: This service updates the attributes of all (compatible) devices, e.g., an entity associated with a meter will receive temperature and humidity values, etc. (this service consume 1 API call *number_of_devices*execution) fields: - """ + """ h_switchbot_get_status() # Get an initial status for sensor-like devices. @service def switchbot_get_single_status(devices=None): - """yaml -name: SwitchBot Turn Device ON -description: Turn Switchbot controlled device ON +name: SwitchBot Get Specific Status +description: This service updates the attributes of selected devices, e.g. an entity associated with a meter will receive temperature and humidity values, etc. It's advisable to use this service instead of "switchbot_get_status" if you need to update only a few devices, in order make less API calls (this service consume 1 API call *number_of_devices*execution) fields: devices: name: Devices @@ -230,7 +229,7 @@ def switchbot_get_single_status(devices=None): def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): """yaml name: SwitchBot IR HVAC Control -description: Control IR HVAC connected to your SwitchBot account. +description: Control IR HVAC connected to your SwitchBot account. (this service consume 1 API call *execution) fields: device: name: Device @@ -272,8 +271,8 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, mode: name: Mode description: Select a mode (required for "on" state) ("Auto" by Default) - example: Auto - default: Auto + example: Heat + default: Cool required: false selector: select: @@ -299,13 +298,13 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, - Medium - High mode: list - """ + """ deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) + headers_dict = auth(pyscript.app_config) if temperature == None or state == "off": temperature = 26 if mode == None or state == "off": - mode = "Auto" + mode = "Cool" if fan_speed == None or state == "off": fan_speed = "Auto" modes={"Auto": "1","Cool": "2","Dry": "3","Fan": "4","Heat": "5"} @@ -316,7 +315,7 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, def switchbot_ir_light(device=None, command=None, steps=None): """yaml name: SwitchBot IR Light Control -description: Control IR Light connected to your SwitchBot account. +description: Control IR Light connected to your SwitchBot account. (this service consume 1 API call *steps*execution or 1 *executon(turnON or turnOFF)) fields: device: name: Device @@ -354,7 +353,7 @@ def switchbot_ir_light(device=None, command=None, steps=None): mode: box """ deviceId = extract_device_id(device) - headers = auth(**pyscript.app_config) + headers = auth(pyscript.app_config) if steps == None or command == "turnOn" or command== "turnOff": steps=1 for i in range(steps): @@ -365,7 +364,7 @@ def switchbot_ir_light(device=None, command=None, steps=None): def switchbot_curtain(device=None, command=None, index=None, mode=None, position=None): """yaml name: SwitchBot Curtain Control -description: Control Switchbot Curtain connected to your SwitchBot account. +description: Control Switchbot Curtain connected to your SwitchBot account. (this service consume 1 API call *execution) fields: device: name: Device @@ -434,7 +433,7 @@ def switchbot_curtain(device=None, command=None, index=None, mode=None, position """ deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) + headers_dict = auth(pyscript.app_config) modes={"Performance": "0", "Silent": "1", "Default": "ff"} if position == None: position = "50" @@ -449,7 +448,7 @@ def switchbot_curtain(device=None, command=None, index=None, mode=None, position def switchbot_bot(device=None, command=None, repetition=None): """yaml name: SwitchBot Bot Control -description: Control Switchbot Bot connected to your SwitchBot account. +description: Control Switchbot Bot connected to your SwitchBot account. (this service consume 1 API call *repetion*execution or 1 *executon(turnON or turnOFF)) fields: device: name: Device @@ -486,7 +485,7 @@ def switchbot_bot(device=None, command=None, repetition=None): mode: box """ deviceId = extract_device_id(device) - headers_dict=auth(**pyscript.app_config) + headers_dict=auth(pyscript.app_config) if repetition == None or not (command == "press"): repetition = 1 for i in range(repetition): @@ -494,10 +493,9 @@ def switchbot_bot(device=None, command=None, repetition=None): @service def switchbot_turn_on(device=None): - """yaml name: SwitchBot Turn Device ON -description: Turn Switchbot controlled device ON +description: Turn Switchbot controlled device ON (this service consume 1 API call *execution) fields: device: name: Device @@ -509,14 +507,14 @@ def switchbot_turn_on(device=None): entity: """ deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) + headers_dict = auth(pyscript.app_config) command_execute(headers_dict, deviceId, "turnOn") @service def switchbot_turn_off(device=None): """yaml name: SwitchBot Turn Device OFF -description: Turn Switchbot controlled device OFF +description: Turn Switchbot controlled device OFF (this service consume 1 API call *execution) fields: device: name: Device @@ -528,7 +526,7 @@ def switchbot_turn_off(device=None): entity: """ deviceId = extract_device_id(device) - headers_dict=auth(**pyscript.app_config) + headers_dict=auth(pyscript.app_config) command_execute(headers_dict, deviceId, "turnOff") @@ -536,7 +534,7 @@ def switchbot_turn_off(device=None): def switchbot_generic_command(device=None, command=None, parameter=None, commandType=None): """yaml name: SwitchBot Generic Command -description: Control Switchbot Device through custom command(refer to https://github.com/OpenWonderLabs/SwitchBotAPI) +description: Control Switchbot Device through custom command() (this service consume 1 API call *execution) fields: device: name: Device @@ -576,8 +574,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command options: - command - customize - - """ + """ deviceId = extract_device_id(device) - headers_dict = auth(**pyscript.app_config) + headers_dict = auth(pyscript.app_config) command_execute(headers_dict, deviceId, command, parameter=parameter, custom=(commandType=='customize')) \ No newline at end of file From c85066292ae629c7af987e603e5c0ac061749628 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Fri, 24 Feb 2023 23:13:19 +0100 Subject: [PATCH 16/31] desc updated --- pyscript/apps/switchbot.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index ac2a793..c65af9e 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -181,7 +181,7 @@ def h_switchbot_get_status(devices=None): def switchbot_refresh_devices(): """yaml name: SwitchBot Refresh Devices -description: This service lists the devices registered in all the "Switchbot Hub" connected to your account. The devices are saved as "switch.switchbot_remote_" or similar in Home Assistant. (this service consume 1 API call *executions) +description: "This service lists the devices registered in all the Switchbot Hub™ connected to your account. The devices are saved as 'switch.switchbot_remote_' or similar in Home Assistant. (API call consumed: 1*execution)" fields: """ headers_dict=auth(pyscript.app_config) @@ -202,7 +202,7 @@ def switchbot_refresh_devices(): def switchbot_get_status(): """yaml name: SwitchBot Get Status -description: This service updates the attributes of all (compatible) devices, e.g., an entity associated with a meter will receive temperature and humidity values, etc. (this service consume 1 API call *number_of_devices*execution) +description: "This service updates the attributes of all devices. (API call consumed: 1*number_of_devices*execution)" fields: """ h_switchbot_get_status() # Get an initial status for sensor-like devices. @@ -211,11 +211,11 @@ def switchbot_get_status(): def switchbot_get_single_status(devices=None): """yaml name: SwitchBot Get Specific Status -description: This service updates the attributes of selected devices, e.g. an entity associated with a meter will receive temperature and humidity values, etc. It's advisable to use this service instead of "switchbot_get_status" if you need to update only a few devices, in order make less API calls (this service consume 1 API call *number_of_devices*execution) +description: "This service updates the attributes of selected devices. It's advisable use this service instead of 'switchbot_get_status' if you need to update only a few devices. (API call consumed: 1*number_of_devices*execution)" fields: devices: name: Devices - description: Target device + description: Target devices example: switch.switchbot_remote_light default: required: true @@ -229,7 +229,7 @@ def switchbot_get_single_status(devices=None): def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, state=None): """yaml name: SwitchBot IR HVAC Control -description: Control IR HVAC connected to your SwitchBot account. (this service consume 1 API call *execution) +description: "Control IR HVAC connected to your SwitchBot account. (API call consumed: 1*execution)" fields: device: name: Device @@ -270,7 +270,7 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, mode: name: Mode - description: Select a mode (required for "on" state) ("Auto" by Default) + description: Select a mode (required for "on" state) ("Cool" by Default) example: Heat default: Cool required: false @@ -315,7 +315,7 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, def switchbot_ir_light(device=None, command=None, steps=None): """yaml name: SwitchBot IR Light Control -description: Control IR Light connected to your SwitchBot account. (this service consume 1 API call *steps*execution or 1 *executon(turnON or turnOFF)) +description: "Control IR Light connected to your SwitchBot account. (API call consumed: 1*steps*execution)" fields: device: name: Device @@ -342,7 +342,7 @@ def switchbot_ir_light(device=None, command=None, steps=None): mode: list steps: name: Steps - description: Number of Steps (required for "brightnessUp" and "brightnessDown" command) ("1" by Default) + description: Number of Steps ("brightnessUp"/"brightnessDown" command only) ("1" by Default) example: 1 default: 1 required: false @@ -364,7 +364,7 @@ def switchbot_ir_light(device=None, command=None, steps=None): def switchbot_curtain(device=None, command=None, index=None, mode=None, position=None): """yaml name: SwitchBot Curtain Control -description: Control Switchbot Curtain connected to your SwitchBot account. (this service consume 1 API call *execution) +description: "Control Switchbot Curtain™ connected to your SwitchBot account. (API call consumed: 1*execution)" fields: device: name: Device @@ -391,8 +391,8 @@ def switchbot_curtain(device=None, command=None, index=None, mode=None, position mode: list index: - name: Index [WIP] - description: waiting for switchbot team clarifications ("0" by Default) + name: Index + description: Index ("0" by Default) example: 0 default: 0 required: false @@ -448,7 +448,7 @@ def switchbot_curtain(device=None, command=None, index=None, mode=None, position def switchbot_bot(device=None, command=None, repetition=None): """yaml name: SwitchBot Bot Control -description: Control Switchbot Bot connected to your SwitchBot account. (this service consume 1 API call *repetion*execution or 1 *executon(turnON or turnOFF)) +description: "Control Switchbot Bot™ connected to your SwitchBot account. (API call consumed: 1*repetions*execution)" fields: device: name: Device @@ -474,7 +474,7 @@ def switchbot_bot(device=None, command=None, repetition=None): mode: list repetition: name: Repetition - description: Number of repetitions (required for "press" command) ("1" by Default) + description: Number of repetitions ("press" command only) ("1" by Default) example: 1 default: 1 required: false @@ -495,7 +495,7 @@ def switchbot_bot(device=None, command=None, repetition=None): def switchbot_turn_on(device=None): """yaml name: SwitchBot Turn Device ON -description: Turn Switchbot controlled device ON (this service consume 1 API call *execution) +description: "Turn ON Switchbot a controlled device, behavior varies by device (API call consumed: 1*execution)" fields: device: name: Device @@ -514,7 +514,7 @@ def switchbot_turn_on(device=None): def switchbot_turn_off(device=None): """yaml name: SwitchBot Turn Device OFF -description: Turn Switchbot controlled device OFF (this service consume 1 API call *execution) +description: "Turn ON Switchbot a controlled device, behavior varies by device (API call consumed: 1*execution)" fields: device: name: Device @@ -534,7 +534,7 @@ def switchbot_turn_off(device=None): def switchbot_generic_command(device=None, command=None, parameter=None, commandType=None): """yaml name: SwitchBot Generic Command -description: Control Switchbot Device through custom command() (this service consume 1 API call *execution) +description: "Control Switchbot Device through custom command (refer to https://github.com/OpenWonderLabs/SwitchBotAPI) (API call consumed: 1*execution)" fields: device: name: Device @@ -556,7 +556,7 @@ def switchbot_generic_command(device=None, command=None, parameter=None, command parameter: name: Parameters - description: Some commands require parameters, such as SetChannel + description: Some commands require parameters, such as "SetChannel" example: default: required: false From 460b911b544745191a4ece8e2d972d274ad8ed1a Mon Sep 17 00:00:00 2001 From: SiriosDev Date: Sun, 26 Feb 2023 19:35:23 +0100 Subject: [PATCH 17/31] code optimization --- pyscript/apps/switchbot.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index c65af9e..deca527 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -1,5 +1,11 @@ -import time, hashlib, hmac, base64, requests -import re, yaml, io +from time import time +from hashlib import sha256 +from hmac import new +from base64 import b64encode +from requests import post, get +from re import sub + +import yaml, io TTD = {'Curtain': 'cover', 'Contact Sensor': 'binary_sensor', 'Meter':'sensor', 'MeterPlus':'sensor', "Air Conditioner": "climate", 'Light': 'light', 'Bot': 'switch'} ICONS = {'Projector': 'projector', 'Light': 'lightbulb-on', 'TV':'television', 'Fan':'fan', 'Air Conditioner': 'air-conditioner', 'Curtain': 'curtains', 'Contact Sensor': 'leak', 'Meter': 'thermometer', 'MeterPlus': 'thermometer'} @@ -17,13 +23,13 @@ def auth(conf_dict=None): secret=str(conf_dict['secret']) nonce=str(conf_dict['nonce']) - t = int(round(time.time() * 1000)) + t = int(round(time() * 1000)) string_to_sign = '{}{}{}'.format(token, t, nonce) string_to_sign = bytes(string_to_sign, 'utf-8') secret = bytes(secret, 'utf-8') - sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest()) + sign = b64encode(new(secret, msg=string_to_sign, digestmod=sha256).digest()) h={"Authorization": (str(token)), "t": (str(t)), "sign": (str(sign, 'utf-8')), "nonce": (str(nonce)), "Content-Type": "application/json; charset=utf8"} return h @@ -56,10 +62,10 @@ def gen_dev_uid(dev:dict): domain = type_to_domain(dev.get(KEY_DEV_TYPE)) if name is not None: name = name.lower() - name = re.sub(r'[^0-9a-z]+', '_', name) + name = sub(r'[^0-9a-z]+', '_', name) if name not in ['_', '']: return domain + '.' + PREFIX + name - return domain + '.' + PREFIX + re.sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_IR_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) + return domain + '.' + PREFIX + sub(r'[^0-9a-z]+', '_', str(dev.get(KEY_DEV_IR_TYPE)).lower())+'_'+str(dev.get(KEY_DEV_ID)[-4:]) def type_to_domain(type): if type in TTD: @@ -109,11 +115,11 @@ def create_non_ir_entities(devices): @pyscript_executor def requestHelper(_url,_json,_headers): - x=requests.post(_url,json = _json, headers=_headers) + x=post(_url,json = _json, headers=_headers) @pyscript_executor def requestGetHelper(_url,_json,_headers): - return requests.get(_url,json = _json, headers=_headers) + return get(_url,json = _json, headers=_headers) def command_execute(headers, device_id, command, parameter=None, custom=False): From d38ed1c4ba9e90c902c3d0ab0ca510ae1ab5ecb1 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Fri, 3 Mar 2023 20:42:21 +0100 Subject: [PATCH 18/31] new device warning + reformatt --- README.md | 199 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 114 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index b106304..4bf5724 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -[![License][licensing-shield]](LICENSE) -All rights to the [API][switchbot-api-repo] belong to [OpenWonderLabs][OpenWonderLabs-lnk]. +[![License][licensing-shield]](LICENSE)
+All rights to the [API][switchbot-api-repo] belong to [OpenWonderLabs][openwonderlabs-lnk]. # SwitchBot API Script Caller (v 0.2.1) +> **Warning**
+> At the moment not all API-compatible models are implemented, if you have the capability do a [fork][fork] and implement it (and request a [PR][pr]), otherwise open an [issue][issues] with "TEST|**_Model_Name_**" in the title + This (Py)Script allows you to control all (WIP) your SwitchBot devices via API calls (1.1). ‼️‼️At the moment we have implemented all the devices that we can test, so if you want to be a tester please open an [issue][issues] with "test" in the title or if you are also able to develop go ahead and do a [PR][pr]‼️‼️ @@ -12,99 +15,109 @@ For more info click [here][switchbot-api-repo] - [Requirements](#requirements) - [Installation](#installation) - [How To Use](#how-to-use) - - [Refresh Devices](#switchbot-refresh-devices) - - [Get Status](#switchbot-get-status) - - [Turn On](#switchbot-turn-on) - - [Turn Off](#switchbot-turn-off) - - [Bot](#switchbot-bot-control) - - [Curtain](#switchbot-curtain-control) - - [HVAC](#switchbot-ir-hvac-control) - - [Light](#switchbot-ir-light-control) - - [Generic Command](#switchbot-generic-command) + - [Refresh Devices](#switchbot-refresh-devices) + - [Get Status](#switchbot-get-status) + - [Turn On](#switchbot-turn-on) + - [Turn Off](#switchbot-turn-off) + - [Bot](#switchbot-bot-control) + - [Curtain](#switchbot-curtain-control) + - [HVAC](#switchbot-ir-hvac-control) + - [Light](#switchbot-ir-light-control) + - [Generic Command](#switchbot-generic-command) - [Work in Progress](#work-in-progress) - [Changelog](#changelog) ## Requirements - HACS ([docs][hacs-docs]) - - PyScript Integration ([docs][pyscript-docs]) - + - PyScript Integration ([docs][pyscript-docs]) + ## Installation + ⚠️ **If you update from one of the following versions there have been breaking changes:** +
Versions - [Unnumbered (2023.01.14)][unnum] -
- +
### Procedure + 1. **Clone this repository in your config folder** ```sh cd /config git clone https://github.com/SiriosDev/SwitchBot-API-Script-Caller.git ``` 2. **Check if you have a `pyscript/config.yaml` file. If not, create one and then add the following in your main top-level configuration.yaml file.** - ``` yaml - pyscript: !include pyscript/config.yaml - ``` + ```yaml + pyscript: !include pyscript/config.yaml + ``` 3. **Include [`pyscript/switchbot.yaml`](./pyscript/switchbot.yaml) in your `pyscript/config.yaml` under the `switchbot` section** ```yaml # /config/pyscript/config.yaml allow_all_imports: true apps: - # (...) - # ↓↓↓ attention indentation - switchbot: !include /config/SwitchBot-API-Script-Caller/pyscript/switchbot.yaml + # (...) + # ↓↓↓ attention indentation + switchbot: !include /config/SwitchBot-API-Script-Caller/pyscript/switchbot.yaml # (...) ``` 4. **Set the authentication secrets in `secrets.yaml` homeassistant file** - - Random Value (`switchbot_nonc`) (I suggest using an UUID generator, but any unique alphanumeric string is fine) - ```yaml - # secrets.yaml - # (...) - # Token and Secret Key : from `Developer Option` in the SwitchBot App (version ≥6.14) - switchbot_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - switchbot_sec: xxxxxxxxxxxx - # Random Value: you can use a UUID generator, but any unique alphanumeric string is OK - switchbot_nonc: xxxxxxxxxx - ``` + - Random Value (`switchbot_nonc`) (I suggest using an UUID generator, but any unique alphanumeric string is fine) + ```yaml + # secrets.yaml + # (...) + # Token and Secret Key : from `Developer Option` in the SwitchBot App (version ≥6.14) + switchbot_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + switchbot_sec: xxxxxxxxxxxx + # Random Value: you can use a UUID generator, but any unique alphanumeric string is OK + switchbot_nonc: xxxxxxxxxx + ``` 5. **Link the files in the `pyscript` directory** + ```sh # use `mkdir -p /config/pyscript/apps/` if the directory doesn't exist cd /config/pyscript/apps/ - + # Create a symbolic link to the apps directory named switchbot ln -s /config/SwitchBot-API-Script-Caller/pyscript/apps/switchbot.py switchbot.py ``` - + ### Further Update + By following this procedure, the script can then be updated with newer version using git. + ```sh cd SwitchBot-API-Script-Caller git pull ``` + ⚠️ **See changelog before updating.** The project is still under development, and breaking changes may **frequently** occur. ### Installation Notes + - In order to see the `Developer options` in the Switchbot app (version ≥6.14), click repetively on the version number in the App's settings.
Click here for detailed procedure - + ![SwitchBot](https://user-images.githubusercontent.com/26876994/210898538-5d07a304-3446-48e0-b020-69140ba89b45.png) - +
-- A symbolic link is symbolic and represent the exact path you enter, if you move the targeted file or if the target is outside of the container (e.g. when using docker) the link will not work. Make sure that you are using a relative path that is accessible for the host reading the link. + +- A symbolic link is symbolic and represent the exact path you enter, if you move the targeted file or if the target is outside of the container (e.g. when using docker) the link will not work. Make sure that you are using a relative path that is accessible for the host reading the link. - Ensure that `pyscript` is operational before to install this script. - Except dirs strictly related to pyscript, all others dir are recommended, so organize them as you like, keeping in mind that changing the contents of the "`clone`", could cause the update via `git pull` to fail. ## How To Use + This script (for now) provides the following services in home assisant. It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devices) first in order to be able to use the other features, as it will generate the required Home Assistant entities for your devices. ### Summary + - [SwitchBot Refresh Devices (`pyscript.switchbot_refresh_devices`)](#switchbot-refresh-devices) - [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) - [SwitchBot Turn ON (`pyscript.switchbot_turn_on`)](#switchbot-turn-on) @@ -116,112 +129,127 @@ It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devi - [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) ### 🔸SwitchBot Refresh Devices -_Creates Home Assistant entity with the best type-domain association, otherwise with domain `switch`. These devices are stored as `.switchbot_remote_` eg: A SwitchBot Curtain™ will become `cover.switchbot_remote_bedroom_curtains`_ + +_Creates Home Assistant entity with the best type-domain association, otherwise with domain `switch`. These devices are stored as `.switchbot_remote_`eg: A SwitchBot Curtain™ will become`cover.switchbot_remote_bedroom_curtains`* _The `` corresponds to the name of the device in the SwitchBot app._ -_If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_` (e.g. `switch.switchbot_remote_light_0D62`)_ +_If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_`(e.g.`switch.switchbot*remote_light_0D62`)* _The `` is an internal unique code._ -***⚠️The entities can then be used for sending commands or getting status using other functions of this pyscript.⚠️***
-***⚠️ Not working stand alone ⚠️***
+**_⚠️The entities can then be used for sending commands or getting status using other functions of this pyscript.⚠️_**
+**_⚠️ Not working stand alone ⚠️_**
_If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ -Parameters: ***None*** - +Parameters: **_None_** + ### 🔸Switchbot Get Status + _Gets the state of Switchbot Bots, Contact Sensors, Curtains and Meters._ -Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API]([url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)) for the data returned from a status call. +Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API](<[url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)>) for the data returned from a status call. -Parameters: ***None*** +Parameters: **_None_** ### 🔸SwitchBot Turn On + _Turn a device ON._ Parameters: + - `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). ### 🔸SwitchBot Turn Off + _Turn a device OFF._ Parameters: -- `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). +- `device` + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). ### 🔸SwitchBot Bot Control + _Interface for "Classic" Bot (turnOn, turnOff, press) devices._ **Parameters:** + - `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - `command:` - - string value between `turnOn`, `turnOff`, `press` + - string value between `turnOn`, `turnOff`, `press` - `repetition:` - - int value from `1` to `10`, _only works with `press`_, iterates the command as many times as selected. + - int value from `1` to `10`, _only works with `press`_, iterates the command as many times as selected. ### 🔸Switchbot Curtain Control + _Interface for Curtain (turnOn, turnOff, setPosition) devices._ + - `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - `command:` - - string value between `turnOn`, `turnOff`, `setPosition` + - string value between `turnOn`, `turnOff`, `setPosition` - `index:` - - [wip] int value between `?????` (required for "setPosition" command, otherwise it will be ignored) + - [wip] int value between `?????` (required for "setPosition" command, otherwise it will be ignored) - `mode:` - - string value between `Performance`, `Silent`, `Default` (required for "setPosition" command, otherwise it will be ignored) + - string value between `Performance`, `Silent`, `Default` (required for "setPosition" command, otherwise it will be ignored) - `position:` - - int value (in percentage) between `0` and `100` (required for "setPosition" command, otherwise it will be ignored) + - int value (in percentage) between `0` and `100` (required for "setPosition" command, otherwise it will be ignored) ### 🔸SwitchBot IR HVAC Control + _Interface for infrared HVAC (heating, ventilation and air conditioning) devices._ **Parameters:** + - `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - `state:` - - string value between `on` and `off` + - string value between `on` and `off` - `temperature:` - - int value from `16` to `30` + - int value from `16` to `30` - `mode:` - - string value between `Auto`, `Cool`, `Dry`, `Fan`, `Heat` + - string value between `Auto`, `Cool`, `Dry`, `Fan`, `Heat` - `fan_speed:` - - int value between `Auto`, `Low`, `Medium`, `High` - + - int value between `Auto`, `Low`, `Medium`, `High` ### 🔸SwitchBot IR Light Control + _Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) devices._ **Parameters:** + - `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - `command:` - - string value between `turnOn`, `turnOff`, `brightnessUp` and `brightnessDown` + - string value between `turnOn`, `turnOff`, `brightnessUp` and `brightnessDown` - `steps:` - - int value from `1` to `10`, _only works with `brightnessUp/Down`_, iterates the command as many times as selected. - + - int value from `1` to `10`, _only works with `brightnessUp/Down`_, iterates the command as many times as selected. ### 🔸SwitchBot Generic Command + _Allows you to send any request to the API. (See [documentation][generic-cmd-link])_ **Parameters:** + - `device` - - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). + - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). - `command:` - - One of the commands supported by the device. (see [documentation][generic-cmd-link]) + - One of the commands supported by the device. (see [documentation][generic-cmd-link]) - `parameter:` (optional) - - Parameter for the command, if required (e.g. `SetChannel`) - - use `default` if not used + - Parameter for the command, if required (e.g. `SetChannel`) + - use `default` if not used - `commandType:` - - `command` for standard commands - - `customize` for custom commands - + - `command` for standard commands + - `customize` for custom commands ## Work in Progress + The script works fine, but everything is still WIP, including this file. For any problems open an Issue, (soon I will insert a template for that). ## Changelog + ### v? (🟢 New Features) + **Add support for non-IR devices** : Bot, Contact Sensor, Curtain and Meter. **Add service 'Switchbot Curtains Command'** : Send command to Curtain device. @@ -230,16 +258,15 @@ For any problems open an Issue, (soon I will insert a template for that). **Add time trigger to run 'Refresh Devices' at startup.** - ### 2023.02.19 v0.2.1 (🛠️ Some Fixes) **Fixed ([#15][i15]) `commandType` parameters in `Generic Command`**.
-*Suggest updating if you need to control custom remotes created in the mobile app*. - +_Suggest updating if you need to control custom remotes created in the mobile app_. ### 2023.01.16 v0.2.0 (🟢 New Features and 🛠️ Some Fixes) + **Add service `SwitchBot IR Light Control`**:
- Send command via infrared to light device. +Send command via infrared to light device. **Corrected some descriptions**. @@ -253,31 +280,33 @@ For any problems open an Issue, (soon I will insert a template for that). **Renamed `SwitchBot Generic Command API Interface` in `SwitchBot Generic Command`**: _it doesn't affect function it's just a visual thing_. - ### 2023.01.14 v0.1.0 (⚠️ Breaking changes) + **Add service `SwitchBot Refresh Devices`**:
Retrieves your IR devices from the API. Services now requires `device` instead of `deviceId`. No need to copy paste the id manually anymore. Previously: - - Services param was `deviceId` - + +- Services param was `deviceId` + Now: - - Services Param is `device` (home assistant ID for sensor, e.g. `switch.switchbot_remote_my_light`) - -Make sure to run `SwitchBot Refresh Devices` before configuring anything else. -[Full Changelog History here][Changelog] +- Services Param is `device` (home assistant ID for sensor, e.g. `switch.switchbot_remote_my_light`) + +Make sure to run `SwitchBot Refresh Devices` before configuring anything else. +[Full Changelog History here][changelog] [licensing-shield]: https://img.shields.io/github/license/SiriosDev/SwitchBot-API-Script-Caller?style=flat-square [hacs-docs]: https://hacs.xyz/docs/setup/prerequisites [pyscript-docs]: https://hacs-pyscript.readthedocs.io/en/latest/installation.html [switchbot-api-repo]: https://github.com/OpenWonderLabs/SwitchBotAPI -[OpenWonderLabs-lnk]: https://github.com/OpenWonderLabs +[openwonderlabs-lnk]: https://github.com/OpenWonderLabs [generic-cmd-link]: https://github.com/OpenWonderLabs/SwitchBotAPI#send-device-control-commands [deviceid-link]: https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-list [issues]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/issues/new [pr]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/pulls -[Changelog]: CHANGELOG.md +[fork]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/fork +[changelog]: CHANGELOG.md [i15]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/issues/15 [unnum]: #20230114-v010-%EF%B8%8F-breaking-changes From b577cfc667ddbd984f86581824379108d353c384 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Fri, 3 Mar 2023 21:36:03 +0100 Subject: [PATCH 19/31] Lincesing --- README.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4bf5724..83bf4f2 100755 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ -[![License][licensing-shield]](LICENSE)
+[![License][licensing-shield-this]](LICENSE)
All rights to the [API][switchbot-api-repo] belong to [OpenWonderLabs][openwonderlabs-lnk]. -# SwitchBot API Script Caller (v 0.2.1) - -> **Warning**
-> At the moment not all API-compatible models are implemented, if you have the capability do a [fork][fork] and implement it (and request a [PR][pr]), otherwise open an [issue][issues] with "TEST|**_Model_Name_**" in the title +# SwitchBot API Script Caller (v 0.3) This (Py)Script allows you to control all (WIP) your SwitchBot devices via API calls (1.1). -‼️‼️At the moment we have implemented all the devices that we can test, so if you want to be a tester please open an [issue][issues] with "test" in the title or if you are also able to develop go ahead and do a [PR][pr]‼️‼️ +> **Warning**
+> At the moment not all API-compatible models are implemented, if you have the capability do a [fork][fork] and implement it (and request a [PR][pr]), otherwise open an [issue][issues] with "TEST|**_Model_Name_**" in the title. For more info click [here][switchbot-api-repo] @@ -297,7 +295,10 @@ Make sure to run `SwitchBot Refresh Devices` before configuring anything else. [Full Changelog History here][changelog] -[licensing-shield]: https://img.shields.io/github/license/SiriosDev/SwitchBot-API-Script-Caller?style=flat-square +[licensing-shield-this]: https://img.shields.io/github/license/SiriosDev/SwitchBot-API-Script-Caller?style=flat-square +[licensing-shield-ps]: https://img.shields.io/github/license/custom-components/pyscript?style=flat-square +[licensing-shield-sbapi]: https://img.shields.io/github/license/OpenWonderLabs/SwitchBotAPI?style=flat-square +[licensing-shield-hass]: https://img.shields.io/github/license/home-assistant/core?style=flat-square [hacs-docs]: https://hacs.xyz/docs/setup/prerequisites [pyscript-docs]: https://hacs-pyscript.readthedocs.io/en/latest/installation.html [switchbot-api-repo]: https://github.com/OpenWonderLabs/SwitchBotAPI @@ -310,3 +311,19 @@ Make sure to run `SwitchBot Refresh Devices` before configuring anything else. [changelog]: CHANGELOG.md [i15]: https://github.com/SiriosDev/SwitchBot-API-Script-Caller/issues/15 [unnum]: #20230114-v010-%EF%B8%8F-breaking-changes + +--- + +**SwitchBot API Script Caller** is an unofficial, community-driven script NOT affiliated, endorsed or supported by **Wonderlabs, Inc.** Some images used in this app are copyrighted and are supported under fair use. **SwitchBot** and **SwitchBot model names** are trademarks of **Wonderlabs**. No copyright infringement intended.
+**© SwitchBot Global.**
+**© Wonderlabs.** + +--- + +PyScript is distributed under [![License][licensing-shield-ps]](https://github.com/custom-components/pyscript)license.
+The APIs are distributed under [![License][licensing-shield-sbapi]][switchbot-api-repo] license.
+HomeAssistant is distributed under [![License][licensing-shield-hass]](https://github.com/home-assistant/core) license.
+ +***The License at the top of this document refers only to the code in this repository*** + +--- \ No newline at end of file From 65c0d55364ebbbe8c3d501448b169bf268646679 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Fri, 3 Mar 2023 21:36:27 +0100 Subject: [PATCH 20/31] fix --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 83bf4f2..5df08f1 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ [![License][licensing-shield-this]](LICENSE)
-All rights to the [API][switchbot-api-repo] belong to [OpenWonderLabs][openwonderlabs-lnk]. # SwitchBot API Script Caller (v 0.3) From 8cbe7ca4349cf731d07f24f0a57d6b20389d92b1 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Mon, 6 Mar 2023 14:18:43 +0100 Subject: [PATCH 21/31] new warnings --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5df08f1..f9ec9ff 100755 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This (Py)Script allows you to control all (WIP) your SwitchBot devices via API calls (1.1). > **Warning**
-> At the moment not all API-compatible models are implemented, if you have the capability do a [fork][fork] and implement it (and request a [PR][pr]), otherwise open an [issue][issues] with "TEST|**_Model_Name_**" in the title. +> **At the moment not all API-compatible models are implemented, if you have the capability do a [fork][fork] and implement it (and request a [PR][pr]), otherwise open an [issue][issues] with "TEST|`Model_Name`" in the title.** For more info click [here][switchbot-api-repo] @@ -30,16 +30,6 @@ For more info click [here][switchbot-api-repo] - PyScript Integration ([docs][pyscript-docs]) ## Installation - -⚠️ **If you update from one of the following versions there have been breaking changes:** - -
-Versions - -- [Unnumbered (2023.01.14)][unnum] - -
- ### Procedure 1. **Clone this repository in your config folder** @@ -91,8 +81,18 @@ cd SwitchBot-API-Script-Caller git pull ``` -⚠️ **See changelog before updating.** -The project is still under development, and breaking changes may **frequently** occur. +> **Warning**
+ +The project is still under development, and breaking changes may **frequently** occur.
+**If you update from one of the following versions there have been breaking changes:** + +
+Versions + +- [Unnumbered (2023.01.14)][unnum] + +
+ ### Installation Notes @@ -132,8 +132,13 @@ _Creates Home Assistant entity with the best type-domain association, otherwise _The `` corresponds to the name of the device in the SwitchBot app._ _If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_`(e.g.`switch.switchbot*remote_light_0D62`)* _The `` is an internal unique code._ -**_⚠️The entities can then be used for sending commands or getting status using other functions of this pyscript.⚠️_**
-**_⚠️ Not working stand alone ⚠️_**
+ +> **Warning**
+> **_The entities can then be used for sending commands or getting status using other functions of this pyscript._**
+ +> **Warning**
+> **_Not working stand alone_**
+ _If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ Parameters: **_None_** From af0fab69d4d5fd1e9459ae5a94bda63a477f83e0 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Mon, 6 Mar 2023 14:19:18 +0100 Subject: [PATCH 22/31] fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9ec9ff..55ec741 100755 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ cd SwitchBot-API-Script-Caller git pull ``` -> **Warning**
+**Warning**
The project is still under development, and breaking changes may **frequently** occur.
**If you update from one of the following versions there have been breaking changes:** From 5b712fed6f9de326081f355be073aa74c01acb22 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Mon, 6 Mar 2023 14:19:49 +0100 Subject: [PATCH 23/31] fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55ec741..f9ec9ff 100755 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ cd SwitchBot-API-Script-Caller git pull ``` -**Warning**
+> **Warning**
The project is still under development, and breaking changes may **frequently** occur.
**If you update from one of the following versions there have been breaking changes:** From 6f5d5bfdccdfa1aa48149d21a87328e7bdea7c29 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Mon, 6 Mar 2023 14:21:15 +0100 Subject: [PATCH 24/31] fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9ec9ff..369e946 100755 --- a/README.md +++ b/README.md @@ -82,8 +82,8 @@ git pull ``` > **Warning**
+> The project is still under development, and breaking changes may **frequently** occur. -The project is still under development, and breaking changes may **frequently** occur.
**If you update from one of the following versions there have been breaking changes:**
From 70075df5195c9e0b7a41344ba5f01d7cf2fad0b5 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Mon, 6 Mar 2023 15:01:21 +0100 Subject: [PATCH 25/31] New Refresh --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 369e946..0bfb13c 100755 --- a/README.md +++ b/README.md @@ -11,16 +11,20 @@ For more info click [here][switchbot-api-repo] - [Requirements](#requirements) - [Installation](#installation) + - [Procedure](#procedure) + - [Further Updates](#further-updates) + - [Installation Notes](#installation-notes) - [How To Use](#how-to-use) - - [Refresh Devices](#switchbot-refresh-devices) - - [Get Status](#switchbot-get-status) - - [Turn On](#switchbot-turn-on) - - [Turn Off](#switchbot-turn-off) - - [Bot](#switchbot-bot-control) - - [Curtain](#switchbot-curtain-control) - - [HVAC](#switchbot-ir-hvac-control) - - [Light](#switchbot-ir-light-control) - - [Generic Command](#switchbot-generic-command) + - [Summary](#summary) + - [Refresh Devices](#switchbot-refresh-devices) + - [Get Status](#switchbot-get-status) + - [Turn On](#switchbot-turn-on) + - [Turn Off](#switchbot-turn-off) + - [Bot](#switchbot-bot-control) + - [Curtain](#switchbot-curtain-control) + - [HVAC](#switchbot-ir-hvac-control) + - [Light](#switchbot-ir-light-control) + - [Generic Command](#switchbot-generic-command) - [Work in Progress](#work-in-progress) - [Changelog](#changelog) @@ -72,7 +76,7 @@ For more info click [here][switchbot-api-repo] ln -s /config/SwitchBot-API-Script-Caller/pyscript/apps/switchbot.py switchbot.py ``` -### Further Update +### Further Updates By following this procedure, the script can then be updated with newer version using git. @@ -119,31 +123,39 @@ It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devi - [SwitchBot Get Status (`pyscript.switchbot_get_status`)](#switchbot-get-status) - [SwitchBot Turn ON (`pyscript.switchbot_turn_on`)](#switchbot-turn-on) - [SwitchBot Turn OFF (`pyscript.switchbot_turn_off`)](#switchbot-turn-off) -- [SwitchBot Bot Control (`pyscript.switchbot_curtain_command`)](#switchbot-bot-control) +- [SwitchBot Bot Control (`pyscript.switchbot_bot_command`)](#switchbot-bot-control) - [SwitchBot Curtain Control (`pyscript.switchbot_curtain_command`)](#switchbot-curtain-control) - [SwitchBot IR HVAC Control (`pyscript.switchbot_hvac`)](#switchbot-ir-hvac-control) - [SwitchBot IR Light Control (`pyscript.switchbot_ir_light_control`)](#switchbot-ir-light-control) - [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) ### 🔸SwitchBot Refresh Devices +``` +pyscript.switchbot_refresh_devices +``` -_Creates Home Assistant entity with the best type-domain association, otherwise with domain `switch`. These devices are stored as `.switchbot_remote_`eg: A SwitchBot Curtain™ will become`cover.switchbot_remote_bedroom_curtains`* +_Create Home Assistant entity with the best `type`-`domain` association, otherwise with domain `switch`.
+These devices are stored as `.switchbot_remote_`.
+e.g. A SwitchBot Curtain™ will become`cover.switchbot_remote_bedroom_curtains`_ _The `` corresponds to the name of the device in the SwitchBot app._ -_If `` doesn't contains Alphanum characters (e.g is written in another alphabet), it is replaced by `_`(e.g.`switch.switchbot*remote_light_0D62`)* -_The `` is an internal unique code._ +_If `` doesn't contains Alphanum characters (e.g. is written in another alphabet), it is replaced by `_`(e.g. `switch.switchbot_remote_light_0D62`)_ -> **Warning**
-> **_The entities can then be used for sending commands or getting status using other functions of this pyscript._**
+> **Note**
+> _The `` is an internal unique code._ > **Warning**
-> **_Not working stand alone_**
+> The entities can then be used for sending commands or getting status using other functions of this pyscript.
+> **Not working stand alone.**
_If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ Parameters: **_None_** ### 🔸Switchbot Get Status +``` +pyscript.switchbot_get_status +``` _Gets the state of Switchbot Bots, Contact Sensors, Curtains and Meters._ Runs every five minutes generating 288 API calls per sensor per day. Switchbot limits API calls to 10,000 per day. So, this limits the number of devices to 34 (excluding IR devices.) See the [SwitchbotAPI API](<[url](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status)>) for the data returned from a status call. @@ -151,6 +163,9 @@ Runs every five minutes generating 288 API calls per sensor per day. Switchbot l Parameters: **_None_** ### 🔸SwitchBot Turn On +``` +pyscript.switchbot_turn_on +``` _Turn a device ON._ @@ -160,6 +175,9 @@ Parameters: - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). ### 🔸SwitchBot Turn Off +``` +pyscript.switchbot_turn_off +``` _Turn a device OFF._ @@ -169,6 +187,9 @@ Parameters: - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). ### 🔸SwitchBot Bot Control +``` +pyscript.switchbot_bot_command +``` _Interface for "Classic" Bot (turnOn, turnOff, press) devices._ @@ -182,6 +203,9 @@ _Interface for "Classic" Bot (turnOn, turnOff, press) devices._ - int value from `1` to `10`, _only works with `press`_, iterates the command as many times as selected. ### 🔸Switchbot Curtain Control +``` +pyscript.switchbot_curtain_command +``` _Interface for Curtain (turnOn, turnOff, setPosition) devices._ @@ -197,6 +221,9 @@ _Interface for Curtain (turnOn, turnOff, setPosition) devices._ - int value (in percentage) between `0` and `100` (required for "setPosition" command, otherwise it will be ignored) ### 🔸SwitchBot IR HVAC Control +``` +pyscript.switchbot_hvac +``` _Interface for infrared HVAC (heating, ventilation and air conditioning) devices._ @@ -214,6 +241,9 @@ _Interface for infrared HVAC (heating, ventilation and air conditioning) devices - int value between `Auto`, `Low`, `Medium`, `High` ### 🔸SwitchBot IR Light Control +``` +pyscript.switchbot_ir_light_control +``` _Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) devices._ @@ -227,6 +257,9 @@ _Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) - int value from `1` to `10`, _only works with `brightnessUp/Down`_, iterates the command as many times as selected. ### 🔸SwitchBot Generic Command +``` +pyscript.switchbot_generic_command +``` _Allows you to send any request to the API. (See [documentation][generic-cmd-link])_ From ddc1f65f34ab3195cd868e962a4f28454ebdca2e Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Fri, 10 Mar 2023 23:55:37 +0100 Subject: [PATCH 26/31] New Example - refresh serv --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0bfb13c..c185633 100755 --- a/README.md +++ b/README.md @@ -130,9 +130,6 @@ It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devi - [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) ### 🔸SwitchBot Refresh Devices -``` -pyscript.switchbot_refresh_devices -``` _Create Home Assistant entity with the best `type`-`domain` association, otherwise with domain `switch`.
These devices are stored as `.switchbot_remote_`.
@@ -150,7 +147,13 @@ _If `` doesn't contains Alphanum characters (e.g. is written in ano _If this service does not find all the devices it had previously found, it will alert you with a persistent notification in the HA WebUi._ -Parameters: **_None_** +#### Example +```yaml +service: pyscript.switchbot_refresh_devices +``` +#### Service Data +**_None_** + ### 🔸Switchbot Get Status ``` From 069b3ac2eaf303dfa091dcc2820b65d794f6ae11 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Sun, 12 Mar 2023 21:51:33 +0100 Subject: [PATCH 27/31] new badges --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c185633..fc19d2c 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![License][licensing-shield-this]](LICENSE)
+[![License][licensing-shield-this]](LICENSE) ![Maintenance][maintenance-shield] ![Activity][comActivity-shield] [![Donation][donation-shield]](https://ko-fi.com/siriosdev)
# SwitchBot API Script Caller (v 0.3) @@ -336,6 +336,9 @@ Make sure to run `SwitchBot Refresh Devices` before configuring anything else. [Full Changelog History here][changelog] [licensing-shield-this]: https://img.shields.io/github/license/SiriosDev/SwitchBot-API-Script-Caller?style=flat-square +[maintenance-shield]: https://img.shields.io/maintenance/yes/2023?style=flat-square +[comActivity-shield]: https://img.shields.io/github/commit-activity/m/SiriosDev/SwitchBot-API-Script-Caller?style=flat-square +[donation-shield]: https://img.shields.io/badge/Buy%20Me%20A%20Ko--Fi-Donate-magenta [licensing-shield-ps]: https://img.shields.io/github/license/custom-components/pyscript?style=flat-square [licensing-shield-sbapi]: https://img.shields.io/github/license/OpenWonderLabs/SwitchBotAPI?style=flat-square [licensing-shield-hass]: https://img.shields.io/github/license/home-assistant/core?style=flat-square From 68a7084d84108b684d3de79776fc9cb3d7f2dabd Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Sun, 12 Mar 2023 22:10:52 +0100 Subject: [PATCH 28/31] change refresh description --- pyscript/apps/switchbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index deca527..8a5457b 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -187,7 +187,7 @@ def h_switchbot_get_status(devices=None): def switchbot_refresh_devices(): """yaml name: SwitchBot Refresh Devices -description: "This service lists the devices registered in all the Switchbot Hub™ connected to your account. The devices are saved as 'switch.switchbot_remote_' or similar in Home Assistant. (API call consumed: 1*execution)" +description: "Lists all the devices registered in your Switchbot Hubs™ connected to your account. (APIs call consumed: 1*execution)" fields: """ headers_dict=auth(pyscript.app_config) From 276a81e72bc56b19c71571729a9334905f5c4bc2 Mon Sep 17 00:00:00 2001 From: Sirios_Dev <26876994+SiriosDev@users.noreply.github.com> Date: Sun, 12 Mar 2023 23:12:43 +0100 Subject: [PATCH 29/31] New Refresh --- README.md | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index fc19d2c..7ce5e78 100755 --- a/README.md +++ b/README.md @@ -129,16 +129,24 @@ It is important to execute [`SwitchBot Refresh Devices`](#switchbot-refresh-devi - [SwitchBot IR Light Control (`pyscript.switchbot_ir_light_control`)](#switchbot-ir-light-control) - [SwitchBot Generic Command (`pyscript.switchbot_generic_command`)](#switchbot-generic-command) -### 🔸SwitchBot Refresh Devices +### SwitchBot Refresh Devices + +> **Warning**
+> (API call consumed: 1*execution) + +**This service lists all the devices registered in your Switchbot Hubs™ connected to your account.** + +The new entities will have a type corresponding to that of the switchbot app, following the best `type`-`domain` association, in case of mismatch will be assigned to the domain `switch`. + +> **Note**
+> _These devices are stored as `.switchbot_remote_`._
+> _e.g. A SwitchBot Curtain™ will become `cover.switchbot_remote_bedroom_curtains`_. -_Create Home Assistant entity with the best `type`-`domain` association, otherwise with domain `switch`.
-These devices are stored as `.switchbot_remote_`.
-e.g. A SwitchBot Curtain™ will become`cover.switchbot_remote_bedroom_curtains`_ -_The `` corresponds to the name of the device in the SwitchBot app._ -_If `` doesn't contains Alphanum characters (e.g. is written in another alphabet), it is replaced by `_`(e.g. `switch.switchbot_remote_light_0D62`)_ > **Note**
+> _The `` corresponds to the name of the device in the SwitchBot app, (with `_` instead of ` `)._ +> _If it does not contains Alphanum characters, it is replaced by `_`
(e.g. `switch.switchbot_remote_light_0D62`)_ > _The `` is an internal unique code._ > **Warning**
@@ -155,7 +163,7 @@ service: pyscript.switchbot_refresh_devices **_None_** -### 🔸Switchbot Get Status +### Switchbot Get Status ``` pyscript.switchbot_get_status ``` @@ -165,7 +173,7 @@ Runs every five minutes generating 288 API calls per sensor per day. Switchbot l Parameters: **_None_** -### 🔸SwitchBot Turn On +### SwitchBot Turn On ``` pyscript.switchbot_turn_on ``` @@ -177,7 +185,7 @@ Parameters: - `device` - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). -### 🔸SwitchBot Turn Off +### SwitchBot Turn Off ``` pyscript.switchbot_turn_off ``` @@ -189,7 +197,7 @@ Parameters: - `device` - See [`SwitchBot Refresh Devices`](#switchbot-refresh-devices). -### 🔸SwitchBot Bot Control +### SwitchBot Bot Control ``` pyscript.switchbot_bot_command ``` @@ -205,7 +213,7 @@ _Interface for "Classic" Bot (turnOn, turnOff, press) devices._ - `repetition:` - int value from `1` to `10`, _only works with `press`_, iterates the command as many times as selected. -### 🔸Switchbot Curtain Control +### Switchbot Curtain Control ``` pyscript.switchbot_curtain_command ``` @@ -223,7 +231,7 @@ _Interface for Curtain (turnOn, turnOff, setPosition) devices._ - `position:` - int value (in percentage) between `0` and `100` (required for "setPosition" command, otherwise it will be ignored) -### 🔸SwitchBot IR HVAC Control +### SwitchBot IR HVAC Control ``` pyscript.switchbot_hvac ``` @@ -243,7 +251,7 @@ _Interface for infrared HVAC (heating, ventilation and air conditioning) devices - `fan_speed:` - int value between `Auto`, `Low`, `Medium`, `High` -### 🔸SwitchBot IR Light Control +### SwitchBot IR Light Control ``` pyscript.switchbot_ir_light_control ``` @@ -259,7 +267,7 @@ _Interface for infrared Light (turnOn, turnOff, brightnessUp and brightnessDown) - `steps:` - int value from `1` to `10`, _only works with `brightnessUp/Down`_, iterates the command as many times as selected. -### 🔸SwitchBot Generic Command +### SwitchBot Generic Command ``` pyscript.switchbot_generic_command ``` From 0f58e298d812fb65134cf78f7df26e9f15b1ff50 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:42:32 +0200 Subject: [PATCH 30/31] new auth() logic + user nonce deprecation --- pyscript/apps/switchbot.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index 8a5457b..94430d7 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -4,6 +4,7 @@ from base64 import b64encode from requests import post, get from re import sub +from uuid import uuid4 import yaml, io @@ -18,21 +19,29 @@ # "input" from config def auth(conf_dict=None): - - token=str(conf_dict['token']) - secret=str(conf_dict['secret']) - nonce=str(conf_dict['nonce']) - - t = int(round(time() * 1000)) - string_to_sign = '{}{}{}'.format(token, t, nonce) - - string_to_sign = bytes(string_to_sign, 'utf-8') - secret = bytes(secret, 'utf-8') + + apiHeader={} + + token=str(conf_dict['token']) + secret=str(conf_dict['secret']) + + nonce=uuid4() + + t = int(round(time() * 1000)) - sign = b64encode(new(secret, msg=string_to_sign, digestmod=sha256).digest()) + string_to_sign = bytes(F"{token}{t}{nonce}", 'utf-8') + secret = bytes(secret, 'utf-8') - h={"Authorization": (str(token)), "t": (str(t)), "sign": (str(sign, 'utf-8')), "nonce": (str(nonce)), "Content-Type": "application/json; charset=utf8"} - return h + sign = b64encode(new(secret, msg=string_to_sign, digestmod=sha256).digest()) + + apiHeader['Authorization']=token + apiHeader['Content-Type']='application/json' + apiHeader['charset']='utf8' + apiHeader['t']=str(t) + apiHeader['sign']=str(sign, 'utf-8') + apiHeader['nonce']=str(nonce) + + return apiHeader def gen_icon(dev): '''Generate icon based on device type. Default to a remote icon.''' From 8f2d4df1534a8592859328e7d7cce07074dc0251 Mon Sep 17 00:00:00 2001 From: SiriosDev <{ID}+{username}@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:46:02 +0200 Subject: [PATCH 31/31] Added Alt Auto in HVAC, better compatibilty --- pyscript/apps/switchbot.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pyscript/apps/switchbot.py b/pyscript/apps/switchbot.py index 94430d7..5d3d953 100755 --- a/pyscript/apps/switchbot.py +++ b/pyscript/apps/switchbot.py @@ -285,14 +285,15 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, mode: name: Mode - description: Select a mode (required for "on" state) ("Cool" by Default) - example: Heat - default: Cool + description: Select a mode (required for "on" state) ("Fan" by Default) (Use Alt-Auto if Auto not working) + example: Fan + default: Fan required: false selector: select: options: - Auto + - Alt-Auto - Cool - Heat - Dry @@ -322,7 +323,7 @@ def switchbot_ir_hvac(device=None, temperature=None, mode=None, fan_speed=None, mode = "Cool" if fan_speed == None or state == "off": fan_speed = "Auto" - modes={"Auto": "1","Cool": "2","Dry": "3","Fan": "4","Heat": "5"} + modes={"Alt-Auto":"0", "Auto": "1","Cool": "2","Dry": "3","Fan": "4","Heat": "5"} speeds={"Auto": "1","Low": "2","Medium": "3","High": "4"} command_execute(headers_dict, deviceId, 'setAll', parameter=f"{temperature},{modes[mode]},{speeds[fan_speed]},{state}")