├── .dockerignore ├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── README.rst ├── examples ├── blinds.py ├── camera.py ├── custom_device.py ├── dim_switch.py ├── doorbell.py ├── garagedoor.py ├── light.py ├── multi_switch.py ├── push_notification.py ├── smart_lock.py ├── speaker.py ├── switch.py ├── temperature_sensor.py ├── thermostat.py └── tv.py ├── setup.py └── sinric ├── __init__.py ├── _brightness_controller.py ├── _callback_handler.py ├── _camera_stream_controller.py ├── _color_controller.py ├── _color_temperature.py ├── _events.py ├── _leaky_bucket.py ├── _lock_controller.py ├── _mode_controller.py ├── _power_controller.py ├── _power_level_controller.py ├── _queues.py ├── _range_value_controller.py ├── _signature.py ├── _sinricpro.py ├── _sinricpro_constants.py ├── _sinricpro_udp.py ├── _sinricpro_websocket.py ├── _speaker_controller.py ├── _temperature_controller.py ├── _thermostat_controller.py ├── _tv_contorller.py └── helpers ├── set_limits.py └── wait.py /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | build/ 3 | dist/ 4 | README.md 5 | README.rst 6 | test.py 7 | test_events.py 8 | sinricpro_logfile.log 9 | localdata.json 10 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | jobs: 16 | deploy: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.x' 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install build 30 | - name: Build package 31 | run: python -m build 32 | - name: Publish package 33 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 34 | with: 35 | user: __token__ 36 | password: ${{ secrets.PYPI_API_TOKEN }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | events/ 3 | devices/ 4 | cred* 5 | envsinric/ 6 | tes*.py 7 | sinricpro_logfile* 8 | tests/ 9 | test_bulb.py 10 | credential.py 11 | venv/ 12 | localdata* 13 | dist/ 14 | build/ 15 | *egg*/ 16 | __pycache__/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.7.5] 2 | * fix sinric.helpers not found 3 | 4 | ## [2.7.4] 5 | 6 | ### Features 7 | * **thermostat example added** 8 | * `THERMOSTAT_MODE_COOL`, `THERMOSTAT_MODE_HEAT`, `THERMOSTAT_MODE_AUTO`, `THERMOSTAT_MODE_OFF` constants added to `_sinricpro_constants.py` 9 | 10 | ## [2.7.1] 11 | 12 | ### Features 13 | * **WebRTC for RaspCam** 14 | * **Code refacorting** 15 | 16 | ## [2.6.3] 17 | 18 | ### Bugfix 19 | * **Exit if the connection closed** 20 | * **Example logging disabled** 21 | 22 | ## [2.6.2] 23 | 24 | ### Bugfix 25 | * **'NoneType' object is not callable** 26 | 27 | ## [2.6.1] 28 | 29 | ### Features 30 | * **Push notification feature added** 31 | * **Event callbacks fixed** 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python3 SDK for Sinric Pro 2 | 3 | [![](https://img.shields.io/pypi/format/sinricpro.svg)](https://github.com/sinricpro/Python-SDK) 4 | [![](https://img.shields.io/pypi/v/sinricpro.svg)](https://github.com/sinricpro/Python-SDK) 5 | [![Downloads](https://pepy.tech/badge/sinricpro)](https://pypi.org/project/sinricpro/) 6 | [![](https://img.shields.io/github/repo-size/sinricpro/Python-SDK.svg)](https://github.com/sinricpro/Python-SDK) 7 | [![Discord](https://img.shields.io/badge/discord-%23python-blue.svg)](https://discord.gg/W5299EgB59)
8 | 9 | ## Dependencies 10 | * Python 3.9.7 11 | * websockets 10.1 12 | 13 | Make sure to run `pip install setuptools` before installtion 14 | 15 | ### Install 16 | pip install sinricpro --user 17 | 18 | ### Upgarde 19 | 20 | pip install sinricpro --upgrade --user 21 | 22 | ### Check the examples [here](https://github.com/sinricpro/python-sdk/tree/master/examples) 23 | 24 | ## Join the community! 25 | Join us on our [Official Discord Server](https://discord.gg/W5299EgB59)! 26 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | SINRIC PRO 2 | =============== 3 | 4 | Automate your home using Amazon Alexa, Google Home, SmartThings with Sinric Pro https://sinric.pro/ 5 | 6 | Installation : 7 | -------------- 8 | 9 | Python3 10 | ------- 11 | 12 | :: 13 | 14 | python3 -m pip install sinricpro --user 15 | 16 | Examples : 17 | -------------- 18 | 19 | https://github.com/sinricpro/python-sdk/tree/master/examples -------------------------------------------------------------------------------- /examples/blinds.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | BLINDS_ID = '' 7 | 8 | 9 | def set_power_state(device_id, state): 10 | print('device_id: {} state: {}'.format(device_id, state)) 11 | return True, state 12 | 13 | 14 | def set_range_value(device_id, value, instance_id): 15 | print('device_id: {} set to: {}'.format(device_id, value)) 16 | return True, value, instance_id 17 | 18 | 19 | callbacks = { 20 | SinricProConstants.SET_POWER_STATE: set_power_state, 21 | SinricProConstants.SET_RANGE_VALUE: set_range_value 22 | } 23 | 24 | if __name__ == '__main__': 25 | loop = asyncio.get_event_loop() 26 | client = SinricPro(APP_KEY, [BLINDS_ID], callbacks, 27 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 28 | loop.run_until_complete(client.connect()) 29 | 30 | # client.event_handler.raise_event(BLINDS_ID, SinricProConstants.SET_RANGE_VALUE, data = {SinricProConstants.RANGE_VALUE: 30 }) 31 | # client.event_handler.raise_event(BLINDS_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_ON }) 32 | # client.event_handler.raise_event(BLINDS_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF }) 33 | -------------------------------------------------------------------------------- /examples/camera.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import requests 3 | import asyncio 4 | import base64 5 | 6 | APP_KEY = "" 7 | APP_SECRET = "" 8 | CAMERA_ID = '' 9 | 10 | def get_webrtc_answer(device_id, offer): 11 | sdp_offer = base64.b64decode(offer) 12 | print('device_id: {} offer: {}'.format(device_id, offer)) 13 | 14 | # PORT 8889 for WebRTC. eg: for PiCam, use http://:8889/cam/whep 15 | mediamtx_url = "http://:8889//whep" 16 | headers = {"Content-Type": "application/sdp"} 17 | response = requests.post(mediamtx_url, headers=headers, data=sdp_offer) 18 | 19 | if response.status_code == 201: 20 | answer = base64.b64encode(response.content).decode("utf-8") 21 | return True, answer 22 | else: 23 | return False 24 | 25 | 26 | def power_state(device_id, state): 27 | print('device_id: {} power state: {}'.format(device_id, state)) 28 | return True, state 29 | 30 | def get_camera_stream_url(device_id, protocol): 31 | # Google Home: RTSP protocol not supported. Requires a Chromecast TV or Google Nest Hub 32 | # Alexa: RTSP url must be interleaved TCP on port 443 (for both RTP and RTSP) over TLS 1.2 port 443 33 | 34 | print('device_id: {} protocol: {}'.format(device_id, protocol)) 35 | 36 | if protocol == "rstp": 37 | return True, 'rtsp://rtspurl:443' # RSTP. 38 | else: 39 | return True, 'https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8' # HLS 40 | 41 | callbacks = { 42 | SinricProConstants.GET_WEBRTC_ANSWER: get_webrtc_answer, 43 | SinricProConstants.GET_CAMERA_STREAM_URL: get_camera_stream_url, 44 | SinricProConstants.SET_POWER_STATE: power_state 45 | } 46 | 47 | if __name__ == '__main__': 48 | loop = asyncio.get_event_loop() 49 | client = SinricPro(APP_KEY, [CAMERA_ID], callbacks, 50 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 51 | loop.run_until_complete(client.connect()) 52 | -------------------------------------------------------------------------------- /examples/custom_device.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | DEVICE_ID = '' 7 | 8 | 9 | def power_state(device_id, state): 10 | print(device_id, state) 11 | return True, state 12 | 13 | 14 | def range_value(device_id, range_value, instance_id): 15 | print(device_id, range_value, instance_id) 16 | return True, range_value, instance_id 17 | 18 | 19 | def mode_value(device_id, mode_value, instance_id): 20 | print(device_id, mode_value, instance_id) 21 | return True, mode_value, instance_id 22 | 23 | 24 | callbacks = { 25 | SinricProConstants.SET_POWER_STATE: power_state, 26 | SinricProConstants.SET_RANGE_VALUE: range_value, 27 | SinricProConstants.SET_MODE: mode_value 28 | } 29 | 30 | if __name__ == '__main__': 31 | loop = asyncio.get_event_loop() 32 | client = SinricPro(APP_KEY, [DEVICE_ID], callbacks, 33 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 34 | loop.run_until_complete(client.connect()) 35 | -------------------------------------------------------------------------------- /examples/dim_switch.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | DIM_SWITCH_ID = '' 7 | 8 | 9 | def power_state(device_id, state): 10 | print('device_id: {} state: {}'.format(device_id, state)) 11 | return True, state 12 | 13 | 14 | def power_level(device_id, power_level): 15 | print('device_id: {} power level: {}'.format(device_id, power_level)) 16 | return True, power_level 17 | 18 | 19 | callbacks = { 20 | SinricProConstants.SET_POWER_STATE: power_state, 21 | SinricProConstants.SET_POWER_LEVEL: power_level 22 | } 23 | 24 | if __name__ == '__main__': 25 | loop = asyncio.get_event_loop() 26 | client = SinricPro(APP_KEY, [DIM_SWITCH_ID], callbacks, 27 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 28 | loop.run_until_complete(client.connect()) 29 | 30 | # client.event_handler.raise_event(DIM_SWITCH_ID, SinricProConstants.SET_POWER_LEVEL, data = {SinricProConstants.POWER_LEVEL: 50 }) 31 | # client.event_handler.raise_event(DIM_SWITCH_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_ON }) 32 | # client.event_handler.raise_event(DIM_SWITCH_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF }) 33 | -------------------------------------------------------------------------------- /examples/doorbell.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | DOORBELL_ID = '' 7 | 8 | ''' 9 | DON'T FORGET TO TURN ON 'Doorbell Press' IN ALEXA APP 10 | ''' 11 | 12 | 13 | async def events(): 14 | while True: 15 | # client.event_handler.raise_event(DOORBELL_ID, SinricProConstants.DOORBELLPRESS) 16 | # await sleep(60) # Server will trottle / block IPs sending events too often. 17 | pass 18 | 19 | callbacks = {} 20 | 21 | if __name__ == '__main__': 22 | loop = asyncio.get_event_loop() 23 | client = SinricPro(APP_KEY, [DOORBELL_ID], callbacks, event_callbacks=events, 24 | enable_log=True, restore_states=False, secret_key=APP_SECRET) 25 | loop.run_until_complete(client.connect()) 26 | 27 | # client.event_handler.raise_event(DOORBELL_ID, SinricProConstants.DOORBELLPRESS) 28 | -------------------------------------------------------------------------------- /examples/garagedoor.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | GARAGEDOOR_ID = '' 7 | 8 | 9 | def power_state(device_id, state): 10 | print('device_id: {} state: {}'.format(device_id, state)) 11 | return True, device_id 12 | 13 | 14 | def set_mode(device_id, state, instance_id): 15 | print('device_id: {} mode: {}'.format(device_id, state)) 16 | return True, state, instance_id 17 | 18 | 19 | callbacks = { 20 | SinricProConstants.SET_MODE: set_mode, 21 | SinricProConstants.SET_POWER_STATE: power_state 22 | } 23 | 24 | if __name__ == '__main__': 25 | loop = asyncio.get_event_loop() 26 | client = SinricPro(APP_KEY, [GARAGEDOOR_ID], callbacks, 27 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 28 | loop.run_until_complete(client.connect()) 29 | 30 | # client.event_handler.raise_event(GARAGEDOOR_ID, SinricProConstants.SET_MODE, data = {SinricProConstants.MODE: SinricProConstants.OPEN }) 31 | # client.event_handler.raise_event(GARAGEDOOR_ID, SinricProConstants.SET_MODE, data = {SinricProConstants.MODE: SinricProConstants.CLOSE }) 32 | -------------------------------------------------------------------------------- /examples/light.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | LIGHT_ID = '' 7 | 8 | 9 | def power_state(device_id, state): 10 | print('device_id: {} state: {}'.format(device_id, state)) 11 | return True, state 12 | 13 | 14 | def set_brightness(device_id, brightness): 15 | print('device_id: {} brightness: {}'.format(device_id, brightness)) 16 | return True, brightness 17 | 18 | 19 | def adjust_brightness(device_id, brightness): 20 | print('device_id: {} adjusted brightness level: {}'.format(device_id, brightness)) 21 | return True, brightness 22 | 23 | 24 | def set_color(device_id, r, g, b): 25 | print('device_id: {} R:{},G:{},B:{}'.format(device_id, r, g, b)) 26 | return True 27 | 28 | 29 | def set_color_temperature(device_id, color_temperature): 30 | print('device_id: {} color temperature:{}'.format( 31 | device_id, color_temperature)) 32 | return True 33 | 34 | 35 | def increase_color_temperature(device_id, value): 36 | print('device_id: {} value:{}'.format(device_id, value)) 37 | return True, value 38 | 39 | 40 | def decrease_color_temperature(device_id, value): 41 | print('device_id: {} value:{}'.format(device_id, value)) 42 | return True, value 43 | 44 | 45 | callbacks = { 46 | SinricProConstants.SET_POWER_STATE: power_state, 47 | SinricProConstants.SET_BRIGHTNESS: set_brightness, 48 | SinricProConstants.ADJUST_BRIGHTNESS: adjust_brightness, 49 | SinricProConstants.SET_COLOR: set_color, 50 | SinricProConstants.SET_COLOR_TEMPERATURE: set_color_temperature, 51 | SinricProConstants.INCREASE_COLOR_TEMPERATURE: increase_color_temperature, 52 | SinricProConstants.DECREASE_COLOR_TEMPERATURE: decrease_color_temperature 53 | } 54 | 55 | if __name__ == '__main__': 56 | loop = asyncio.get_event_loop() 57 | client = SinricPro(APP_KEY, [LIGHT_ID], callbacks, enable_log=False, 58 | restore_states=False, secret_key=APP_SECRET) 59 | loop.run_until_complete(client.connect()) 60 | 61 | # To update the light state on server. 62 | # client.event_handler.raise_event(LIGHT_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_ON }) 63 | # client.event_handler.raise_event(LIGHT_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF }) 64 | # client.event_handler.raise_event(LIGHT_ID, SinricProConstants.SET_COLOR, data = {SinricProConstants.RED: 0, SinricProConstants.GREEN: 0, SinricProConstants.BLUE: 0}) 65 | # client.event_handler.raise_event(LIGHT_ID, SinricProConstants.SET_COLOR_TEMPERATURE, data={SinricProConstants.COLOR_TEMPERATURE: 2400}) 66 | -------------------------------------------------------------------------------- /examples/multi_switch.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | SWITCH_ID_1 = '' 7 | SWITCH_ID_2 = '' 8 | 9 | 10 | def power_state(device_id, state): 11 | if device_id == SWITCH_ID_1: 12 | print('device_id: {} state: {}'.format(device_id, state)) 13 | elif device_id == SWITCH_ID_2: 14 | print('device_id: {} state: {}'.format(device_id, state)) 15 | else: 16 | print("device_id not found!") 17 | 18 | return True, state 19 | 20 | 21 | callbacks = { 22 | SinricProConstants.SET_POWER_STATE: power_state 23 | } 24 | 25 | if __name__ == '__main__': 26 | loop = asyncio.get_event_loop() 27 | client = SinricPro(APP_KEY, [SWITCH_ID_1, SWITCH_ID_2], callbacks, 28 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 29 | loop.run_until_complete(client.connect()) 30 | 31 | # To update the power state on server. 32 | # client.event_handler.raise_event(SWITCH_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_ON }) 33 | # client.event_handler.raise_event(SWITCH_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF }) 34 | -------------------------------------------------------------------------------- /examples/push_notification.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | from asyncio import sleep 4 | 5 | APP_KEY = '' 6 | APP_SECRET = '' 7 | DEVICE_ID = '' 8 | 9 | 10 | async def events(): 11 | while True: 12 | client.event_handler.raise_event( 13 | DEVICE_ID, SinricProConstants.PUSH_NOTIFICATION, data={'alert': "Hello there"}) 14 | # Server will trottle / block IPs sending events too often. 15 | await sleep(60) 16 | 17 | if __name__ == '__main__': 18 | loop = asyncio.get_event_loop() 19 | client = SinricPro(APP_KEY, [DEVICE_ID], {}, event_callbacks=events, 20 | enable_log=True, restore_states=False, secret_key=APP_SECRET) 21 | loop.run_until_complete(client.connect()) 22 | -------------------------------------------------------------------------------- /examples/smart_lock.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | LOCK_ID = '' 7 | 8 | 9 | def lock_state(device_id, state): 10 | print('device_id: {} lock state: {}'.format(device_id, state)) 11 | return True, state 12 | 13 | 14 | callbacks = { 15 | SinricProConstants.SET_LOCK_STATE: lock_state 16 | } 17 | 18 | if __name__ == '__main__': 19 | loop = asyncio.get_event_loop() 20 | client = SinricPro(APP_KEY, [LOCK_ID], callbacks, enable_log=False, 21 | restore_states=False, secret_key=APP_SECRET) 22 | loop.run_until_complete(client.connect()) 23 | 24 | 25 | # To update the lock state on server. 26 | # client.event_handler.raise_event(LOCK_ID, SinricProConstants.SET_LOCK_STATE, data={ 27 | # SinricProConstants.STATE: SinricProConstants.LOCK_STATE_LOCKED}) 28 | # client.event_handler.raise_eventttt(LOCK_ID, SinricProConstants.SET_LOCK_STATE, data={ 29 | # SinricProConstants.STATE: SinricProConstants.LOCK_STATE_UNLOCKED}) 30 | -------------------------------------------------------------------------------- /examples/speaker.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | SPEAKER_ID = '' 7 | 8 | 9 | def power_state(device_id, state): 10 | print('device_id: {} lock state: {}'.format(device_id, state)) 11 | return True, state 12 | 13 | 14 | def set_bands(device_id, name, level): 15 | print('device_id: {}, name: {}, name: {}'.format(device_id, name, level)) 16 | return True, {'name': name, 'level': level} 17 | 18 | 19 | def adjust_bands(device_id, name, level, direction): 20 | print('device_id: {}, name: {}, name: {}, direction: {}'.format( 21 | device_id, name, level, direction)) 22 | return True, {'name': name, 'level': level} 23 | 24 | 25 | def reset_bands(device_id, band1, band2, band3): 26 | print('device_id: {}, band1: {}, band2: {}, band3: {}'.format( 27 | device_id, band1, band2, band3)) 28 | return True 29 | 30 | 31 | def set_mode(device_id, mode): 32 | print('device_id: {} mode: {}'.format(device_id, mode)) 33 | return True, mode 34 | 35 | 36 | def set_mute(device_id, mute): 37 | print('device_id: {} mute: {}'.format(device_id, mute)) 38 | return True, mute 39 | 40 | 41 | def set_volume(device_id, volume): 42 | print('device_id: {} volume: {}'.format(device_id, volume)) 43 | return True, volume 44 | 45 | 46 | callbacks = { 47 | SinricProConstants.SET_POWER_STATE: power_state, 48 | SinricProConstants.SET_BANDS: set_bands, 49 | SinricProConstants.SET_MODE: set_mode, 50 | SinricProConstants.ADJUST_BANDS: adjust_bands, 51 | SinricProConstants.RESET_BANDS: reset_bands, 52 | SinricProConstants.SET_MUTE: set_mute, 53 | SinricProConstants.SET_VOLUME: set_volume 54 | } 55 | 56 | if __name__ == '__main__': 57 | loop = asyncio.get_event_loop() 58 | client = SinricPro(APP_KEY, [SPEAKER_ID], callbacks, 59 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 60 | loop.run_until_complete(client.connect()) 61 | 62 | # To update the speaker state on server. 63 | # client.event_handler.raise_event(speakerId, SinricProConstants.SET_BANDS, data = {'name': '','level': 0}) 64 | -------------------------------------------------------------------------------- /examples/switch.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | SWITCH_ID = '' 7 | 8 | 9 | def power_state(device_id, state): 10 | print('device_id: {} state: {}'.format(device_id, state)) 11 | return True, state 12 | 13 | 14 | callbacks = { 15 | SinricProConstants.SET_POWER_STATE: power_state 16 | } 17 | 18 | if __name__ == '__main__': 19 | loop = asyncio.get_event_loop() 20 | client = SinricPro(APP_KEY, [SWITCH_ID], callbacks, 21 | enable_log=False, restore_states=False, secret_key=APP_SECRET) 22 | loop.run_until_complete(client.connect()) 23 | 24 | # To update the power state on server. 25 | # client.event_handler.raise_event(SWITCH_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_ON }) 26 | # client.event_handler.raise_event(SWITCH_ID, SinricProConstants.SET_POWER_STATE, data = {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF }) 27 | -------------------------------------------------------------------------------- /examples/temperature_sensor.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | from asyncio import sleep 4 | 5 | APP_KEY = '' 6 | APP_SECRET = '' 7 | TEMPERATURE_SENSOR_ID = '' 8 | 9 | 10 | async def events(): 11 | while True: 12 | # client.event_handler.raise_event(TEMPERATURE_SENSOR_ID, 13 | # SinricProConstants.CURRENT_TEMPERATURE, 14 | # data= { SinricProConstants.HUMIDITY: 75.3, SinricProConstants.TEMPERATURE: 24}) 15 | # client.event_handler.raise_event(TEMPERATURE_SENSOR_ID, SinricProConstants.SET_POWER_STATE, data= {SinricProConstants.STATE: SinricProConstants.POWER_STATE_ON}) 16 | # client.event_handler.raise_event(TEMPERATURE_SENSOR_ID, SinricProConstants.SET_POWER_STATE, data= {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF}) 17 | # Server will trottle / block IPs sending events too often. 18 | await sleep(60) 19 | 20 | callbacks = {} 21 | 22 | if __name__ == '__main__': 23 | loop = asyncio.get_event_loop() 24 | client = SinricPro(APP_KEY, [TEMPERATURE_SENSOR_ID], callbacks, event_callbacks=events, 25 | enable_log=True, restore_states=False, secret_key=APP_SECRET) 26 | loop.run_until_complete(client.connect()) 27 | 28 | # To update the temperature on the server. 29 | # client.event_handler.raise_event(temperatureSensorDeviceId, SinricProConstants.CURRENT_TEMPERATURE, data={'humidity': 75.3, 'temperature': 24}) 30 | -------------------------------------------------------------------------------- /examples/thermostat.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | from asyncio import sleep 4 | 5 | APP_KEY = "" 6 | APP_SECRET = "" 7 | THERMOSTAT_ID = "" 8 | 9 | def power_state(device_id, state): 10 | print('device_id: {} state: {}'.format(device_id, state)) 11 | return True, state 12 | 13 | def target_temperature(device_id, temperature): 14 | print('device_id: {} set temperature: {}'.format(device_id, temperature)) 15 | return True, temperature 16 | 17 | def set_thermostate_mode(device_id, thermostat_mode): 18 | print('device_id: {} set thermostat mode: {}'.format(device_id, thermostat_mode)) 19 | return True, thermostat_mode 20 | 21 | def mode_value(device_id, mode_value): 22 | print(device_id, mode_value) 23 | return True, mode_value 24 | 25 | async def events(): 26 | while True: 27 | # client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.SET_THERMOSTAT_MODE, data= { SinricProConstants.MODE : SinricProConstants.THERMOSTAT_MODE_COOL}) 28 | # client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.SET_POWER_STATE, data= {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF}) 29 | # client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.CURRENT_TEMPERATURE, data={'humidity': 75.3, 'temperature': 24}) 30 | # Server will trottle / block IPs sending events too often. 31 | await sleep(60) 32 | 33 | callbacks = { 34 | SinricProConstants.SET_POWER_STATE: power_state, 35 | SinricProConstants.TARGET_TEMPERATURE: target_temperature, 36 | SinricProConstants.SET_THERMOSTAT_MODE: set_thermostate_mode 37 | } 38 | 39 | if __name__ == '__main__': 40 | loop = asyncio.get_event_loop() 41 | client = SinricPro(APP_KEY, [THERMOSTAT_ID], callbacks, event_callbacks=events, 42 | enable_log=True, restore_states=False, secret_key=APP_SECRET) 43 | loop.run_until_complete(client.connect()) -------------------------------------------------------------------------------- /examples/tv.py: -------------------------------------------------------------------------------- 1 | from sinric import SinricPro, SinricProConstants 2 | import asyncio 3 | 4 | APP_KEY = '' 5 | APP_SECRET = '' 6 | TV_ID = '' 7 | 8 | 9 | def power_state(device_id, state): 10 | print('state : ', state) 11 | return True, state 12 | 13 | 14 | def set_volume(device_id, volume): 15 | print('volume : ', volume) 16 | return True, volume 17 | 18 | 19 | def adjust_volume(device_id, volume): 20 | print('volume : ', volume) 21 | return True, volume 22 | 23 | 24 | def media_control(device_id, control): 25 | print('control : ', control) 26 | return True, control 27 | 28 | 29 | def select_input(device_id, input): 30 | print('input : ', input) 31 | return True, input 32 | 33 | 34 | def change_channel(device_id, channel_name): 35 | print('channel_name : ', channel_name) 36 | return True, channel_name 37 | 38 | 39 | def skip_channels(device_id, channel_count): 40 | print('channel_count : ', channel_count) 41 | return True, channel_count 42 | 43 | 44 | callbacks = { 45 | SinricProConstants.SET_POWER_STATE: power_state, 46 | SinricProConstants.SET_VOLUME: set_volume, 47 | SinricProConstants.ADJUST_VOLUME: adjust_volume, 48 | SinricProConstants.MEDIA_CONTROL: media_control, 49 | SinricProConstants.SELECT_INPUT: select_input, 50 | SinricProConstants.CHANGE_CHANNEL: change_channel, 51 | SinricProConstants.SKIP_CHANNELS: skip_channels 52 | } 53 | 54 | if __name__ == '__main__': 55 | loop = asyncio.get_event_loop() 56 | client = SinricPro(APP_KEY, [TV_ID], callbacks, enable_log=False, 57 | restore_states=False, secret_key=APP_SECRET) 58 | loop.run_until_complete(client.connect()) 59 | 60 | # To update the TV state on server: 61 | # client.event_handler.raise_event(TV_ID, SinricProConstants.SET_VOLUME, data={'volume': 0}) 62 | # client.event_handler.raise_event(TV_ID, SinricProConstants.MEDIA_CONTROL, data={'control': 'FastForward'}) 63 | # client.event_handler.raise_event(TV_ID, SinricProConstants.CHANGE_CHANNEL, data={'name': 'HBO'}) 64 | # client.event_handler.raise_event(TV_ID, SinricProConstants.SELECT_INPUT, data={"input":"HDMI"}) 65 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | from setuptools import setup 8 | import sys 9 | 10 | if sys.version_info < (3, 6): 11 | sys.exit('Sorry, Python < 3.6 or older is not supported') 12 | 13 | VERSION = "2.7.6" 14 | 15 | with open('README.rst', 'r') as f: 16 | long_description = f.read() 17 | setup( 18 | name="sinricpro", 19 | version=VERSION, 20 | author="sinric", 21 | author_email="support@sinric.com", 22 | maintainer='sinric', 23 | maintainer_email='support@sinric.com', 24 | description="A python package for Sinric Pro", 25 | long_description=long_description, 26 | url="https://github.com/sinricpro/python-sdk", 27 | packages=['sinric', "sinric.helpers"], 28 | install_requires=["websockets==10.1", 29 | "loguru", 30 | "importlib_metadata; python_version < '3.8'"], 31 | keywords=['sinric', 'sinric-pro', 'alexa', 'google home', 'smartthings'], 32 | classifiers=[ 33 | "Programming Language :: Python :: 3", 34 | "Operating System :: OS Independent", 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /sinric/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from ._sinricpro import SinricPro 9 | from ._sinricpro_udp import SinricProUdp 10 | from ._events import Events 11 | from . import Events 12 | from ._sinricpro_constants import SinricProConstants -------------------------------------------------------------------------------- /sinric/_brightness_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from sinric.helpers.set_limits import set_limits 9 | 10 | from ._sinricpro_constants import SinricProConstants 11 | 12 | 13 | class BrightnessController: 14 | def __init__(self, brightness_level) -> None: 15 | self.brightness_level = brightness_level 16 | 17 | async def set_brightness(self, jsn, brightness_callback): 18 | self.brightness_level = jsn.get("payload").get( 19 | SinricProConstants.VALUE).get(SinricProConstants.BRIGHTNESS) 20 | return brightness_callback(jsn.get("payload").get(SinricProConstants.DEVICEID), self.brightness_level) 21 | 22 | async def adjust_brightness(self, jsn, brightness_callback): 23 | self.brightness_level += jsn.get("payload").get( 24 | SinricProConstants.VALUE).get(SinricProConstants.BRIGHTNESS_DELTA) 25 | self.brightness_level = set_limits(self.brightness_level) 26 | 27 | return brightness_callback(jsn.get("payload").get(SinricProConstants.DEVICEID), self.brightness_level) 28 | -------------------------------------------------------------------------------- /sinric/_callback_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from re import X 9 | from json import dumps 10 | from time import time 11 | from uuid import uuid4 12 | 13 | from ._power_controller import PowerController 14 | from ._brightness_controller import BrightnessController 15 | from ._power_level_controller import PowerLevelController 16 | from ._color_controller import ColorController 17 | from ._color_temperature import ColorTemperatureController 18 | from ._thermostat_controller import ThermostateMode 19 | from ._range_value_controller import RangeValueController 20 | from ._temperature_controller import TemperatureController 21 | from ._tv_contorller import TvController 22 | from ._speaker_controller import SpeakerController 23 | from ._mode_controller import ModeController 24 | from ._camera_stream_controller import CameraStreamController 25 | from ._lock_controller import LockStateController 26 | from ._signature import Signature 27 | from ._leaky_bucket import LeakyBucket 28 | from ._sinricpro_constants import SinricProConstants 29 | 30 | # noinspection PyBroadException 31 | 32 | 33 | class CallBackHandler(PowerLevelController, PowerController, BrightnessController, ColorController, ColorTemperatureController, 34 | ThermostateMode, RangeValueController, TemperatureController, TvController, SpeakerController, 35 | LockStateController, ModeController, CameraStreamController, Signature): 36 | def __init__(self, callbacks, trace_bool, logger, enable_track=False, secret_key=""): 37 | self.myHmac = None 38 | self.secret_key = secret_key 39 | self.bucket = LeakyBucket(10, 1000, 60000) 40 | self.enable_track = enable_track 41 | 42 | PowerLevelController.__init__(self, 0) 43 | BrightnessController.__init__(self, 0) 44 | PowerController.__init__(self) 45 | RangeValueController.__init__(self, 0) 46 | ColorController.__init__(self) 47 | ThermostateMode.__init__(self) 48 | TemperatureController.__init__(self, 0) 49 | TvController.__init__(self, 0) 50 | LockStateController.__init__(self) 51 | Signature.__init__(self, self.secret_key) 52 | SpeakerController.__init__(self) 53 | ModeController.__init__(self) 54 | ColorTemperatureController.__init__( 55 | self, 0, [2200, 2700, 4000, 5500, 7000]) 56 | CameraStreamController.__init__(self) 57 | 58 | self.callbacks = callbacks 59 | self.logger = logger 60 | self.trace_response = trace_bool 61 | 62 | async def handle_callbacks(self, data_array, connection, udp_client): 63 | jsn = data_array[0] 64 | response_cmd = data_array[1] 65 | message_type = data_array[2] 66 | 67 | async def handle_response(response, connection, udp_client): 68 | if self.trace_response: 69 | self.logger.info(f"Response : {dumps(response)}") 70 | if response_cmd == 'socket_response': 71 | await connection.send(dumps(response)) 72 | elif response_cmd == 'udp_response' and udp_client != None: 73 | udp_client.sendResponse( 74 | dumps(response).encode('ascii'), data_array[2]) 75 | 76 | def json_response(action, resp, data_dict, instance_id='') -> dict: 77 | header = { 78 | "payloadVersion": 2, 79 | "signatureVersion": 1 80 | } 81 | payload = { 82 | "action": action, 83 | "clientId": jsn.get("payload").get("clientId", "alexa-skill"), 84 | "createdAt": int(time()), 85 | "deviceId": jsn.get("payload").get("deviceId", ""), 86 | "message": "OK", 87 | "replyToken": jsn.get("payload", "").get("replyToken", str(uuid4())), 88 | "success": resp, 89 | "type": "response", 90 | "value": data_dict 91 | } 92 | 93 | if instance_id: 94 | payload['instanceId'] = instance_id 95 | 96 | signature = self.get_signature(payload) 97 | 98 | return {"header": header, "payload": payload, "signature": signature} 99 | 100 | if message_type == 'request_response': 101 | assert (self.verify_signature(jsn.get('payload'), 102 | jsn.get("signature").get("HMAC"))) 103 | action = jsn.get('payload').get('action') 104 | 105 | if action == SinricProConstants.SET_POWER_STATE: 106 | await self._handle_set_power_state(connection, udp_client, jsn, handle_response, json_response, action) 107 | 108 | elif action == SinricProConstants.SET_POWER_LEVEL: 109 | await self._handle_set_power_level(connection, udp_client, jsn, handle_response, json_response, action) 110 | 111 | elif action == SinricProConstants.ADJUST_POWER_LEVEL: 112 | await self._handle_adjust_power_level(connection, udp_client, jsn, handle_response, json_response, action) 113 | 114 | elif action == SinricProConstants.SET_BRIGHTNESS: 115 | await self._handle_set_brightness(connection, udp_client, jsn, handle_response, json_response, action) 116 | 117 | elif action == SinricProConstants.ADJUST_BRIGHTNESS: 118 | await self._handle_adjust_brightness(connection, udp_client, jsn, handle_response, json_response, action) 119 | 120 | elif action == SinricProConstants.SET_COLOR: 121 | await self._handle_set_color(connection, udp_client, jsn, handle_response, json_response, action) 122 | 123 | elif action == SinricProConstants.SET_COLOR_TEMPERATURE: 124 | await self._handle_set_color_tempreature(connection, udp_client, jsn, handle_response, json_response, action) 125 | 126 | elif action == SinricProConstants.INCREASE_COLOR_TEMPERATURE: 127 | await self._handle_increase_color_tempreature(connection, udp_client, jsn, handle_response, json_response, action) 128 | 129 | elif action == SinricProConstants.DECREASE_COLOR_TEMPERATURE: 130 | await self._handle_decrease_color_tempreature(connection, udp_client, jsn, handle_response, json_response, action) 131 | 132 | elif action == SinricProConstants.SET_THERMOSTAT_MODE: 133 | await self._handle_set_thermostat_mode(connection, udp_client, jsn, handle_response, json_response, action) 134 | 135 | elif action == SinricProConstants.SET_RANGE_VALUE: 136 | await self._handle_set_range_value(connection, udp_client, jsn, handle_response, json_response, action) 137 | 138 | elif action == SinricProConstants.ADJUST_RANGE_VALUE: 139 | await self._handle_adjust_range_value(connection, udp_client, jsn, handle_response, json_response, action) 140 | 141 | elif action == SinricProConstants.TARGET_TEMPERATURE: 142 | await self._handle_target_tempreature(connection, udp_client, jsn, handle_response, json_response, action) 143 | 144 | elif action == SinricProConstants.ADJUST_TARGET_TEMPERATURE: 145 | await self._handle_adjust_target_tempreature(connection, udp_client, jsn, handle_response, json_response, action) 146 | 147 | elif action == SinricProConstants.SET_VOLUME: 148 | await self._handle_set_volume(connection, udp_client, jsn, handle_response, json_response, action) 149 | 150 | elif action == SinricProConstants.ADJUST_VOLUME: 151 | await self._handle_adjust_volume(connection, udp_client, jsn, handle_response, json_response, action) 152 | 153 | elif action == SinricProConstants.MEDIA_CONTROL: 154 | await self._handle_media_control(connection, udp_client, jsn, handle_response, json_response, action) 155 | 156 | elif action == SinricProConstants.SELECT_INPUT: 157 | await self._handle_select_input(connection, udp_client, jsn, handle_response, json_response, action) 158 | 159 | elif action == SinricProConstants.CHANGE_CHANNEL: 160 | await self._handle_change_channel(connection, udp_client, jsn, handle_response, json_response, action) 161 | 162 | elif action == SinricProConstants.SKIP_CHANNELS: 163 | await self._handle_skip_channel(connection, udp_client, jsn, handle_response, json_response, action) 164 | 165 | elif action == SinricProConstants.SET_MUTE: 166 | await self._handle_set_mute(connection, udp_client, jsn, handle_response, json_response, action) 167 | 168 | elif action == SinricProConstants.SET_BANDS: 169 | await self._handle_set_bands(connection, udp_client, jsn, handle_response, json_response, action) 170 | 171 | elif action == SinricProConstants.ADJUST_BANDS: 172 | await self._handle_adjust_bands(connection, udp_client, jsn, handle_response, json_response, action) 173 | 174 | elif action == SinricProConstants.RESET_BANDS: 175 | await self._handle_reset_bands(connection, udp_client, jsn, handle_response, json_response, action) 176 | 177 | elif action == SinricProConstants.SET_MODE: 178 | await self._handle_set_mode(connection, udp_client, jsn, handle_response, json_response, action) 179 | 180 | elif action == SinricProConstants.SET_LOCK_STATE: 181 | await self._handle_set_lock_state(connection, udp_client, jsn, handle_response, json_response, action) 182 | 183 | elif action == SinricProConstants.GET_WEBRTC_ANSWER: 184 | await self._handle_get_webrtc_answer(connection, udp_client, jsn, handle_response, json_response, action) 185 | 186 | elif action == SinricProConstants.GET_CAMERA_STREAM_URL: 187 | await self._handle_get_camera_stream_url(connection, udp_client, jsn, handle_response, json_response, action) 188 | 189 | # Handle events 190 | 191 | if message_type == 'event': 192 | if response_cmd == SinricProConstants.DOORBELLPRESS: 193 | if self.bucket.add_drop(): 194 | self.logger.info('Sending Doorbell event') 195 | await connection.send(dumps(jsn)) 196 | 197 | elif response_cmd == SinricProConstants.CURRENT_TEMPERATURE: 198 | if self.bucket.add_drop(): 199 | self.logger.info('Sending temperature humidity event') 200 | await connection.send(dumps(jsn)) 201 | 202 | elif response_cmd == SinricProConstants.SET_POWER_STATE: 203 | if self.bucket.add_drop(): 204 | self.logger.info('Sending power state event') 205 | await connection.send(dumps(jsn)) 206 | 207 | elif response_cmd == SinricProConstants.SET_POWER_LEVEL: 208 | if self.bucket.add_drop(): 209 | self.logger.info('Sending power level event') 210 | await connection.send(dumps(jsn)) 211 | 212 | elif response_cmd == SinricProConstants.SET_BRIGHTNESS: 213 | if self.bucket.add_drop(): 214 | self.logger.info('Sending brightness event') 215 | await connection.send(dumps(jsn)) 216 | 217 | elif response_cmd == SinricProConstants.SET_COLOR: 218 | if self.bucket.add_drop(): 219 | self.logger.info('Sending color event') 220 | await connection.send(dumps(jsn)) 221 | 222 | elif response_cmd == SinricProConstants.SET_COLOR_TEMPERATURE: 223 | if self.bucket.add_drop(): 224 | self.logger.info('Sending color temperature event') 225 | await connection.send(dumps(jsn)) 226 | 227 | elif response_cmd == SinricProConstants.SET_THERMOSTAT_MODE: 228 | if self.bucket.add_drop(): 229 | self.logger.info('Sending thermostat mode event') 230 | await connection.send(dumps(jsn)) 231 | 232 | elif response_cmd == SinricProConstants.SET_RANGE_VALUE: 233 | if self.bucket.add_drop(): 234 | self.logger.info('Sending range value event') 235 | await connection.send(dumps(jsn)) 236 | 237 | elif response_cmd == SinricProConstants.MOTION: 238 | if self.bucket.add_drop(): 239 | self.logger.info('Sending motion event') 240 | await connection.send(dumps(jsn)) 241 | 242 | elif response_cmd == SinricProConstants.SET_CONTACT_STATE: 243 | if self.bucket.add_drop(): 244 | self.logger.info('Sending contact event') 245 | await connection.send(dumps(jsn)) 246 | 247 | elif response_cmd == SinricProConstants.SET_VOLUME: 248 | if self.bucket.add_drop(): 249 | self.logger.info('Sending voluming event') 250 | await connection.send(dumps(jsn)) 251 | 252 | elif response_cmd == SinricProConstants.SELECT_INPUT: 253 | if self.bucket.add_drop(): 254 | self.logger.info('Sending select input event') 255 | await connection.send(dumps(jsn)) 256 | 257 | elif response_cmd == SinricProConstants.MEDIA_CONTROL: 258 | if self.bucket.add_drop(): 259 | self.logger.info('Sending media control event') 260 | await connection.send(dumps(jsn)) 261 | 262 | elif response_cmd == SinricProConstants.CHANGE_CHANNEL: 263 | if self.bucket.add_drop(): 264 | self.logger.info('Sending change channel event') 265 | await connection.send(dumps(jsn)) 266 | 267 | elif response_cmd == SinricProConstants.SET_BANDS: 268 | if self.bucket.add_drop(): 269 | self.logger.info('Sending band event') 270 | await connection.send(dumps(jsn)) 271 | 272 | elif response_cmd == SinricProConstants.SET_MODE: 273 | if self.bucket.add_drop(): 274 | self.logger.info('Sending mode event') 275 | await connection.send(dumps(jsn)) 276 | 277 | elif response_cmd == SinricProConstants.SET_LOCK_STATE: 278 | if self.bucket.add_drop(): 279 | self.logger.info('Sending lock event') 280 | await connection.send(dumps(jsn)) 281 | 282 | elif response_cmd == SinricProConstants.RESET_BANDS: 283 | if self.bucket.add_drop(): 284 | self.logger.info('Sending reset bands event') 285 | await connection.send(dumps(jsn)) 286 | 287 | elif response_cmd == SinricProConstants.PUSH_NOTIFICATION: 288 | if self.bucket.add_drop(): 289 | self.logger.info('Sending push notification event') 290 | await connection.send(dumps(jsn)) 291 | 292 | async def _handle_get_camera_stream_url(self, connection, udp_client, jsn, handle_response, json_response, action): 293 | try: 294 | resp, url = await self.get_camera_stream_url(jsn, self.callbacks.get(action)) 295 | response = json_response(action, resp, {"url": url}) 296 | 297 | if resp: 298 | await handle_response(response, connection, udp_client) 299 | except AssertionError: 300 | self.logger.error( 301 | "Signature verification failed for " + jsn.get('payload').get('action')) 302 | except Exception as e: 303 | self.logger.error(str(e)) 304 | 305 | async def _handle_get_webrtc_answer(self, connection, udp_client, jsn, handle_response, json_response, action): 306 | try: 307 | resp, value = await self.get_webrtc_answer(jsn, self.callbacks.get(action)) 308 | response = json_response(action, resp, {"answer": value}) 309 | 310 | if resp: 311 | await handle_response(response, connection, udp_client) 312 | except AssertionError: 313 | self.logger.error( 314 | "Signature verification failed for " + jsn.get('payload').get('action')) 315 | except Exception as e: 316 | self.logger.error(str(e)) 317 | 318 | async def _handle_set_lock_state(self, connection, udp_client, jsn, handle_response, json_response, action): 319 | try: 320 | resp, value = await self.set_lock_state(jsn, self.callbacks.get(action)) 321 | # TODO: Fix this later 322 | response = json_response( 323 | action, resp, {"state": value.upper() + 'ED'}) 324 | 325 | if resp: 326 | await handle_response(response, connection, udp_client) 327 | except AssertionError: 328 | self.logger.error( 329 | "Signature verification failed for " + jsn.get('payload').get('action')) 330 | except Exception as e: 331 | self.logger.error(str(e)) 332 | 333 | async def _handle_set_mode(self, connection, udp_client, jsn, handle_response, json_response, action): 334 | try: 335 | resp, value, instance_id = await self.set_mode(jsn, self.callbacks.get(action)) 336 | response = json_response(action, resp, { 337 | "mode": value 338 | }, instance_id) 339 | 340 | if resp: 341 | await handle_response(response, connection, udp_client) 342 | except AssertionError: 343 | self.logger.error( 344 | "Signature verification failed for " + jsn.get('payload').get('action')) 345 | except Exception as e: 346 | self.logger.error(str(e)) 347 | 348 | async def _handle_reset_bands(self, connection, udp_client, jsn, handle_response, json_response, action): 349 | try: 350 | resp = await self.reset_bands(jsn, self.callbacks.get(action)) 351 | response = json_response(action, resp, { 352 | "bands": [ 353 | { 354 | "name": "BASS", 355 | "level": 0 356 | }, 357 | { 358 | "name": "MIDRANGE", 359 | "level": 0 360 | }, 361 | { 362 | "name": "TREBLE", 363 | "level": 0 364 | }] 365 | }) 366 | if resp: 367 | await handle_response(response, connection, udp_client) 368 | except AssertionError: 369 | self.logger.error( 370 | "Signature verification failed for " + jsn.get('payload').get('action')) 371 | except Exception as e: 372 | self.logger.error(str(e)) 373 | 374 | async def _handle_adjust_bands(self, connection, udp_client, jsn, handle_response, json_response, action): 375 | try: 376 | resp, value = await self.adjust_bands(jsn, self.callbacks.get(action)) 377 | response = json_response(action, resp, { 378 | "bands": [ 379 | { 380 | "name": value.get('name'), 381 | "level": value.get('level') 382 | } 383 | ] 384 | }) 385 | 386 | if resp: 387 | await handle_response(response, connection, udp_client) 388 | except AssertionError: 389 | self.logger.error( 390 | "Signature verification failed for " + jsn.get('payload').get('action')) 391 | except Exception as e: 392 | self.logger.error(str(e)) 393 | 394 | async def _handle_set_bands(self, connection, udp_client, jsn, handle_response, json_response, action): 395 | try: 396 | resp, value = await self.set_bands(jsn, self.callbacks.get(action)) 397 | response = json_response(action, resp, { 398 | "bands": [ 399 | { 400 | "name": value.get('name'), 401 | "level": value.get('level') 402 | } 403 | ] 404 | }) 405 | 406 | if resp: 407 | await handle_response(response, connection, udp_client) 408 | except AssertionError: 409 | self.logger.error( 410 | "Signature verification failed for " + jsn.get('payload').get('action')) 411 | except Exception as e: 412 | self.logger.error(str(e)) 413 | 414 | async def _handle_set_mute(self, connection, udp_client, jsn, handle_response, json_response, action): 415 | try: 416 | resp, value = await self.set_mute(jsn, self.callbacks.get(action)) 417 | response = json_response(action, resp, {"mute": value}) 418 | 419 | if resp: 420 | await handle_response(response, connection, udp_client) 421 | except AssertionError: 422 | self.logger.error( 423 | "Signature verification failed for " + jsn.get('payload').get('action')) 424 | except Exception as e: 425 | self.logger.error(str(e)) 426 | 427 | async def handle_skip_channel(self, connection, udp_client, jsn, handle_response, json_response, action): 428 | try: 429 | resp, value = await self.skip_channels(jsn, self.callbacks.get(action)) 430 | response = json_response( 431 | action, resp, {"channel": {"name": value}}) 432 | 433 | if resp: 434 | await handle_response(response, connection, udp_client) 435 | except AssertionError: 436 | self.logger.error( 437 | "Signature verification failed for " + jsn.get('payload').get('action')) 438 | except Exception as e: 439 | self.logger.error(str(e)) 440 | 441 | async def _handle_change_channel(self, connection, udp_client, jsn, handle_response, json_response, action): 442 | try: 443 | resp, value = await self.change_channel(jsn, self.callbacks.get(action)) 444 | response = json_response( 445 | action, resp, {"channel": {"name": value}}) 446 | if resp: 447 | await handle_response(response, connection, udp_client) 448 | except AssertionError: 449 | self.logger.error( 450 | "Signature verification failed for " + jsn.get('payload').get('action')) 451 | except Exception as e: 452 | self.logger.error(str(e)) 453 | 454 | async def _handle_select_input(self, connection, udp_client, jsn, handle_response, json_response, action): 455 | try: 456 | resp, value = await self.select_input(jsn, self.callbacks.get(action)) 457 | response = json_response(action, resp, {"input": value}) 458 | if resp: 459 | await handle_response(response, connection, udp_client) 460 | except AssertionError: 461 | self.logger.error( 462 | "Signature verification failed for " + jsn.get('payload').get('action')) 463 | except Exception as e: 464 | self.logger.error(str(e)) 465 | 466 | async def _handle_media_control(self, connection, udp_client, jsn, handle_response, json_response, action): 467 | try: 468 | resp, value = await self.media_control(jsn, self.callbacks.get(action)) 469 | response = json_response(action, resp, {"control": value}) 470 | if resp: 471 | await handle_response(response, connection, udp_client) 472 | except AssertionError: 473 | self.logger.error( 474 | "Signature verification failed for " + jsn.get('payload').get('action')) 475 | except Exception as e: 476 | self.logger.error(str(e)) 477 | 478 | async def _handle_adjust_volume(self, connection, udp_client, jsn, handle_response, json_response, action): 479 | try: 480 | resp, value = await self.adjust_volume(jsn, self.callbacks.get(action)) 481 | response = json_response(action, resp, {"volume": value}) 482 | 483 | if resp: 484 | await handle_response(response, connection, udp_client) 485 | except AssertionError: 486 | self.logger.error( 487 | "Signature verification failed for " + jsn.get('payload').get('action')) 488 | except Exception as e: 489 | self.logger.error(str(e)) 490 | 491 | async def _handle_set_volume(self, connection, udp_client, jsn, handle_response, json_response, action): 492 | try: 493 | resp, value = await self.set_volume(jsn, self.callbacks.get(action)) 494 | response = json_response(action, resp, {"volume": value}) 495 | 496 | if resp: 497 | await handle_response(response, connection, udp_client) 498 | except AssertionError: 499 | self.logger.error( 500 | "Signature verification failed for " + jsn.get('payload').get('action')) 501 | except Exception as e: 502 | self.logger.error(str(e)) 503 | 504 | async def _handle_adjust_target_tempreature(self, connection, udp_client, jsn, handle_response, json_response, action): 505 | try: 506 | resp, value = await self.adjust_temperature(jsn, self.callbacks.get(action)) 507 | response = json_response(action, resp, {"temperature": value}) 508 | if resp: 509 | await handle_response(response, connection, udp_client) 510 | except AssertionError: 511 | self.logger.error( 512 | "Signature verification failed for " + jsn.get('payload').get('action')) 513 | except Exception as e: 514 | self.logger.error(str(e)) 515 | 516 | async def _handle_target_tempreature(self, connection, udp_client, jsn, handle_response, json_response, action): 517 | try: 518 | resp, value = await self.target_temperature(jsn, self.callbacks.get(action)) 519 | response = json_response(action, resp, {"temperature": value}) 520 | 521 | if resp: 522 | await handle_response(response, connection, udp_client) 523 | except AssertionError: 524 | self.logger.error( 525 | "Signature verification failed for " + jsn.get('payload').get('action')) 526 | except Exception as e: 527 | self.logger.error(str(e)) 528 | 529 | async def _handle_adjust_range_value(self, connection, udp_client, jsn, handle_response, json_response, action): 530 | try: 531 | resp, value = await self.adjust_range_value(jsn, self.callbacks.get('adjustRangeValue')) 532 | response = json_response(action, resp, {"rangeValue": value}) 533 | if resp: 534 | await handle_response(response, connection, udp_client) 535 | except AssertionError: 536 | self.logger.error( 537 | "Signature verification failed for " + jsn.get('payload').get('action')) 538 | except Exception as e: 539 | self.logger.error(str(e)) 540 | 541 | async def _handle_set_range_value(self, connection, udp_client, jsn, handle_response, json_response, action): 542 | try: 543 | resp, value, instance_id = await self.set_range_value(jsn, self.callbacks.get(action)) 544 | response = json_response( 545 | action, resp, {"rangeValue": value}, instance_id) 546 | if resp: 547 | await handle_response(response, connection, udp_client) 548 | except AssertionError: 549 | self.logger.error( 550 | "Signature verification failed for " + jsn.get('payload').get('action')) 551 | except Exception as e: 552 | self.logger.error(str(e)) 553 | 554 | async def _handle_set_thermostat_mode(self, connection, udp_client, jsn, handle_response, json_response, action): 555 | try: 556 | resp, value = await self.set_thermostate_mode(jsn, self.callbacks[action]) 557 | response = json_response(action, resp, {"thermostatMode": value}) 558 | 559 | if resp: 560 | await handle_response(response, connection, udp_client) 561 | except Exception as e: 562 | self.logger.error(f'Error : {e}') 563 | 564 | async def _handle_decrease_color_tempreature(self, connection, udp_client, jsn, handle_response, json_response, action): 565 | try: 566 | resp, value = await self.decrease_color_temperature(jsn, self.callbacks[action]) 567 | response = json_response(action, resp, {"colorTemperature": value}) 568 | 569 | if resp: 570 | await handle_response(response, connection, udp_client) 571 | except AssertionError: 572 | self.logger.error( 573 | "Signature verification failed for " + jsn.get('payload').get('action')) 574 | except Exception as e: 575 | self.logger.error(f'Error : {e}') 576 | 577 | async def _handle_increase_color_tempreature(self, connection, udp_client, jsn, handle_response, json_response, action): 578 | try: 579 | resp, value = await self.increase_color_temperature(jsn, self.callbacks[action]) 580 | response = json_response(action, resp, {"colorTemperature": value}) 581 | 582 | if resp: 583 | await handle_response(response, connection, udp_client) 584 | except AssertionError: 585 | self.logger.error( 586 | "Signature verification failed for " + jsn.get('payload').get('action')) 587 | except Exception as e: 588 | self.logger.error(f'Error : {e}') 589 | 590 | async def _handle_set_color_tempreature(self, connection, udp_client, jsn, handle_response, json_response, action): 591 | try: 592 | resp = await self.set_color_temperature(jsn, self.callbacks[action]) 593 | response = json_response(action, resp, {"colorTemperature": jsn.get( 594 | "payload").get("value").get("colorTemperature")}) 595 | 596 | if resp: 597 | await handle_response(response, connection, udp_client) 598 | except AssertionError: 599 | self.logger.error( 600 | "Signature verification failed for " + jsn.get('payload').get('action')) 601 | except Exception as e: 602 | self.logger.error(f'Error : {e}') 603 | 604 | async def _handle_set_color(self, connection, udp_client, jsn, handle_response, json_response, action): 605 | try: 606 | resp = await self.set_color(jsn, self.callbacks[action]) 607 | response = json_response(action=action, resp=resp, data_dict={ 608 | "color": { 609 | "b": jsn.get("payload").get("value").get("color").get("b"), 610 | "g": jsn.get("payload").get("value").get("color").get("g"), 611 | "r": jsn.get("payload").get("value").get("color").get("r") 612 | } 613 | }) 614 | 615 | if resp: 616 | await handle_response(response, connection, udp_client) 617 | except AssertionError: 618 | self.logger.error( 619 | "Signature verification failed for " + jsn.get('payload').get('action')) 620 | except Exception as e: 621 | self.logger.error(f'Error : {e}') 622 | 623 | async def _handle_adjust_brightness(self, connection, udp_client, jsn, handle_response, json_response, action): 624 | try: 625 | resp, value = await self.adjust_brightness(jsn, self.callbacks[action]) 626 | response = json_response(action, resp, {"brightness": value}) 627 | 628 | if resp: 629 | await handle_response(response, connection, udp_client) 630 | except AssertionError: 631 | self.logger.error( 632 | "Signature verification failed for " + jsn.get('payload').get('action')) 633 | except Exception as e: 634 | self.logger.error(f'Error : {e}') 635 | 636 | async def _handle_set_brightness(self, connection, udp_client, jsn, handle_response, json_response, action): 637 | try: 638 | resp, value = await self.set_brightness(jsn, self.callbacks[action]) 639 | response = json_response(action, resp, {"brightness": value}) 640 | 641 | if resp: 642 | await handle_response(response, connection, udp_client) 643 | except AssertionError: 644 | self.logger.error( 645 | "Signature verification failed for " + jsn.get('payload').get('action')) 646 | except Exception as e: 647 | self.logger.error(f'Error : {e}') 648 | 649 | async def _handle_adjust_power_level(self, connection, udp_client, jsn, handle_response, json_response, action): 650 | try: 651 | resp, value = await self.adjust_power_level(jsn, self.callbacks[action]) 652 | response = json_response(action, resp, {"powerLevel": value}) 653 | 654 | if resp: 655 | await handle_response(response, connection, udp_client) 656 | except AssertionError: 657 | self.logger.error( 658 | "Signature verification failed for " + jsn.get('payload').get('action')) 659 | except Exception as e: 660 | self.logger.error(f'Error : {e}') 661 | 662 | async def _handle_set_power_level(self, connection, udp_client, jsn, handle_response, json_response, action): 663 | try: 664 | resp, value = await self.set_power_level(jsn, self.callbacks[action]) 665 | response = json_response(action, resp, {"powerLevel": value}) 666 | 667 | if resp: 668 | await handle_response(response, connection, udp_client) 669 | except AssertionError: 670 | self.logger.error( 671 | "Signature verification failed for " + jsn.get('payload').get('action')) 672 | except Exception as e: 673 | self.logger.error(str(e)) 674 | 675 | async def _handle_set_power_state(self, connection, udp_client, jsn, handle_response, json_response, action): 676 | try: 677 | resp, state = await self.power_state(jsn, self.callbacks[action]) 678 | response = json_response(action, resp, {"state": state}) 679 | if resp: 680 | await handle_response(response, connection, udp_client) 681 | except AssertionError: 682 | self.logger.error( 683 | "Signature verification failed for " + jsn.get('payload').get('action')) 684 | except Exception as e: 685 | self.logger.error(f'Error : {e}') 686 | 687 | # else: 688 | # self.logger.info(response_cmd + ' not found!') 689 | -------------------------------------------------------------------------------- /sinric/_camera_stream_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from ._sinricpro_constants import SinricProConstants 9 | 10 | class CameraStreamController: 11 | 12 | async def get_webrtc_answer(self, jsn, get_webrtc_answer_callback): 13 | self.offer = jsn.get("payload").get("value").get("offer") 14 | 15 | return get_webrtc_answer_callback(jsn.get("payload").get(SinricProConstants.DEVICEID), 16 | self.offer) 17 | 18 | async def get_camera_stream_url(self, jsn, get_camera_stream_url_callback): 19 | self.protocol = jsn.get("payload").get("value").get("protocol") 20 | 21 | return get_camera_stream_url_callback(jsn.get("payload").get(SinricProConstants.DEVICEID), 22 | self.protocol) 23 | -------------------------------------------------------------------------------- /sinric/_color_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | 9 | class ColorController: 10 | 11 | async def set_color(self, jsn, set_color_callback): 12 | return set_color_callback(jsn.get("payload").get("deviceId"), 13 | jsn.get("payload").get("value").get("color").get("r"), 14 | jsn.get("payload").get("value").get("color").get("g"), 15 | jsn.get("payload").get("value").get("color").get("b")) 16 | -------------------------------------------------------------------------------- /sinric/_color_temperature.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | 9 | class ColorTemperatureController: 10 | def __init__(self, temperatures_index, temperatures_array): 11 | self.temperatures_index = temperatures_index 12 | self.temperatures_array = temperatures_array 13 | 14 | async def set_color_temperature(self, jsn, set_callback): 15 | return set_callback(jsn.get("payload").get("deviceId"), 16 | jsn.get("payload").get("value").get("colorTemperature")) 17 | 18 | async def increase_color_temperature(self, jsn, increase_callback): 19 | return increase_callback(jsn.get("payload").get("deviceId"), jsn.get("payload").get("value")) 20 | 21 | async def decrease_color_temperature(self, jsn, decrease_callback): 22 | return decrease_callback(jsn.get("payload").get("deviceId"), jsn.get("payload").get("value")) 23 | -------------------------------------------------------------------------------- /sinric/_events.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from time import time, sleep 9 | from ._queues import queue 10 | import uuid 11 | from ._signature import Signature 12 | from ._sinricpro_constants import SinricProConstants 13 | 14 | # noinspection PyBroadException 15 | class Events(Signature): 16 | def __init__(self, connection, logger=None, secret_key=""): 17 | self.connection = connection 18 | self.logger = logger 19 | self.secret_key = secret_key 20 | Signature.__init__(self, self.secret_key) 21 | 22 | # noinspection PyBroadException 23 | def raise_event(self, device_id, event_name, data=None): 24 | if data is None: 25 | data = {} 26 | try: 27 | def json_response(action, device_id, value, type_of_interaction="PHYSICAL_INTERACTION") -> dict: 28 | header = { 29 | "payloadVersion": 2, 30 | "signatureVersion": 1 31 | } 32 | 33 | payload = { 34 | "action": action, 35 | "cause": { 36 | "type": type_of_interaction 37 | }, 38 | "createdAt": int(time()), 39 | "deviceId": device_id, 40 | "replyToken": str(uuid.uuid4()), 41 | "type": "event", 42 | "value": value 43 | } 44 | 45 | signature = self.get_signature(payload) 46 | return {"header": header, "payload": payload, "signature": signature} 47 | 48 | if event_name == SinricProConstants.SET_POWER_STATE: 49 | response = json_response(event_name, device_id, {SinricProConstants.STATE: data.get("state", "Off")}) 50 | queue.put([response, event_name, 'event']) 51 | 52 | elif event_name == SinricProConstants.SET_POWER_LEVEL: 53 | response = json_response(event_name, device_id, { SinricProConstants.POWER_LEVEL: data.get(SinricProConstants.POWER_LEVEL)}) 54 | queue.put([response, event_name, 'event']) 55 | 56 | elif event_name == SinricProConstants.SET_BRIGHTNESS: 57 | response = json_response(event_name, device_id, { SinricProConstants.BRIGHTNESS : data.get(SinricProConstants.BRIGHTNESS) }) 58 | queue.put([response, event_name, 'event']) 59 | 60 | elif event_name == SinricProConstants.SET_COLOR: 61 | response = json_response(event_name, device_id, { 62 | "color": { 63 | "r": data.get('r'), 64 | "g": data.get('g'), 65 | "b": data.get('b') 66 | } 67 | }) 68 | queue.put([response, event_name, 'event']) 69 | 70 | elif event_name == SinricProConstants.SET_COLOR_TEMPERATURE: 71 | response = json_response(event_name, device_id, { SinricProConstants.COLOR_TEMPERATURE: 2400 }) 72 | queue.put([response, event_name, 'event']) 73 | 74 | elif event_name == SinricProConstants.DOORBELLPRESS: 75 | response = json_response(event_name, device_id, { "state": "pressed" }) 76 | queue.put([response, event_name, 'event']) 77 | 78 | elif event_name == SinricProConstants.CURRENT_TEMPERATURE: 79 | response = json_response(event_name, device_id, { 80 | SinricProConstants.TEMPERATURE : round(data.get(SinricProConstants.TEMPERATURE), 1), 81 | SinricProConstants.HUMIDITY : round(data.get(SinricProConstants.HUMIDITY), 1), 82 | }, type_of_interaction = "PERIODIC_POLL") 83 | queue.put([response, event_name, 'event']) 84 | 85 | elif event_name == SinricProConstants.PUSH_NOTIFICATION: 86 | response = json_response(event_name, device_id, { 87 | "alert": data.get('alert'), 88 | }) 89 | queue.put([response, event_name, 'event']) 90 | 91 | elif event_name == SinricProConstants.SET_THERMOSTAT_MODE: 92 | response = json_response(event_name, device_id, { 93 | SinricProConstants.THERMOSTATMODE: data.get(SinricProConstants.MODE) 94 | }) 95 | queue.put([response, event_name, 'event']) 96 | 97 | elif event_name == SinricProConstants.SET_RANGE_VALUE: 98 | response = json_response(event_name, device_id, { 99 | SinricProConstants.RANGE_VALUE: data.get(SinricProConstants.RANGE_VALUE) 100 | }) 101 | queue.put([response, event_name, 'event']) 102 | 103 | elif event_name == SinricProConstants.MOTION: 104 | response = json_response(SinricProConstants.MOTION, device_id, { 105 | SinricProConstants.STATE: data.get(SinricProConstants.STATE) 106 | }) 107 | queue.put([response, event_name, 'event']) 108 | 109 | elif event_name == SinricProConstants.SET_CONTACT_STATE or event_name == SinricProConstants.SET_LOCK_STATE: 110 | response = json_response(event_name, device_id, { 111 | SinricProConstants.STATE: data.get(SinricProConstants.STATE) 112 | }) 113 | queue.put([response, event_name, 'event']) 114 | 115 | elif event_name == SinricProConstants.SET_VOLUME: 116 | response = json_response(event_name, device_id, { 117 | SinricProConstants.VOLUME : data.get(SinricProConstants.VOLUME) 118 | }) 119 | queue.put([response, event_name, 'event']) 120 | 121 | elif event_name == SinricProConstants.SELECT_INPUT: 122 | response = json_response(event_name, device_id, { 123 | SinricProConstants.INPUT: data.get(SinricProConstants.INPUT) 124 | }) 125 | queue.put([response, event_name, 'event']) 126 | 127 | elif event_name == SinricProConstants.MEDIA_CONTROL: 128 | response = json_response(event_name, device_id, { 129 | SinricProConstants.CONTROL: data.get(SinricProConstants.CONTROL) 130 | }) 131 | queue.put([response, event_name, 'event']) 132 | 133 | elif event_name == SinricProConstants.CHANGE_CHANNEL: 134 | response = json_response(event_name, device_id, { 135 | "channel": { 136 | "name": data.get('name') 137 | } 138 | }) 139 | queue.put([response, event_name, 'event']) 140 | 141 | elif event_name == SinricProConstants.SET_BANDS: 142 | response = json_response(event_name, device_id, { 143 | "bands": [ 144 | { 145 | "name": data.get('name'), 146 | "level": data.get('level') 147 | } 148 | ] 149 | }) 150 | queue.put([response, event_name, 'event']) 151 | 152 | elif event_name == SinricProConstants.SET_MODE: 153 | response = json_response(event_name, device_id, { 154 | SinricProConstants.MODE: data.get(SinricProConstants.MODE) 155 | }) 156 | queue.put([response, event_name, 'event']) 157 | 158 | elif event_name == SinricProConstants.RESET_BANDS: 159 | response = json_response(event_name, device_id, { 160 | "bands": [ 161 | { 162 | "name": "BASS", 163 | "level": 0 164 | }, 165 | { 166 | "name": "MIDRANGE", 167 | "level": 0 168 | }, 169 | { 170 | "name": "TREBLE", 171 | "level": 0 172 | }] 173 | }) 174 | queue.put([response, event_name, 'event']) 175 | 176 | elif event_name == SinricProConstants.SET_MUTE: 177 | response = json_response(event_name, device_id, { 178 | SinricProConstants.MUTE: data.get(SinricProConstants.MUTE, False) 179 | }) 180 | queue.put([response, event_name, 'event']) 181 | 182 | else: 183 | self.logger.exception('Event :' + event_name + ' not found!') 184 | 185 | except Exception: 186 | self.logger.exception('Error Occurred') 187 | -------------------------------------------------------------------------------- /sinric/_leaky_bucket.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class LeakyBucket: 4 | def __init__(self, bucket_size, drop_in_time, drop_out_time): 5 | self.bucket_size = bucket_size 6 | self.drop_in_time = drop_in_time 7 | self.drop_out_time = drop_out_time 8 | self.drop_in_bucket = 0 9 | self.last_drop = 0 10 | 11 | def millis(self): 12 | return int(round(time.time() * 1000)) 13 | 14 | def leak_drops(self): 15 | actual_millis = self.millis() 16 | drops_to_leak = round((actual_millis - self.last_drop) / self.drop_out_time) 17 | if drops_to_leak > 0: 18 | if self.drop_in_bucket <= drops_to_leak: 19 | self.drop_in_bucket = 0 20 | else: 21 | self.drop_in_bucket = self.drop_in_bucket - drops_to_leak 22 | 23 | def add_drop(self): 24 | self.leak_drops() 25 | actual_millis = self.millis() 26 | if (self.drop_in_bucket < self.bucket_size) and ( 27 | (actual_millis - self.last_drop) > (self.drop_in_bucket + self.drop_in_time)): 28 | self.drop_in_bucket = self.drop_in_bucket + 1 29 | self.last_drop = actual_millis 30 | return True 31 | if self.drop_in_bucket >= self.bucket_size: 32 | return False 33 | return False -------------------------------------------------------------------------------- /sinric/_lock_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinricinricinricinricinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from ._sinricpro_constants import SinricProConstants 9 | 10 | class LockStateController: 11 | def __init__(self): 12 | self.lock_state = '' 13 | 14 | async def set_lock_state(self, jsn, callback): 15 | return callback(jsn.get(SinricProConstants.DEVICEID), jsn.get("payload").get('value', False).get(SinricProConstants.STATE, "unlock")) 16 | -------------------------------------------------------------------------------- /sinric/_mode_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinricinricinricinricinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from ._sinricpro_constants import SinricProConstants 9 | 10 | 11 | class ModeController: 12 | def __init__(self): 13 | self.instance_id = '' 14 | 15 | async def set_mode(self, jsn, callback): 16 | self.instance_id = jsn.get("payload").get(SinricProConstants.INSTANCE_ID, '') 17 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get('value').get('mode'), self.instance_id) 18 | -------------------------------------------------------------------------------- /sinric/_power_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinricinricinricinricinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from ._sinricpro_constants import SinricProConstants 9 | 10 | 11 | class PowerController: 12 | 13 | async def power_state(self, jsn, power_state_callback): 14 | return power_state_callback(jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get("value").get("state")) 15 | -------------------------------------------------------------------------------- /sinric/_power_level_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from sinric.helpers.set_limits import set_limits 9 | from ._sinricpro_constants import SinricProConstants 10 | 11 | 12 | class PowerLevelController: 13 | def __init__(self, power_level) -> None: 14 | self.power_level = power_level 15 | 16 | async def set_power_level(self, jsn, power_level_callback): 17 | self.power_level = jsn.get("payload").get( 18 | SinricProConstants.VALUE).get(SinricProConstants.POWER_LEVEL) 19 | return power_level_callback(jsn.get("payload").get(SinricProConstants.DEVICEID), 20 | self.power_level) 21 | 22 | async def adjust_power_level(self, jsn, adjust_power_level_cb): 23 | self.power_level += jsn[SinricProConstants.VALUE][SinricProConstants.POWER_LEVEL_DELTA] 24 | self.power_level = set_limits(self.power_level) 25 | return adjust_power_level_cb(jsn[SinricProConstants.DEVICEID], 26 | self.power_level) 27 | -------------------------------------------------------------------------------- /sinric/_queues.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinricinricinricinricinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from queue import Queue 9 | 10 | queue = Queue() -------------------------------------------------------------------------------- /sinric/_range_value_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from sinric.helpers.set_limits import set_limits 9 | from ._sinricpro_constants import SinricProConstants 10 | 11 | 12 | class RangeValueController: 13 | def __init__(self, range_value): 14 | self.range_value = range_value 15 | self.instance_id = '' 16 | 17 | async def set_range_value(self, jsn, range_callback): 18 | self.range_value = jsn.get("payload").get( 19 | SinricProConstants.VALUE).get(SinricProConstants.RANGE_VALUE) 20 | self.instance_id = jsn.get("payload").get( 21 | SinricProConstants.INSTANCE_ID, '') 22 | return range_callback(jsn.get("payload").get(SinricProConstants.DEVICEID), self.range_value, self.instance_id) 23 | 24 | async def adjust_range_value(self, jsn, callback): 25 | self.range_value += jsn.get("payload").get( 26 | SinricProConstants.VALUE).get(SinricProConstants.RANGE_VALUE) 27 | self.range_value = set_limits(self.range_value) 28 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), self.range_value) 29 | -------------------------------------------------------------------------------- /sinric/_signature.py: -------------------------------------------------------------------------------- 1 | from json import dumps 2 | from base64 import b64encode 3 | import hmac as sinricHmac 4 | from hashlib import sha256 5 | 6 | 7 | class Signature: 8 | def __init__(self, secret_key): 9 | self.secret_key = secret_key 10 | 11 | def verify_signature(self, payload, signature) -> bool: 12 | self.hmac = sinricHmac.new(self.secret_key.encode('utf-8'), 13 | dumps(payload, separators=(',', ':')).encode('utf-8'), sha256) 14 | return b64encode(self.hmac.digest()).decode('utf-8') == signature 15 | 16 | def get_signature(self, payload) -> dict: 17 | reply_hmac = sinricHmac.new(self.secret_key.encode('utf-8'), 18 | dumps(payload, separators=(',', ':')).encode('utf-8'), sha256) 19 | 20 | encoded_hmac = b64encode(reply_hmac.digest()) 21 | 22 | return { 23 | "HMAC": encoded_hmac.decode('utf-8') 24 | } 25 | -------------------------------------------------------------------------------- /sinric/_sinricpro.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | import asyncio 9 | import re 10 | import sys 11 | 12 | from loguru import logger 13 | from ._sinricpro_websocket import SinricProSocket 14 | from ._events import Events 15 | from typing import ( 16 | Any, 17 | Awaitable, 18 | Callable, 19 | Iterable as TypingIterable, 20 | List, 21 | Optional, 22 | Set, 23 | Type, 24 | Union, 25 | cast, 26 | ) 27 | 28 | # logger.add("{}.log".format("sinricpro_logfile"), rotation="10 MB") 29 | 30 | 31 | class SinricPro: 32 | def __init__(self, api, device_id, request_callbacks, event_callbacks=None, enable_log=False, restore_states=False, 33 | secret_key="", loop_delay=0.5): 34 | try: 35 | assert (self.verify_device_ids(device_id)) 36 | self.restore_states = restore_states 37 | self.app_key = api 38 | self.loop_delay = loop_delay if loop_delay > 0 else 0.5 39 | self.secret_key = secret_key 40 | self.device_id = device_id 41 | self.logger = logger 42 | self.request_callbacks = request_callbacks 43 | self.socket = SinricProSocket(self.app_key, self.device_id, self.request_callbacks, enable_log, self.logger, 44 | self.restore_states, self.secret_key, loop_delay=loop_delay) 45 | self.connection = None 46 | self.event_callbacks = event_callbacks 47 | self.event_handler = Events( 48 | self.connection, self.logger, self.secret_key) 49 | 50 | except AssertionError as e: 51 | logger.error("Device Id verification failed") 52 | sys.exit(0) 53 | 54 | def verify_device_ids(self, device_id_arr): 55 | arr = device_id_arr 56 | for i in arr: 57 | res = re.findall(r'^[a-fA-F0-9]{24}$', i) 58 | if len(res) == 0: 59 | return False 60 | return True 61 | 62 | async def connect(self, udp_client=None, sleep=0): 63 | try: 64 | self.connection = await self.socket.connect() 65 | receive_message_task = asyncio.create_task(self.socket.receive_message(connection=self.connection)) 66 | handle_queue_task = asyncio.create_task(self.socket.handle_queue()) 67 | 68 | if self.event_callbacks is not None: 69 | handle_event_queue_task = asyncio.create_task( 70 | self.event_callbacks()) 71 | await handle_event_queue_task 72 | 73 | await handle_queue_task 74 | await receive_message_task 75 | 76 | except KeyboardInterrupt: 77 | self.logger.error('Keyboard interrupt') 78 | except Exception as e: 79 | self.logger.error(e) 80 | 81 | # async def run_app(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: 82 | # """Run an app locally""" 83 | 84 | # if loop is None: 85 | # loop = asyncio.new_event_loop() 86 | 87 | # #loop.set_debug(debug) 88 | # self.connection = await self.socket.connect() 89 | 90 | # receive_message_task = loop.create_task(self.socket.receive_message(connection=self.connection)) 91 | # handle_queue_task = loop.create_task(self.socket.handle_queue()) 92 | # #handle_event_queue_task = asyncio.create_task(self.event_callbacks()) 93 | 94 | # try: 95 | # asyncio.set_event_loop(loop) 96 | # loop.run_until_complete([receive_message_task, handle_queue_task]) 97 | # except (KeyboardInterrupt): # pragma: no cover GracefulExit 98 | # pass 99 | # finally: 100 | # #_cancel_tasks({main_task}, loop) 101 | # #_cancel_tasks(asyncio.all_tasks(loop), loop) 102 | # loop.run_until_complete(loop.shutdown_asyncgens()) 103 | # loop.close() 104 | # asyncio.set_event_loop(None) -------------------------------------------------------------------------------- /sinric/_sinricpro_constants.py: -------------------------------------------------------------------------------- 1 | class SinricProConstants(object): 2 | 3 | # actions 4 | DOORBELLPRESS = 'DoorbellPress' 5 | CURRENT_TEMPERATURE = 'currentTemperature' 6 | SET_POWER_STATE = 'setPowerState' 7 | SET_POWER_LEVEL = 'setPowerLevel' 8 | ADJUST_POWER_LEVEL = 'adjustPowerLevel' 9 | SET_BRIGHTNESS = 'setBrightness' 10 | ADJUST_BRIGHTNESS = 'adjustBrightness' 11 | SET_COLOR = 'setColor' 12 | SET_COLOR_TEMPERATURE = 'setColorTemperature' 13 | INCREASE_COLOR_TEMPERATURE = 'increaseColorTemperature' 14 | DECREASE_COLOR_TEMPERATURE = 'decreaseColorTemperature' 15 | SET_THERMOSTAT_MODE = 'setThermostatMode' 16 | SET_RANGE_VALUE = 'setRangeValue' 17 | ADJUST_RANGE_VALUE = 'adjustRangeValue' 18 | TARGET_TEMPERATURE = 'targetTemperature' 19 | ADJUST_TARGET_TEMPERATURE = 'adjustTargetTemperature' 20 | SET_VOLUME = 'setVolume' 21 | ADJUST_VOLUME = 'adjustVolume' 22 | MEDIA_CONTROL = 'mediaControl' 23 | SELECT_INPUT = 'selectInput' 24 | CHANGE_CHANNEL = 'changeChannel' 25 | SKIP_CHANNELS = 'skipChannels' 26 | SET_MUTE = 'setMute' 27 | SET_BANDS = 'setBands' 28 | ADJUST_BANDS = 'adjustBands' 29 | RESET_BANDS = 'resetBands' 30 | SET_MODE = 'setMode' 31 | SET_LOCK_STATE = 'setLockState' 32 | GET_WEBRTC_ANSWER = 'getWebRTCAnswer' 33 | GET_CAMERA_STREAM_URL = 'getCameraStreamUrl' 34 | PUSH_NOTIFICATION = 'pushNotification' 35 | MOTION = 'motion' 36 | SET_CONTACT_STATE = 'setContactState' 37 | 38 | # payload attributes 39 | POWER_LEVEL = 'powerLevel' 40 | POWER_LEVEL_DELTA = 'powerLevelDelta' 41 | BRIGHTNESS = 'brightness' 42 | BRIGHTNESS_DELTA = 'brightnessDelta' 43 | COLOR = 'color' 44 | COLOR_TEMPERATURE = 'colorTemperature' 45 | POWER_STATE = 'powerState' 46 | STATE = 'state' 47 | HUMIDITY = 'humidity' 48 | TEMPERATURE = 'temperature' 49 | 50 | # message attributes 51 | DEVICEID = 'deviceId' 52 | INSTANCE_ID = 'instanceId' 53 | VALUE = 'value' 54 | RANGE_VALUE = 'rangeValue' 55 | LEVEL_DELTA = 'levelDelta' 56 | LEVELDIRECTION = 'levelDirection' 57 | LEVEL = 'level' 58 | NAME = 'name' 59 | BANDS = 'bands' 60 | THERMOSTATMODE = 'thermostatMode' 61 | MODE = 'mode' 62 | # values 63 | LOCK_STATE_LOCKED = 'LOCKED' 64 | LOCK_STATE_UNLOCKED = 'UNLOCKED' 65 | 66 | POWER_STATE_ON = 'On' 67 | POWER_STATE_OFF = 'Off' 68 | 69 | THERMOSTAT_MODE_COOL = 'COOL' 70 | THERMOSTAT_MODE_HEAT = 'HEAT' 71 | THERMOSTAT_MODE_AUTO = 'AUTO' 72 | THERMOSTAT_MODE_OFF = 'OFF' 73 | 74 | CLOSE = "Close" 75 | OPEN = "Open" 76 | 77 | VOLUME = 'volume' 78 | 79 | INPUT = 'input' 80 | CONTROL = 'control' 81 | 82 | RED = 'r' 83 | GREEN = 'g' 84 | BLUE = 'b' 85 | 86 | MUTE = 'mute' 87 | 88 | def __setattr__(self, *_): 89 | pass 90 | -------------------------------------------------------------------------------- /sinric/_sinricpro_udp.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | import asyncio 9 | import socket 10 | from socket import AF_INET, SOCK_DGRAM 11 | from ._queues import queue 12 | import json 13 | from asyncio import sleep 14 | import struct 15 | 16 | 17 | class EchoServerProtocol(asyncio.DatagramProtocol): 18 | def __init__(self, enablePrint, deviceIdArr=[]) -> None: 19 | super().__init__() 20 | self.enablePrint = enablePrint 21 | self.deviceIdArr = deviceIdArr 22 | 23 | def connection_made(self, transport): 24 | self.transport = transport 25 | 26 | def datagram_received(self, data, addr): 27 | jsonData = json.loads(data.decode('ascii')) 28 | if jsonData.get('payload', None).get('deviceId', None) in self.deviceIdArr: 29 | queue.put([jsonData, 'udp_response', addr]) 30 | else: 31 | print('Invalid Device id') 32 | if self.enablePrint: 33 | print(data) 34 | print('UDP senderID : ', addr) 35 | 36 | 37 | class SinricProUdp: 38 | def __init__(self, callbacks_udp, deviceIdArr, enable_trace=False, loop_delay=0.5, loopInstance=None): 39 | self.callbacks = callbacks_udp 40 | self.deviceIdArr = deviceIdArr 41 | self.enablePrint = enable_trace 42 | self.loopInstance = loopInstance 43 | self.loop_delay = loop_delay if loop_delay > 0 else 0.5 44 | self.udp_ip = '224.9.9.9' 45 | self.udp_port = 3333 46 | self.address = ('', self.udp_port) 47 | self.sockServ = socket.socket(AF_INET, SOCK_DGRAM) 48 | self.sockServ.bind(self.address) 49 | self.sockServ.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, 50 | struct.pack("4sl", socket.inet_aton(self.udp_ip), socket.INADDR_ANY)) 51 | 52 | def sendResponse(self, data, sender): 53 | self.sockServ.sendto(data, sender) 54 | 55 | async def listen(self): 56 | await self.loopInstance.create_datagram_endpoint( 57 | lambda: EchoServerProtocol( 58 | enablePrint=self.enablePrint, deviceIdArr=self.deviceIdArr), 59 | local_addr=None, remote_addr=None, sock=self.sockServ) 60 | while True: 61 | await asyncio.sleep(self.loop_delay) 62 | -------------------------------------------------------------------------------- /sinric/_sinricpro_websocket.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from websockets import client 9 | from websockets.exceptions import ConnectionClosed 10 | import json 11 | 12 | from .helpers.wait import waitAsync 13 | from ._queues import queue 14 | from ._callback_handler import CallBackHandler 15 | from ._signature import Signature 16 | from time import time 17 | import asyncio 18 | 19 | try: 20 | from importlib.metadata import version 21 | except ImportError: 22 | from importlib_metadata import version 23 | 24 | 25 | class SinricProSocket(Signature): 26 | 27 | def __init__(self, app_key, device_id, callbacks, enable_trace=False, logger=None, restore_states=False, 28 | secret_key="", loop_delay=0.5): 29 | self.app_key = app_key 30 | self.secret_key = secret_key 31 | self.restore_states = restore_states 32 | self.logger = logger 33 | self.device_ids = device_id 34 | self.connection = None 35 | self.callbacks = callbacks 36 | self.loop_delay = loop_delay 37 | 38 | self.callbackHandler = CallBackHandler(self.callbacks, enable_trace, self.logger, self.restore_states, 39 | secret_key=self.secret_key) 40 | self.enableTrace = enable_trace 41 | Signature.__init__(self, self.secret_key) 42 | 43 | async def connect(self): # Producer 44 | sdk_version = version("sinricpro") 45 | self.connection = await client.connect('wss://ws.sinric.pro', 46 | extra_headers={'appkey': self.app_key, 47 | 'deviceids': ';'.join(self.device_ids), 48 | 'platform': 'python', 49 | 'sdkversion': sdk_version, 50 | 'restoredevicestates': ( 51 | 'true' if self.restore_states else 'false')}, 52 | ping_interval=30000, ping_timeout=10000) 53 | if self.connection.open: 54 | self.logger.success("Connected :)") 55 | timestamp = await self.connection.recv() 56 | if (int(time()) - json.loads(timestamp).get('timestamp') > 60000): 57 | self.logger.warning('Timestamp is not in sync. Please check your system time.') 58 | return self.connection 59 | 60 | async def receive_message(self, connection): 61 | while True: 62 | try: 63 | message = await waitAsync(connection.recv()) 64 | if message is None: 65 | continue 66 | if self.enableTrace: 67 | self.logger.info(f"Request : {message}") 68 | request = json.loads(message) 69 | queue.put([request, 'socket_response', 'request_response']) 70 | await asyncio.sleep(self.loop_delay) 71 | except ConnectionClosed as e: 72 | self.logger.info('Connection with server closed') 73 | raise e 74 | 75 | async def handle_queue(self): 76 | while True: 77 | await asyncio.sleep(self.loop_delay) 78 | 79 | while queue.qsize() > 0: 80 | await self.callbackHandler.handle_callbacks(queue.get(), self.connection, None) 81 | -------------------------------------------------------------------------------- /sinric/_speaker_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from sinric.helpers.set_limits import set_limits 9 | from ._sinricpro_constants import SinricProConstants 10 | 11 | 12 | class SpeakerController: 13 | 14 | def __init__(self): 15 | self.band = 0 16 | 17 | async def set_bands(self, jsn, callback): 18 | value = jsn.get("payload").get(SinricProConstants.VALUE) 19 | bands = value.get(SinricProConstants.BANDS)[0] 20 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), bands.get(SinricProConstants.NAME), bands.get(SinricProConstants.LEVEL)) 21 | 22 | async def adjust_bands(self, jsn, callback): 23 | value = jsn.get("payload").get(SinricProConstants.VALUE) 24 | bands = value.get(SinricProConstants.BANDS)[0] 25 | self.band += bands.get(SinricProConstants.LEVEL_DELTA, 0) 26 | self.band = set_limits(self.band) 27 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), bands.get(SinricProConstants.NAME), self.band, 28 | bands.get(SinricProConstants.LEVELDIRECTION)) 29 | 30 | async def reset_bands(self, jsn, callback): 31 | value = jsn.get("payload").get(SinricProConstants.VALUE) 32 | band1 = value.get(SinricProConstants.BANDS)[0] 33 | band2 = value.get(SinricProConstants.BANDS)[1] 34 | band3 = value.get(SinricProConstants.BANDS)[2] 35 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), band1, band2, band3) 36 | -------------------------------------------------------------------------------- /sinric/_temperature_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from sinric.helpers.set_limits import set_limits 9 | from ._sinricpro_constants import SinricProConstants 10 | 11 | 12 | class TemperatureController: 13 | def __init__(self, temperature): 14 | self.temperature = temperature 15 | 16 | async def target_temperature(self, jsn, callback): 17 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get(SinricProConstants.VALUE).get('temperature')) 18 | 19 | async def adjust_temperature(self, jsn, callback): 20 | self.temperature += jsn.get("payload").get( 21 | SinricProConstants.VALUE).get('temperature') 22 | self.temperature = set_limits(self.temperature) 23 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), self.temperature) 24 | -------------------------------------------------------------------------------- /sinric/_thermostat_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from ._sinricpro_constants import SinricProConstants 9 | 10 | 11 | class ThermostateMode: 12 | 13 | async def set_thermostate_mode(self, jsn, thermostat_callback): 14 | return thermostat_callback( 15 | jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get("value").get(SinricProConstants.THERMOSTATMODE)) 16 | -------------------------------------------------------------------------------- /sinric/_tv_contorller.py: -------------------------------------------------------------------------------- 1 | """ 2 | * Copyright (c) 2019-2023 Sinric. All rights reserved. 3 | * Licensed under Creative Commons Attribution-Share Alike (CC BY-SA) 4 | * 5 | * This file is part of the Sinric Pro (https://github.com/sinricpro/) 6 | """ 7 | 8 | from sinric.helpers.set_limits import set_limits 9 | from ._sinricpro_constants import SinricProConstants 10 | 11 | 12 | class TvController: 13 | def __init__(self, x): 14 | self.volume = x 15 | 16 | async def set_volume(self, jsn, callback): 17 | self.volume = jsn.get("payload").get( 18 | SinricProConstants.VALUE).get('volume') 19 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), self.volume) 20 | 21 | async def adjust_volume(self, jsn, callback): 22 | self.volume += jsn.get("payload").get( 23 | SinricProConstants.VALUE).get('volume') 24 | self.volume = set_limits(self.volume) 25 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), self.volume) 26 | 27 | async def set_mute(self, jsn, callback): 28 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get(SinricProConstants.VALUE).get('mute')) 29 | 30 | async def media_control(self, jsn, callback): 31 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get(SinricProConstants.VALUE).get('control')) 32 | 33 | async def select_input(self, jsn, callback): 34 | inp = jsn.get("payload").get(SinricProConstants.VALUE).get('input') 35 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), inp) 36 | 37 | async def change_channel(self, jsn, callback): 38 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get(SinricProConstants.VALUE).get('channel').get('name')) 39 | 40 | async def skip_channels(self, jsn, callback): 41 | return callback(jsn.get("payload").get(SinricProConstants.DEVICEID), jsn.get("payload").get(SinricProConstants.VALUE).get('channelCount')) 42 | -------------------------------------------------------------------------------- /sinric/helpers/set_limits.py: -------------------------------------------------------------------------------- 1 | from numbers import Real 2 | from typing import Generic, TypeVar, Union 3 | 4 | # TODO: understand python generics 5 | _number = TypeVar('_number', bound=Union[int, float, Real]) 6 | 7 | 8 | def set_limits(value: _number, min: int = 0, max: int = 100) -> _number: 9 | """ 10 | Set value to within bounds. 11 | 12 | Args: 13 | value (Generic[_number]): The number you want to set the bounds for 14 | min (int, optional): The lower limit. Defaults to 0. 15 | max (int, optional): The upper limit. Defaults to 100. 16 | 17 | Returns: 18 | _number: The value capped to the limits 19 | """ 20 | if (value < min): 21 | # TODO cast to number type 22 | return min 23 | elif value > max: 24 | return max 25 | return value 26 | -------------------------------------------------------------------------------- /sinric/helpers/wait.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import asyncio 4 | 5 | 6 | async def waitAsync(aysncAction): 7 | try: 8 | val = await asyncio.wait_for(aysncAction, timeout=1) 9 | return val 10 | except Exception: 11 | return None --------------------------------------------------------------------------------