diff --git a/RPI Code/MQ-2/Raspberry-Pi-Gas-Sensor-MQ b/RPI Code/MQ-2/Raspberry-Pi-Gas-Sensor-MQ deleted file mode 160000 index cb64968..0000000 --- a/RPI Code/MQ-2/Raspberry-Pi-Gas-Sensor-MQ +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cb649680218eada1c7b1fcca4b1bfd6f492ae5c6 diff --git a/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/MCP3008.py b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/MCP3008.py new file mode 100644 index 0000000..e1ac709 --- /dev/null +++ b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/MCP3008.py @@ -0,0 +1,23 @@ +from spidev import SpiDev + +class MCP3008: + def __init__(self, bus = 0, device = 0): + self.bus, self.device = bus, device + self.spi = SpiDev() + self.open() + self.spi.max_speed_hz = 1000000 # 1MHz + + def open(self): + self.spi.open(self.bus, self.device) + self.spi.max_speed_hz = 1000000 # 1MHz + + def read(self, channel = 0): + cmd1 = 4 | 2 | (( channel & 4) >> 2) + cmd2 = (channel & 3) << 6 + + adc = self.spi.xfer2([cmd1, cmd2, 0]) + data = ((adc[1] & 15) << 8) + adc[2] + return data + + def close(self): + self.spi.close() diff --git a/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/MCP3008.pyc b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/MCP3008.pyc new file mode 100644 index 0000000..1e5677c Binary files /dev/null and b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/MCP3008.pyc differ diff --git a/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/README.md b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/README.md new file mode 100644 index 0000000..25741af --- /dev/null +++ b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/README.md @@ -0,0 +1,6 @@ +Raspberry Pi Gas Sensor MQ Python Example +================ + +Tutorial (german): https://tutorials-raspberrypi.de/raspberry-pi-gas-sensor-mq2-konfigurieren-und-auslesen + +Tutorial (english): https://tutorials-raspberrypi.com/configure-and-read-out-the-raspberry-pi-gas-sensor-mq-x diff --git a/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/example.py b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/example.py new file mode 100644 index 0000000..c28c826 --- /dev/null +++ b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/example.py @@ -0,0 +1,17 @@ +from mq import * +import sys, time + +try: + print("Press CTRL+C to abort.") + + mq = MQ(); + while True: + perc = mq.MQPercentage() + sys.stdout.write("\r") + sys.stdout.write("\033[K") + sys.stdout.write("LPG: %g ppm, CO: %g ppm, Smoke: %g ppm" % (perc["GAS_LPG"], perc["CO"], perc["SMOKE"])) + sys.stdout.flush() + time.sleep(0.1) + +except: + print("\nAbort by user") \ No newline at end of file diff --git a/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/mq.py b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/mq.py new file mode 100644 index 0000000..f233467 --- /dev/null +++ b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/mq.py @@ -0,0 +1,140 @@ + +# adapted from sandboxelectronics.com/?p=165 + +import time +import math +from MCP3008 import MCP3008 + +class MQ(): + + ######################### Hardware Related Macros ######################### + MQ_PIN = 0 # define which analog input channel you are going to use (MCP3008) + RL_VALUE = 5 # define the load resistance on the board, in kilo ohms + RO_CLEAN_AIR_FACTOR = 9.83 # RO_CLEAR_AIR_FACTOR=(Sensor resistance in clean air)/RO, + # which is derived from the chart in datasheet + + ######################### Software Related Macros ######################### + CALIBARAION_SAMPLE_TIMES = 50 # define how many samples you are going to take in the calibration phase + CALIBRATION_SAMPLE_INTERVAL = 500 # define the time interal(in milisecond) between each samples in the + # cablibration phase + READ_SAMPLE_INTERVAL = 50 # define how many samples you are going to take in normal operation + READ_SAMPLE_TIMES = 5 # define the time interal(in milisecond) between each samples in + # normal operation + + ######################### Application Related Macros ###################### + GAS_LPG = 0 + GAS_CO = 1 + GAS_SMOKE = 2 + + def __init__(self, Ro=10, analogPin=0): + self.Ro = Ro + self.MQ_PIN = analogPin + self.adc = MCP3008() + + self.LPGCurve = [2.3,0.21,-0.47] # two points are taken from the curve. + # with these two points, a line is formed which is "approximately equivalent" + # to the original curve. + # data format:{ x, y, slope}; point1: (lg200, 0.21), point2: (lg10000, -0.59) + self.COCurve = [2.3,0.72,-0.34] # two points are taken from the curve. + # with these two points, a line is formed which is "approximately equivalent" + # to the original curve. + # data format:[ x, y, slope]; point1: (lg200, 0.72), point2: (lg10000, 0.15) + self.SmokeCurve =[2.3,0.53,-0.44] # two points are taken from the curve. + # with these two points, a line is formed which is "approximately equivalent" + # to the original curve. + # data format:[ x, y, slope]; point1: (lg200, 0.53), point2: (lg10000, -0.22) + + print("Calibrating...") + self.Ro = self.MQCalibration(self.MQ_PIN) + print("Calibration is done...\n") + print("Ro=%f kohm" % self.Ro) + + + def MQPercentage(self): + val = {} + read = self.MQRead(self.MQ_PIN) + val["GAS_LPG"] = self.MQGetGasPercentage(read/self.Ro, self.GAS_LPG) + val["CO"] = self.MQGetGasPercentage(read/self.Ro, self.GAS_CO) + val["SMOKE"] = self.MQGetGasPercentage(read/self.Ro, self.GAS_SMOKE) + return val + + ######################### MQResistanceCalculation ######################### + # Input: raw_adc - raw value read from adc, which represents the voltage + # Output: the calculated sensor resistance + # Remarks: The sensor and the load resistor forms a voltage divider. Given the voltage + # across the load resistor and its resistance, the resistance of the sensor + # could be derived. + ############################################################################ + def MQResistanceCalculation(self, raw_adc): + return float(self.RL_VALUE*(1023.0-raw_adc)/float(raw_adc)); + + + ######################### MQCalibration #################################### + # Input: mq_pin - analog channel + # Output: Ro of the sensor + # Remarks: This function assumes that the sensor is in clean air. It use + # MQResistanceCalculation to calculates the sensor resistance in clean air + # and then divides it with RO_CLEAN_AIR_FACTOR. RO_CLEAN_AIR_FACTOR is about + # 10, which differs slightly between different sensors. + ############################################################################ + def MQCalibration(self, mq_pin): + val = 0.0 + for i in range(self.CALIBARAION_SAMPLE_TIMES): # take multiple samples + val += self.MQResistanceCalculation(self.adc.read(mq_pin)) + time.sleep(self.CALIBRATION_SAMPLE_INTERVAL/1000.0) + + val = val/self.CALIBARAION_SAMPLE_TIMES # calculate the average value + + val = val/self.RO_CLEAN_AIR_FACTOR # divided by RO_CLEAN_AIR_FACTOR yields the Ro + # according to the chart in the datasheet + + return val; + + + ######################### MQRead ########################################## + # Input: mq_pin - analog channel + # Output: Rs of the sensor + # Remarks: This function use MQResistanceCalculation to caculate the sensor resistenc (Rs). + # The Rs changes as the sensor is in the different consentration of the target + # gas. The sample times and the time interval between samples could be configured + # by changing the definition of the macros. + ############################################################################ + def MQRead(self, mq_pin): + rs = 0.0 + + for i in range(self.READ_SAMPLE_TIMES): + rs += self.MQResistanceCalculation(self.adc.read(mq_pin)) + time.sleep(self.READ_SAMPLE_INTERVAL/1000.0) + + rs = rs/self.READ_SAMPLE_TIMES + + return rs + + ######################### MQGetGasPercentage ############################## + # Input: rs_ro_ratio - Rs divided by Ro + # gas_id - target gas type + # Output: ppm of the target gas + # Remarks: This function passes different curves to the MQGetPercentage function which + # calculates the ppm (parts per million) of the target gas. + ############################################################################ + def MQGetGasPercentage(self, rs_ro_ratio, gas_id): + if ( gas_id == self.GAS_LPG ): + return self.MQGetPercentage(rs_ro_ratio, self.LPGCurve) + elif ( gas_id == self.GAS_CO ): + return self.MQGetPercentage(rs_ro_ratio, self.COCurve) + elif ( gas_id == self.GAS_SMOKE ): + return self.MQGetPercentage(rs_ro_ratio, self.SmokeCurve) + return 0 + + ######################### MQGetPercentage ################################# + # Input: rs_ro_ratio - Rs divided by Ro + # pcurve - pointer to the curve of the target gas + # Output: ppm of the target gas + # Remarks: By using the slope and a point of the line. The x(logarithmic value of ppm) + # of the line could be derived if y(rs_ro_ratio) is provided. As it is a + # logarithmic coordinate, power of 10 is used to convert the result to non-logarithmic + # value. + ############################################################################ + def MQGetPercentage(self, rs_ro_ratio, pcurve): + return (math.pow(10,( ((math.log(rs_ro_ratio)-pcurve[1])/ pcurve[2]) + pcurve[0]))) + diff --git a/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/mq.pyc b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/mq.pyc new file mode 100644 index 0000000..d32b46a Binary files /dev/null and b/RPI Code/MQ-2_/Raspberry-Pi-Gas-Sensor-MQ/mq.pyc differ diff --git a/RPI Code/MQ-2/test-mq2.py b/RPI Code/MQ-2_/test-mq2.py similarity index 100% rename from RPI Code/MQ-2/test-mq2.py rename to RPI Code/MQ-2_/test-mq2.py diff --git a/RPI Code/Meross/MerossIot b/RPI Code/Meross/MerossIot deleted file mode 160000 index 34f9422..0000000 --- a/RPI Code/Meross/MerossIot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 34f9422f89605c0994ae718d8ec42322ddaec996 diff --git a/RPI Code/Meross_/MerossIot/.gitignore b/RPI Code/Meross_/MerossIot/.gitignore new file mode 100644 index 0000000..8d2e2f1 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/.gitignore @@ -0,0 +1,110 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Local test +local_test/ + +# IntelliJ +.idea \ No newline at end of file diff --git a/RPI Code/Meross_/MerossIot/LICENSE b/RPI Code/Meross_/MerossIot/LICENSE new file mode 100644 index 0000000..e28bd7a --- /dev/null +++ b/RPI Code/Meross_/MerossIot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Alberto Geniola + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/RPI Code/Meross_/MerossIot/README.md b/RPI Code/Meross_/MerossIot/README.md new file mode 100644 index 0000000..5dcfeb8 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/README.md @@ -0,0 +1,102 @@ +[![Build status](https://albertogeniola.visualstudio.com/Meross/_apis/build/status/Meross-Python%20package-CI)](https://albertogeniola.visualstudio.com/Meross/_build/latest?definitionId=1) +![Deployment](https://albertogeniola.vsrm.visualstudio.com/_apis/public/Release/badge/c4128d1b-c23c-418d-95c5-2de061954ee5/1/1) + +# Meross IoT library +A pure-python based library providing API for controlling Meross IoT devices over the internet. + +To see what devices are currently supported, checkout the *Currently supported devices* section. +Hopefully, more Meross hardware will be supported in the future. + +This library is still work in progress, therefore use it with caution. + +## Installation +Due to the popularity of the library, I've decided to list it publicly on the Pipy index. +So, the installation is as simple as typing the following command: + +``` +pip install meross_iot --upgrade +``` + +## Usage +The following script demonstrates how to use this library. + +```python +import time +import sys +from meross_iot.api import MerossHttpClient + +if __name__=='__main__': + httpHandler = MerossHttpClient(email="YOUR_MEROSS_CLOUD_EMAIL", password="YOUR_PASSWORD") + + # Retrieves the list of supported devices + print("Listing Devices...") + devices = httpHandler.list_supported_devices() + + for counter, device in enumerate(devices): + print("Playing with device: %d" % counter) + # Returns most of the info about the power plug + print("\nGetting system data...") + data = device.get_sys_data() + + # Turns the power-plug on + print("\nTurning the device on...") + device.turn_off() + + # Turns the power-plug off + print("\nTurning the device off...") + device.turn_on() + + # Reads the historical device consumption + print("\nReading consumption data...") + consumption = device.get_power_consumptionX() + + # Returns the list of WIFI Network available for the plug + # (Note. this takes some time to complete) + print("\nScanning Wifi...") + wifi_list = device.get_wifi_list() + + # Info about the device + print("\nGetting device trace...") + trace = device.get_trace() + print("\nGetting device debug...") + debug = device.get_debug() + + # Returns the capabilities of this device + print("\nRetrieving device abilities...") + abilities = device.get_abilities() + + # I still have to figure this out :S + # The following command is not yet implemented on all devices + # and might not work as expected. + # report = device.get_report() + + # Returns the current power consumption and voltage from the plug + # (Note: this is not really realtime, but close enough) + print("\nReading electricity...") + electricity = device.get_electricity() + +``` + +## Currently supported devices +Even though this library was firstly meant to drive only the Meross MSS310, +other nice developers contributed to its realization. The following is the +currently supported list of devices: + +- MSS310 both hw v1 and v2 (Thanks to [DanoneKiD](https://github.com/DanoneKiD)) +- MSS210 (Thanks to [ictes](https://github.com/ictes)) +- MSS110 (Thanks to [soberstadt](https://github.com/soberstadt)) +- MSS425E (Thanks to [ping-localhost](https://github.com/ping-localhost)) + +## Protocol details +This library was implemented by reverse-engineering the network communications between the plug and the meross network. +Anyone can do the same by simply installing a Man-In-The-Middle proxy and routing the ssl traffic of an Android emulator through the sniffer. + +If you want to understand how the Meross protocol works, [have a look at the Wiki](https://github.com/albertogeniola/MerossIot/wiki). Be aware: this is still work in progress, so some pages of the wiki might still be blank/under construction. + +## Donate! +I like reverse engineering and protocol inspection, I think it keeps your mind trained and healthy. However, if you liked or appreciated by work, why don't you buy me a beer? It would really motivate me to continue working on this repository to improve documentation, code and extend the supported meross devices. + +[![Buy me a beer](http://4.bp.blogspot.com/-1Md6-deTZ84/VA_lzcxMx1I/AAAAAAAACl8/wP_4rXBXwyI/s1600/PayPal-Donation-Button.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6HPAB89UYSZF2) + + + diff --git a/RPI Code/Meross_/MerossIot/README.md.save b/RPI Code/Meross_/MerossIot/README.md.save new file mode 100644 index 0000000..5dcfeb8 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/README.md.save @@ -0,0 +1,102 @@ +[![Build status](https://albertogeniola.visualstudio.com/Meross/_apis/build/status/Meross-Python%20package-CI)](https://albertogeniola.visualstudio.com/Meross/_build/latest?definitionId=1) +![Deployment](https://albertogeniola.vsrm.visualstudio.com/_apis/public/Release/badge/c4128d1b-c23c-418d-95c5-2de061954ee5/1/1) + +# Meross IoT library +A pure-python based library providing API for controlling Meross IoT devices over the internet. + +To see what devices are currently supported, checkout the *Currently supported devices* section. +Hopefully, more Meross hardware will be supported in the future. + +This library is still work in progress, therefore use it with caution. + +## Installation +Due to the popularity of the library, I've decided to list it publicly on the Pipy index. +So, the installation is as simple as typing the following command: + +``` +pip install meross_iot --upgrade +``` + +## Usage +The following script demonstrates how to use this library. + +```python +import time +import sys +from meross_iot.api import MerossHttpClient + +if __name__=='__main__': + httpHandler = MerossHttpClient(email="YOUR_MEROSS_CLOUD_EMAIL", password="YOUR_PASSWORD") + + # Retrieves the list of supported devices + print("Listing Devices...") + devices = httpHandler.list_supported_devices() + + for counter, device in enumerate(devices): + print("Playing with device: %d" % counter) + # Returns most of the info about the power plug + print("\nGetting system data...") + data = device.get_sys_data() + + # Turns the power-plug on + print("\nTurning the device on...") + device.turn_off() + + # Turns the power-plug off + print("\nTurning the device off...") + device.turn_on() + + # Reads the historical device consumption + print("\nReading consumption data...") + consumption = device.get_power_consumptionX() + + # Returns the list of WIFI Network available for the plug + # (Note. this takes some time to complete) + print("\nScanning Wifi...") + wifi_list = device.get_wifi_list() + + # Info about the device + print("\nGetting device trace...") + trace = device.get_trace() + print("\nGetting device debug...") + debug = device.get_debug() + + # Returns the capabilities of this device + print("\nRetrieving device abilities...") + abilities = device.get_abilities() + + # I still have to figure this out :S + # The following command is not yet implemented on all devices + # and might not work as expected. + # report = device.get_report() + + # Returns the current power consumption and voltage from the plug + # (Note: this is not really realtime, but close enough) + print("\nReading electricity...") + electricity = device.get_electricity() + +``` + +## Currently supported devices +Even though this library was firstly meant to drive only the Meross MSS310, +other nice developers contributed to its realization. The following is the +currently supported list of devices: + +- MSS310 both hw v1 and v2 (Thanks to [DanoneKiD](https://github.com/DanoneKiD)) +- MSS210 (Thanks to [ictes](https://github.com/ictes)) +- MSS110 (Thanks to [soberstadt](https://github.com/soberstadt)) +- MSS425E (Thanks to [ping-localhost](https://github.com/ping-localhost)) + +## Protocol details +This library was implemented by reverse-engineering the network communications between the plug and the meross network. +Anyone can do the same by simply installing a Man-In-The-Middle proxy and routing the ssl traffic of an Android emulator through the sniffer. + +If you want to understand how the Meross protocol works, [have a look at the Wiki](https://github.com/albertogeniola/MerossIot/wiki). Be aware: this is still work in progress, so some pages of the wiki might still be blank/under construction. + +## Donate! +I like reverse engineering and protocol inspection, I think it keeps your mind trained and healthy. However, if you liked or appreciated by work, why don't you buy me a beer? It would really motivate me to continue working on this repository to improve documentation, code and extend the supported meross devices. + +[![Buy me a beer](http://4.bp.blogspot.com/-1Md6-deTZ84/VA_lzcxMx1I/AAAAAAAACl8/wP_4rXBXwyI/s1600/PayPal-Donation-Button.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6HPAB89UYSZF2) + + + diff --git a/RPI Code/Meross_/MerossIot/ext-res/meross_cloud_arch.png b/RPI Code/Meross_/MerossIot/ext-res/meross_cloud_arch.png new file mode 100644 index 0000000..f1322ac Binary files /dev/null and b/RPI Code/Meross_/MerossIot/ext-res/meross_cloud_arch.png differ diff --git a/RPI Code/Meross_/MerossIot/ext-res/meross_cloud_draw_io.xml b/RPI Code/Meross_/MerossIot/ext-res/meross_cloud_draw_io.xml new file mode 100644 index 0000000..389e094 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/ext-res/meross_cloud_draw_io.xml @@ -0,0 +1 @@ +7Vjfj5s4EP5rIt09BBEMIXlMsptrpVbKaVe63tPKCw74ahjOOJukf/2Nwfxme9s23UirJg+xPw9mZr75hpgJ2SSnPyTN4o8QMjFx7PA0ITcTx5m5zhx/NHIukYXtlkAkeWiMGuCOf2EGtA164CHLO4YKQCiedcEA0pQFqoNRKeHYNduD6N41oxEbAHcBFUP0Lx6q2ETh+A3+jvEoru48my/LlYRWxiaSPKYhHFsQuZ2QjQRQ5Sg5bZjQyavyUl63fWa1dkyyVL3oAsPEExUHE9zHP+/vEVlL+MzkxJkL3Gj9qEeRHv3GQVkJk5DnVgDJhKwc2579PjQ0AapzlTU4KMFTtqlJsdFqD6nagABZ2BD8brWn60jSkLNmLYUUt1nnSrs1dsGeC9HC98UH8ZDmMQvN3Z6YVBx5/EAfmdhBzhWHFNceQSkdS22wEjzSCwoyRKmZBegQ5oSsY5UInM9MAKZCZ041N0HrW9I8K6Pd85P2Y42UZ3oxOUVaHRY95q4lWQ4HGbD3gfZnjdNy1LVK/lXqIZOgIABRp1j7zE7PFsCsLivUI4OEKXlGE3OB45tKNFJ0HK+cH5vCnhuTuFXTFUaNlKJ656bacGAK7pniWw6K7939/S5HaLV7P6ifWit2n9YWHd0CMVUjenTXPF6qHvqcJnlAmRUIOIQWzfhDQlPsCUnBSb9Sbdtf3swvw6Y777NpD9j0R9j0L8AmmQ0YYyG2TTMFqWKIIKXitkGxyRzScIxHduLqk4Ytz8z+Nkb/MKXORnH0oAChZu8PoAkqdii90S58PZnocSE9k7HqeUJlxCqz+XjSJRNU8afu/j+SwmqPdje+uyPVY6+rhaLgeFI8qtqZGy/Z/630QiNrGnyOCk7GWmlxs1XVz0abm/HnJlZKP4tXOnRnG4QpsTg2tD1HtmXx3HC2IVUUfzSOkt8esy8AyTRkT0xAxuRUL0xzJMHZejamYEvs6cxZWFkaXaLx2V5HKu5sKBVvRCreJaTivAmpONeUivNLKq8mFf+KUnHfhFTINaVCfknltaRC3CtKxf85UvH7WsFcyPOnyk5PihVrXk13THJ0X/81/iZx9c9wi4AFwdip73HhuZ793XIk3ogcl68kR7L4OTQ539vSrpr1H22CxaUrKem5ZZABT1Xe2nmngdYxyfc7KiWk95akZ09s+2v2OCg9aPivQ3lZSXjDDl28YSlOwrsXvFt5g507AogEmyYU+winAlt4jgFMq2V3Udg+5AmVKovxoP/gLrLTBXu56y2sbjcnyxppH6jJsJ9X2Dc0Cpw27/3KImrenpLb/wA= \ No newline at end of file diff --git a/RPI Code/Meross_/MerossIot/meross_iot/__init__.py b/RPI Code/Meross_/MerossIot/meross_iot/__init__.py new file mode 100644 index 0000000..e00300d --- /dev/null +++ b/RPI Code/Meross_/MerossIot/meross_iot/__init__.py @@ -0,0 +1 @@ +name = "meross_iot" diff --git a/RPI Code/Meross_/MerossIot/meross_iot/api.py b/RPI Code/Meross_/MerossIot/meross_iot/api.py new file mode 100644 index 0000000..f334204 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/meross_iot/api.py @@ -0,0 +1,137 @@ +import base64 +import hashlib +import json +import random +import string +import time + +import requests + +from meross_iot.device_factory import build_wrapper + +# Appears to be used as a part of the signature algorithm as constant "salt" (kinda useless) +_SECRET = "23x17ahWarFH6w29" +_MEROSS_URL = "https://iot.meross.com" +_LOGIN_URL = "%s%s" % (_MEROSS_URL, "/v1/Auth/Login") +_LOG_URL = "%s%s" % (_MEROSS_URL, "/v1/log/user") +_DEV_LIST = "%s%s" % (_MEROSS_URL, "/v1/Device/devList") + + +class MerossHttpClient: + _token = None + _key = None + _userid = None + _userEmail = None + + _email = None + _password = None + _authenticated = False + + def __init__(self, email, password): + self._email = email + self._password = password + + def _authenticated_post(self, + url, # type: str + params_data # type: dict + ): + + nonce = self._generate_nonce(16) + timestamp_millis = int(round(time.time() * 1000)) + login_params = self._encode_params(params_data) + + # Generate the md5-hash (called signature) + m = hashlib.md5() + datatosign = '%s%s%s%s' % (_SECRET, timestamp_millis, nonce, login_params) + m.update(datatosign.encode("utf8")) + md5hash = m.hexdigest() + + headers = { + "Authorization": "Basic" if self._token is None else "Basic %s" % self._token, + "vender": "Meross", + "AppVersion": "1.3.0", + "AppLanguage": "EN", + "User-Agent": "okhttp/3.6.0" + } + + payload = { + 'params': login_params, + 'sign': md5hash, + 'timestamp': timestamp_millis, + 'nonce': nonce + } + + # Perform the request. + r = requests.post(url, data=payload, headers=headers) + + # Check if that is ok. + if r.status_code != 200: + raise AuthenticatedPostException() + + # Save returned value + jsondata = r.json() + # print(jsondata) + + if jsondata["info"].lower() != "success": + raise AuthenticatedPostException() + + return jsondata["data"] + + def _encode_params(self, + parameters # type: dict + ): + jsonstring = json.dumps(parameters) + return str(base64.b64encode(jsonstring.encode("utf8")), "utf8") + + def _generate_nonce(self, length): + return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length)) + + def _login(self): + try: + data = {"email": self._email, "password": self._password} + response_data = self._authenticated_post(_LOGIN_URL, params_data=data) + self._token = response_data["token"] + self._key = response_data["key"] + self._userid = response_data["userid"] + self._userEmail = response_data["email"] + self._authenticated = True + except: + return False + + try: + # The protocol does not really need the following call. However we want to be nice do it anyways + self._log() + except: + pass + + return True + + def _log(self, ): + data = {'extra': {}, 'model': 'Android,Android SDK built for x86_64', 'system': 'Android', + 'uuid': '493dd9174941ed58waitForOpenWifi', 'vendor': 'Meross', 'version': '6.0'} + response_data = self._authenticated_post(_LOG_URL, params_data=data) + + def list_devices(self): + if not self._authenticated and not self._login(): + raise UnauthorizedException() + + return self._authenticated_post(_DEV_LIST, {}) + + def list_supported_devices(self): + supported_devices = [] + for dev in self.list_devices(): + deviceType = dev['deviceType'] + device = build_wrapper(self._token, self._key, self._userid, deviceType, dev) + if device is not None: + supported_devices.append(device) + # else log... + + return supported_devices + + +class AuthenticatedPostException(Exception): + pass + + +class UnauthorizedException(Exception): + pass diff --git a/RPI Code/Meross_/MerossIot/meross_iot/device_factory.py b/RPI Code/Meross_/MerossIot/meross_iot/device_factory.py new file mode 100644 index 0000000..bbf1885 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/meross_iot/device_factory.py @@ -0,0 +1,20 @@ +from meross_iot.supported_devices.power_plugs import Mss310, Mss210, Mss110, Mss425e + + +def build_wrapper( + token, + key, + user_id, + device_type, # type: str + device_specs # type: dict +): + if device_type.lower() == "mss310": + return Mss310(token, key, user_id, **device_specs) + elif device_type.lower() == "mss210": + return Mss210(token, key, user_id, **device_specs) + elif device_type.lower() == "mss110": + return Mss110(token, key, user_id, **device_specs) + elif device_type.lower() == "mss425e": + return Mss425e(token, key, user_id, **device_specs) + else: + return None diff --git a/RPI Code/Meross_/MerossIot/meross_iot/supported_devices/__init__.py b/RPI Code/Meross_/MerossIot/meross_iot/supported_devices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_/MerossIot/meross_iot/supported_devices/power_plugs.py b/RPI Code/Meross_/MerossIot/meross_iot/supported_devices/power_plugs.py new file mode 100644 index 0000000..b7938bf --- /dev/null +++ b/RPI Code/Meross_/MerossIot/meross_iot/supported_devices/power_plugs.py @@ -0,0 +1,412 @@ +debug = False + +import json +import logging +import random +import ssl +import string +import sys +import time +from enum import Enum +from hashlib import md5 +from logging import StreamHandler +from threading import RLock, Condition + +import paho.mqtt.client as mqtt +from paho.mqtt import MQTTException + +from meross_iot.utilities.synchronization import AtomicCounter + +l = logging.getLogger("meross_powerplug") +l.addHandler(StreamHandler(stream=sys.stdout)) +l.setLevel(logging.DEBUG) + + +class ClientStatus(Enum): + INITIALIZED = 1 + CONNECTING = 2 + CONNECTED = 3 + SUBSCRIBED = 4 + CONNECTION_DROPPED = 5 + + +class Device: + _status_lock = None + _client_status = None + + _token = None + _key = None + _user_id = None + _domain = None + _port = 2001 + _channels = [] + + _uuid = None + _client_id = None + _app_id = None + + # Device informations + _name = None + _type = None + _hwversion = None + _fwversion = None + + # Topic name where the client should publish to its commands. Every client should have a dedicated one. + _client_request_topic = None + + # Topic name in which the client retrieves its own responses from server. + _client_response_topic = None + + # Topic where important notifications are pushed (needed if any other client is dealing with the same device) + _user_topic = None + + # Paho mqtt client object + _mqtt_client = None + + # Waiting condition used to wait for command ACKs + _waiting_message_ack_queue = None + _waiting_subscribers_queue = None + _waiting_message_id = None + + _ack_response = None + + # Block for at most 10 seconds. + _command_timeout = 10 + + _error = None + + _status = None + + def __init__(self, + token, + key, + user_id, + **kwords): + + self._status_lock = RLock() + + self._waiting_message_ack_queue = Condition() + self._waiting_subscribers_queue = Condition() + self._subscription_count = AtomicCounter(0) + + self._set_status(ClientStatus.INITIALIZED) + + self._token = token, + self._key = key + self._user_id = user_id + self._uuid = kwords['uuid'] + if "domain" in kwords: + self._domain = kwords['domain'] + else: + self._domain = "eu-iot.meross.com" + if "channels" in kwords: + self._channels = kwords['channels'] + + # Informations about device + if "devName" in kwords: + self._name = kwords['devName'] + if "deviceType" in kwords: + self._type = kwords['deviceType'] + if "fmwareVersion" in kwords: + self._fwversion = kwords['fmwareVersion'] + if "hdwareVersion" in kwords: + self._hwversion = kwords['hdwareVersion'] + + self._generate_client_and_app_id() + + # Password is calculated as the MD5 of USERID concatenated with KEY + md5_hash = md5() + clearpwd = "%s%s" % (self._user_id, self._key) + md5_hash.update(clearpwd.encode("utf8")) + hashed_password = md5_hash.hexdigest() + + # Start the mqtt client + self._mqtt_client = mqtt.Client(client_id=self._client_id, + protocol=mqtt.MQTTv311) # ex. app-id -> app:08d4c9f99da40203ebc798a76512ec14 + self._mqtt_client.on_connect = self._on_connect + self._mqtt_client.on_message = self._on_message + self._mqtt_client.on_disconnect = self._on_disconnect + self._mqtt_client.on_subscribe = self._on_subscribe + self._mqtt_client.on_log = self._on_log + self._mqtt_client.username_pw_set(username=self._user_id, password=hashed_password) + self._mqtt_client.tls_set(ca_certs=None, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, + tls_version=ssl.PROTOCOL_TLS, + ciphers=None) + + self._mqtt_client.connect(self._domain, self._port, keepalive=30) + self._set_status(ClientStatus.CONNECTING) + + # Starts a new thread that handles mqtt protocol and calls us back via callbacks + self._mqtt_client.loop_start() + + with self._waiting_subscribers_queue: + self._waiting_subscribers_queue.wait() + if self._client_status != ClientStatus.SUBSCRIBED: + # An error has occurred + raise Exception(self._error) + + def _on_disconnect(self, client, userdata, rc): + l.info("Disconnection detected. Reason: %s" % str(rc)) + + # We should clean all the data structures. + with self._status_lock: + self._subscription_count = AtomicCounter(0) + self._error = "Connection dropped by the server" + self._set_status(ClientStatus.CONNECTION_DROPPED) + + with self._waiting_subscribers_queue: + self._waiting_subscribers_queue.notify_all() + + with self._waiting_message_ack_queue: + self._waiting_message_ack_queue.notify_all() + + if rc == mqtt.MQTT_ERR_SUCCESS: + pass + else: + # TODO: Should we reconnect by calling again the client.loop_start() ? + client.loop_stop() + + def _on_unsubscribe(self): + l.info("Unsubscribed from topic") + self._subscription_count.dec() + + def _on_subscribe(self, client, userdata, mid, granted_qos): + l.info("Succesfully subscribed!") + if self._subscription_count.inc() == 2: + with self._waiting_subscribers_queue: + self._set_status(ClientStatus.SUBSCRIBED) + self._waiting_subscribers_queue.notify_all() + + def _on_connect(self, client, userdata, rc, other): + l.info("Connected with result code %s" % str(rc)) + self._set_status(ClientStatus.SUBSCRIBED) + + self._set_status(ClientStatus.CONNECTED) + + self._client_request_topic = "/appliance/%s/subscribe" % self._uuid + self._client_response_topic = "/app/%s-%s/subscribe" % (self._user_id, self._app_id) + self._user_topic = "/app/%s/subscribe" % self._user_id + + # Subscribe to the relevant topics + l.info("Subscribing to topics...") + client.subscribe(self._user_topic) + client.subscribe(self._client_response_topic) + + # The callback for when a PUBLISH message is received from the server. + def _on_message(self, client, userdata, msg): + l.debug(msg.topic + " --> " + str(msg.payload)) + + try: + message = json.loads(str(msg.payload, "utf8")) + header = message['header'] + + message_hash = md5() + strtohash = "%s%s%s" % (header['messageId'], self._key, header['timestamp']) + message_hash.update(strtohash.encode("utf8")) + expected_signature = message_hash.hexdigest().lower() + + if (header['sign'] != expected_signature): + raise MQTTException('The signature did not match!') + + # If the message is the RESP for some previous action, process return the control to the "stopped" method. + if header['messageId'] == self._waiting_message_id: + with self._waiting_message_ack_queue: + self._ack_response = message + self._waiting_message_ack_queue.notify() + + # Otherwise process it accordingly + elif self._message_from_self(message): + if header['method'] == "PUSH" and 'payload' in message and 'toggle' in message['payload']: + self._handle_toggle(message) + else: + l.debug("UNKNOWN msg received by %s" % self._uuid) + # if header['method'] == "PUSH": + # TODO + else: + # do nothing because the message was from a different device + pass + except Exception as e: + l.debug("%s failed to process message because: %s" % (self._uuid, e)) + + def _on_log(self, client, userdata, level, buf): + # print("Data: %s - Buff: %s" % (userdata, buf)) + pass + + def _generate_client_and_app_id(self): + md5_hash = md5() + md5_hash.update(("%s%s" % ("API", self._uuid)).encode("utf8")) + self._app_id = md5_hash.hexdigest() + self._client_id = 'app:%s' % md5_hash.hexdigest() + + def _mqtt_message(self, method, namespace, payload): + # Generate a random 16 byte string + randomstring = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(16)) + + # Hash it as md5 + md5_hash = md5() + md5_hash.update(randomstring.encode('utf8')) + messageId = md5_hash.hexdigest().lower() + timestamp = int(round(time.time())) + + # Hash the messageId, the key and the timestamp + md5_hash = md5() + strtohash = "%s%s%s" % (messageId, self._key, timestamp) + md5_hash.update(strtohash.encode("utf8")) + signature = md5_hash.hexdigest().lower() + + data = { + "header": + { + "from": self._client_response_topic, + "messageId": messageId, # Example: "122e3e47835fefcd8aaf22d13ce21859" + "method": method, # Example: "GET", + "namespace": namespace, # Example: "Appliance.System.All", + "payloadVersion": 1, + "sign": signature, # Example: "b4236ac6fb399e70c3d61e98fcb68b74", + "timestamp": timestamp + }, + "payload": payload + } + strdata = json.dumps(data) + l.debug("--> %s" % strdata) + self._mqtt_client.publish(topic=self._client_request_topic, payload=strdata.encode("utf-8")) + return messageId + + def _wait_for_status(self, status): + ok = False + while not ok: + if not self._status_lock.acquire(True, self._command_timeout): + raise TimeoutError() + ok = status == self._client_status + self._status_lock.release() + + def _set_status(self, status): + with self._status_lock: + self._client_status = status + + def _execute_cmd(self, method, namespace, payload): + with self._waiting_subscribers_queue: + while self._client_status != ClientStatus.SUBSCRIBED: + self._waiting_subscribers_queue.wait() + + # Execute the command and retrieve the message-id + self._waiting_message_id = self._mqtt_message(method, namespace, payload) + + # Wait synchronously until we get the ACK. + with self._waiting_message_ack_queue: + self._waiting_message_ack_queue.wait() + + return self._ack_response['payload'] + + def _message_from_self(self, message): + try: + return 'from' in message['header'] and message['header']['from'].split('/')[2] == self._uuid + except: + return False + + def _handle_toggle(self, message): + if 'onoff' in message['payload']['toggle']: + self._status = (message['payload']['toggle']['onoff'] == 1) + + def get_sys_data(self): + return self._execute_cmd("GET", "Appliance.System.All", {}) + + def get_wifi_list(self): + return self._execute_cmd("GET", "Appliance.Config.WifiList", {}) + + def get_trace(self): + return self._execute_cmd("GET", "Appliance.Config.Trace", {}) + + def get_debug(self): + return self._execute_cmd("GET", "Appliance.System.Debug", {}) + + def get_abilities(self): + return self._execute_cmd("GET", "Appliance.System.Ability", {}) + + def get_report(self): + return self._execute_cmd("GET", "Appliance.System.Report", {}) + + def get_status(self): + if self._status is None: + self._status = self.get_sys_data()['all']['control']['toggle']['onoff'] == 1 + return self._status + + def device_id(self): + return self._uuid + + def get_channels(self): + return self._channels + + def turn_on(self): + self._status = True + payload = {"channel": 0, "toggle": {"onoff": 1}} + return self._execute_cmd("SET", "Appliance.Control.Toggle", payload) + + def turn_off(self): + self._status = False + payload = {"channel": 0, "toggle": {"onoff": 0}} + return self._execute_cmd("SET", "Appliance.Control.Toggle", payload) + + +class Mss310(Device): + def get_power_consumptionX(self): + return self._execute_cmd("GET", "Appliance.Control.ConsumptionX", {}) + + def get_electricity(self): + return self._execute_cmd("GET", "Appliance.Control.Electricity", {}) + + def turn_on(self): + if self._hwversion.split(".")[0] == "2": + payload = {'togglex': {"onoff": 1}} + return self._execute_cmd("SET", "Appliance.Control.ToggleX", payload) + else: + payload = {"channel": 0, "toggle": {"onoff": 1}} + return self._execute_cmd("SET", "Appliance.Control.Toggle", payload) + + def turn_off(self): + if self._hwversion.split(".")[0] == "2": + payload = {'togglex': {"onoff": 0}} + return self._execute_cmd("SET", "Appliance.Control.ToggleX", payload) + else: + payload = {"channel": 0, "toggle": {"onoff": 0}} + return self._execute_cmd("SET", "Appliance.Control.Toggle", payload) + + +class Mss425e(Device): + # TODO Implement for all channels + def _handle_toggle(self, message): + return None + + # TODO Implement for all channels + def get_status(self): + return None + + def turn_on(self): + payload = {'togglex': {"onoff": 1}} + return self._execute_cmd("SET", "Appliance.Control.ToggleX", payload) + + def turn_off(self): + payload = {'togglex': {"onoff": 0}} + return self._execute_cmd("SET", "Appliance.Control.ToggleX", payload) + + def turn_on_channel(self, channel): + payload = {'togglex': {'channel': channel, 'onoff': 1}} + return self._execute_cmd("SET", "Appliance.Control.ToggleX", payload) + + def turn_off_channel(self, channel): + payload = {'togglex': {'channel': channel, 'onoff': 0}} + return self._execute_cmd("SET", "Appliance.Control.ToggleX", payload) + + def enable_usb(self): + return self.turn_on_channel(4) + + def disable_usb(self): + return self.turn_off_channel(4) + + +class Mss210(Device): + pass + + +class Mss110(Device): + pass diff --git a/RPI Code/Meross_/MerossIot/meross_iot/utilities/__init__.py b/RPI Code/Meross_/MerossIot/meross_iot/utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_/MerossIot/meross_iot/utilities/synchronization.py b/RPI Code/Meross_/MerossIot/meross_iot/utilities/synchronization.py new file mode 100644 index 0000000..28119b3 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/meross_iot/utilities/synchronization.py @@ -0,0 +1,19 @@ +from threading import RLock + + +class AtomicCounter(object): + _lock = None + + def __init__(self, initialValue): + self._lock = RLock() + self._val = initialValue + + def dec(self): + with self._lock: + self._val -= 1 + return self._val + + def inc(self): + with self._lock: + self._val += 1 + return self._val diff --git a/RPI Code/Meross_/MerossIot/requirements.txt b/RPI Code/Meross_/MerossIot/requirements.txt new file mode 100644 index 0000000..6353cd4 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/requirements.txt @@ -0,0 +1,2 @@ +paho-mqtt>=1.3.1 +requests>=2.19.1 \ No newline at end of file diff --git a/RPI Code/Meross_/MerossIot/setup.py b/RPI Code/Meross_/MerossIot/setup.py new file mode 100644 index 0000000..b601e11 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/setup.py @@ -0,0 +1,40 @@ +from os import path + +from setuptools import setup, find_packages + +here = path.abspath(path.dirname(__file__)) + +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name='meross_iot', + version='0.1.4.2', + packages=find_packages(exclude=('tests',)), + url='https://github.com/albertogeniola/MerossIot', + license='MIT', + author='Alberto Geniola', + author_email='albertogeniola@gmail.com', + classifiers=[ + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 3', + 'Operating System :: OS Independent' + ], + description='A simple library to deal with Meross devices. At the moment MSS110, MSS210, MSS310 smart plugs and ' + 'the MSS425E power strip', + long_description=long_description, + long_description_content_type='text/markdown', + keywords='meross smartplug iot mqtt domotic switch mss310 mss210 mss110 mss425e', + project_urls={ + 'Documentation': 'https://github.com/albertogeniola/MerossIot', + 'Funding': 'https://donate.pypi.org', + 'Source': 'https://github.com/albertogeniola/MerossIot', + 'Tracker': 'https://github.com/albertogeniola/MerossIot/issues', + }, + install_requires=[ + 'paho-mqtt>=1.3.1', + 'requests>=2.19.1' + ], + python_requires='>=3', + test_suite='tests' +) diff --git a/RPI Code/Meross_/MerossIot/tests/RealTest.py b/RPI Code/Meross_/MerossIot/tests/RealTest.py new file mode 100644 index 0000000..b9b3575 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/tests/RealTest.py @@ -0,0 +1,53 @@ +import time +import sys +from meross_iot.api import MerossHttpClient + +if __name__=='__main__': + httpHandler = MerossHttpClient(email="thomas.fransolet@hotmail.be", password="Awesome09") + + # Retrieves the list of supported devices + print("Listing Devices...") + devices = httpHandler.list_supported_devices() + + for counter, device in enumerate(devices): + print("Playing with device: %d" % counter) + # Returns most of the info about the power plug + print("\nGetting system data...") + data = device.get_sys_data() + + # Turns the power-plug on + print("\nTurning the device on...") + device.turn_off() + + # Turns the power-plug off + print("\nTurning the device off...") + device.turn_on() + + # Reads the historical device consumption + print("\nReading consumption data...") + consumption = device.get_power_consumptionX() + + # Returns the list of WIFI Network available for the plug + # (Note. this takes some time to complete) + print("\nScanning Wifi...") + wifi_list = device.get_wifi_list() + + # Info about the device + print("\nGetting device trace...") + trace = device.get_trace() + print("\nGetting device debug...") + debug = device.get_debug() + + # Returns the capabilities of this device + print("\nRetrieving device abilities...") + abilities = device.get_abilities() + + # I still have to figure this out :S + # The following command is not yet implemented on all devices + # and might not work as expected. + # report = device.get_report() + + # Returns the current power consumption and voltage from the plug + # (Note: this is not really realtime, but close enough) + print("\nReading electricity...") + electricity = device.get_electricity() \ No newline at end of file diff --git a/RPI Code/Meross_/MerossIot/tests/__init__.py b/RPI Code/Meross_/MerossIot/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_/MerossIot/tests/test_power_plugs.py b/RPI Code/Meross_/MerossIot/tests/test_power_plugs.py new file mode 100644 index 0000000..68bfee7 --- /dev/null +++ b/RPI Code/Meross_/MerossIot/tests/test_power_plugs.py @@ -0,0 +1,69 @@ +import os +import time +import unittest + +from meross_iot.api import MerossHttpClient +from meross_iot.supported_devices.power_plugs import Mss310 + +EMAIL = os.environ.get('thomas.fransolet@hotmail.be') +PASSWORD = os.environ.get('Awesome09') + + +class TestHttpMethods(unittest.TestCase): + def setUp(self): + self.client = MerossHttpClient(email=EMAIL, password=PASSWORD) + + def test_device_listing(self): + devices = self.client.list_devices() + assert devices is not None + assert len(devices) > 0 + + def test_supported_device_listing(self): + devices = self.client.list_supported_devices() + assert devices is not None + assert len(devices) > 0 + + +class TestMSS310Test(unittest.TestCase): + def setUp(self): + httpHandler = MerossHttpClient(email=EMAIL, password=PASSWORD) + + # Retrieves the list of supported devices + devices = httpHandler.list_supported_devices() + for counter, device in enumerate(devices): + if isinstance(device, Mss310): + self.device = device + break + + def test_power_cycle(self): + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()) + + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()) + + self.device.turn_on() + time.sleep(2) + + self.assertTrue(self.device.get_status()) + + def test_get_info(self): + consumption = self.device.get_power_consumptionX() + assert consumption is not None + + wifi_list = self.device.get_wifi_list() + assert wifi_list is not None + + trace = self.device.get_trace() + assert trace is not None + + debug = self.device.get_debug() + assert debug is not None + + abilities = self.device.get_abilities() + assert abilities is not None + + electricity = self.device.get_electricity() + assert electricity is not None \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot b/RPI Code/Meross_2/MerossIot deleted file mode 160000 index 0e49e69..0000000 --- a/RPI Code/Meross_2/MerossIot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e49e69854f060518309892ebb8684e9dea457bd diff --git a/RPI Code/Meross_2/MerossIot_/.git - Raccourci.lnk b/RPI Code/Meross_2/MerossIot_/.git - Raccourci.lnk new file mode 100644 index 0000000..b1ee621 Binary files /dev/null and b/RPI Code/Meross_2/MerossIot_/.git - Raccourci.lnk differ diff --git a/RPI Code/Meross_2/MerossIot_/.gitignore b/RPI Code/Meross_2/MerossIot_/.gitignore new file mode 100644 index 0000000..2466641 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/.gitignore @@ -0,0 +1,115 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Local test +local_test/* +utilities/InfoGather.py + +# Dev utils +ext-res/tests/node_modules +ext-res/tests/package-lock.json + +# IntelliJ +.idea \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/LICENSE b/RPI Code/Meross_2/MerossIot_/LICENSE new file mode 100644 index 0000000..e28bd7a --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Alberto Geniola + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/RPI Code/Meross_2/MerossIot_/README.md b/RPI Code/Meross_2/MerossIot_/README.md new file mode 100644 index 0000000..2dbd02e --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/README.md @@ -0,0 +1,269 @@ +![Azure DevOps builds (branch)](https://img.shields.io/azure-devops/build/albertogeniola/c4128d1b-c23c-418d-95c5-2de061954ee5/1/0.3.X.X.svg) +![Deployment](https://albertogeniola.vsrm.visualstudio.com/_apis/public/Release/badge/c4128d1b-c23c-418d-95c5-2de061954ee5/1/1) +![Test status](https://img.shields.io/azure-devops/tests/albertogeniola/meross/1/master.svg) +[![PyPI version](https://badge.fury.io/py/meross-iot.svg)](https://badge.fury.io/py/meross-iot) +[![Downloads](https://pepy.tech/badge/meross-iot)](https://pepy.tech/project/meross-iot) +![PyPI - Downloads](https://img.shields.io/pypi/dm/meross-iot.svg?label=Pypi%20Downloads) +[![Beerpay](https://beerpay.io/albertogeniola/MerossIot/badge.svg?style=flat)](https://beerpay.io/albertogeniola/MerossIot) + +# Meross IoT library +A pure-python based library providing API for controlling Meross IoT devices over the internet. + +To see what devices are currently supported, checkout the *Currently supported devices* section. +However, some devices _might work as expected even if they are not listed_ among the supported devices. +In such cases, you're invited to open an issue and report tbe working/non-working status of your device. +This will help us to keep track of new devices and current support status of the library. + + +This library is still work in progress, therefore use it with caution. + +## Installation +Due to the popularity of the library, I've decided to list it publicly on the Pipy index. +So, the installation is as simple as typing the following command: + +``` +pip install meross_iot==0.3.1.3 --upgrade +``` + +## Usage +The following script demonstrates how to use this library. + +```python +from meross_iot.manager import MerossManager +from meross_iot.meross_event import MerossEventType +from meross_iot.cloud.devices.light_bulbs import GenericBulb +from meross_iot.cloud.devices.power_plugs import GenericPlug +from meross_iot.cloud.devices.door_openers import GenericGarageDoorOpener +import time +import os + + +EMAIL = os.environ.get('MEROSS_EMAIL') or "YOUR_MEROSS_CLOUD_EMAIL" +PASSWORD = os.environ.get('MEROSS_PASSWORD') or "YOUR_MEROSS_CLOUD_PASSWORD" + + +def event_handler(eventobj): + if eventobj.event_type == MerossEventType.DEVICE_ONLINE_STATUS: + print("Device online status changed: %s went %s" % (eventobj.device.name, eventobj.status)) + pass + elif eventobj.event_type == MerossEventType.DEVICE_SWITCH_STATUS: + print("Switch state changed: Device %s (channel %d) went %s" % (eventobj.device.name, eventobj.channel_id, + eventobj.switch_state)) + else: + print("Unknown event!") + + +if __name__=='__main__': + # Initiates the Meross Cloud Manager. This is in charge of handling the communication with the remote endpoint + manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + + # Register event handlers for the manager... + manager.register_event_handler(event_handler) + + # Starts the manager + manager.start() + + # You can retrieve the device you are looking for in various ways: + # By kind + bulbs = manager.get_devices_by_kind(GenericBulb) + plugs = manager.get_devices_by_kind(GenericPlug) + door_openers = manager.get_devices_by_kind(GenericGarageDoorOpener) + all_devices = manager.get_supported_devices() + + # Print some basic specs about the discovered devices + print("All the bulbs I found:") + for b in bulbs: + print(b) + + print("All the plugs I found:") + for p in plugs: + print(p) + + print("All the garage openers I found:") + for g in door_openers: + print(g) + + print("All the supported devices I found:") + for d in all_devices: + print(d) + + # You can also retrieve devices by the UUID/name + # a_device = manager.get_device_by_name("My Plug") + # a_device = manager.get_device_by_uuid("My Plug") + + # Or you can retrieve all the device by the HW type + # all_mss310 = manager.get_devices_by_type("mss310") + + # ------------------------------ + # Let's play the garage openers. + # ------------------------------ + for g in door_openers: + if not g.online: + print("The garage controller %s seems to be offline. Cannot play with that..." % g.name) + continue + + print("Opening door %s..." % g.name) + g.open_door() + print("Closing door %s..." % g.name) + g.close_door() + + # --------------------- + # Let's play with bulbs + # --------------------- + for b in bulbs: # type: GenericBulb + if not b.online: + print("The bulb %s seems to be offline. Cannot play with that..." % b.name) + continue + + print("Let's play with bulb %s" % b.name) + if not b.supports_light_control(): + print("Too bad bulb %s does not support light control %s" % b.name) + else: + # Let's make it red! + b.set_light_color(rgb=(255, 0, 0)) + + b.turn_on() + time.sleep(1) + b.turn_off() + + # --------------------------- + # Let's play with smart plugs + # --------------------------- + for p in plugs: # type: GenericPlug + if not p.online: + print("The plug %s seems to be offline. Cannot play with that..." % p.name) + continue + + print("Let's play with smart plug %s" % p.name) + + channels = len(p.get_channels()) + print("The plug %s supports %d channels." % (p.name, channels)) + for i in range(0, channels): + print("Turning on channel %d of %s" % (i, p.name)) + p.turn_on_channel(i) + + time.sleep(1) + + print("Turning off channel %d of %s" % (i, p.name)) + p.turn_off_channel(i) + + usb_channel = p.get_usb_channel_index() + if usb_channel is not None: + print("Awesome! This device also supports USB power.") + p.enable_usb() + time.sleep(1) + p.disable_usb() + + if p.supports_electricity_reading(): + print("Awesome! This device also supports power consumption reading.") + print("Current consumption is: %s" % str(p.get_electricity())) + + # At this point, we are all done playing with the library, so we gracefully disconnect and clean resources. + print("We are done playing. Cleaning resources...") + manager.stop() + + print("Bye bye!") +``` + +## Currently supported devices +Starting from v0.2.0.0, this library should support the majority of Meross devices on the market. +The list of tested devices is the following: +- MSL120 +- MSS110 +- MSS210 +- MSS310 +- MSS310h +- MSS425e +- MSS530H +- MSG100 + +I'd like to thank all the people who contributed to the early stage of library development, +who stimulated me to continue the development and making this library support more devices: + +Thanks to [DanoneKiD](https://github.com/DanoneKiD), [virtualdj](https://github.com/virtualdj), [ictes](https://github.com/ictes), [soberstadt](https://github.com/soberstadt), [ping-localhost](https://github.com/ping-localhost). + +## Protocol details +This library was implemented by reverse-engineering the network communications between the plug and the meross network. +Anyone can do the same by simply installing a Man-In-The-Middle proxy and routing the ssl traffic of an Android emulator through the sniffer. + +If you want to understand how the Meross protocol works, [have a look at the Wiki](https://github.com/albertogeniola/MerossIot/wiki). Be aware: this is still work in progress, so some pages of the wiki might still be blank/under construction. + +## Homeassistant integration +Yeah, it happened. As soon as I started developing this library, I've discovered the HomeAssistant world. +Thus, I've decided to spend some time to develop a full featured Homeassistant custom component, that you find [here](https://github.com/albertogeniola/meross-homeassistant). +Thanks to @troykelly who made a wish and supported my efforts in developing such component! + +## Donate! +I like reverse engineering and protocol inspection, I think it keeps your mind trained and healthy. +However, if you liked or appreciated by work, why don't you buy me a beer? +It would really motivate me to continue working on this repository to improve documentation, code and extend the supported meross devices. + +Moreover, donations will make me raise money to spend on other Meross devices. +So far, I've bought the following devices: +- MSL120 +- MSS210 +- MSS310 +- MSS425E +- MSS530H +- MSG100 + +By issuing a donation, you will: +1. Give me the opportunity to buy new devices and support them in this library +1. Pay part of electricity bill used to keep running the plugs 24/7 +(Note that they are used for Unit-Testing on the continuous integration engine when someone pushes a PR... I love DEVOPing!) +1. You'll increase the quality of my coding sessions with free-beer! + +[![Buy me a beer](http://4.bp.blogspot.com/-1Md6-deTZ84/VA_lzcxMx1I/AAAAAAAACl8/wP_4rXBXwyI/s1600/PayPal-Donation-Button.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CQQCK3RN32BHL&source=url) + +[![Beerpay](https://beerpay.io/albertogeniola/MerossIot/badge.svg?style=beer-square)](https://beerpay.io/albertogeniola/MerossIot) [![Beerpay](https://beerpay.io/albertogeniola/MerossIot/make-wish.svg?style=flat-square)](https://beerpay.io/albertogeniola/MerossIot?focus=wish) + +### Look at these babies! + +

+Look at the test environment that ensures high quality code of the library! +

+Current test environemnt +

When a pull-request is performed against this repository, a CI pipeline takes care of building the code, +testing it on Python 3.5/3.6/3.7, relying on some junit tests and, if all the tests pass as expected, the library +is released on Pypi. However, to ensure that the code really works, +the pipeline will issue on/off commands against real devices, that are dedicated 24/7 to the tests. +Such devices have been bought by myself (with contributions received by donators). +However, keeping such devices connected 24/7 has a cost, which I sustain happily due to the success of the library. +Anyways, feel free to contribute via donations! +

+ +## Changelog +### 0.3.1.3 +- Added event fire capability to GenericBulb class. +- Fixed bulb state kwargs bug +- Improved set_light_color function for bulbs +### 0.3.0.2 +- Fixed door closing checks when using the async + callback close() and open() methods. +### 0.3.0.1 +- Added get_power_consumption() and get_electricity() methods as abstract methods of AbstractMerossDevice +- Fixed regression passing manager parameter when firing Meross events. +### 0.3.0.0rc4 +- Added added switch_state to the generated event +### 0.3.0.0rc3 +- Added quick fix for MSS560 color control +### 0.3.0.0rc2 +- Fixed Major bugs with MSG100 +- Updated README examples +### 0.3.0.0rc1 +- Added MSG100 support +- Fixed errors being logged when power consumptionX command was issued on powerplugs +### 0.3.0.0b1 +- General refactor of the library +- Added event-based support +- Fixed default mqtt broker address for non-european devices +### 0.2.2.1 +- Added basic bulb support: turning on/off and light control +- Implemented MSL120 support +- Implemented MSL120 automatic test +- Extended example script usage to show how to control the light bulbs +- Added maximum retry limit for execute_command and connect() +### 0.2.1.1 +- Code refactoring to support heterogeneous devices (bulbs, plugs, garage openers) +### 0.2.1.0 +- Implemented auto-reconnect on lost connection +- Improving locking system in order to prevent library hangs when no ack is received diff --git a/RPI Code/Meross_2/MerossIot_/ext-res/meross_cloud_arch.png b/RPI Code/Meross_2/MerossIot_/ext-res/meross_cloud_arch.png new file mode 100644 index 0000000..f1322ac Binary files /dev/null and b/RPI Code/Meross_2/MerossIot_/ext-res/meross_cloud_arch.png differ diff --git a/RPI Code/Meross_2/MerossIot_/ext-res/meross_cloud_draw_io.xml b/RPI Code/Meross_2/MerossIot_/ext-res/meross_cloud_draw_io.xml new file mode 100644 index 0000000..389e094 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/ext-res/meross_cloud_draw_io.xml @@ -0,0 +1 @@ +7Vjfj5s4EP5rIt09BBEMIXlMsptrpVbKaVe63tPKCw74ahjOOJukf/2Nwfxme9s23UirJg+xPw9mZr75hpgJ2SSnPyTN4o8QMjFx7PA0ITcTx5m5zhx/NHIukYXtlkAkeWiMGuCOf2EGtA164CHLO4YKQCiedcEA0pQFqoNRKeHYNduD6N41oxEbAHcBFUP0Lx6q2ETh+A3+jvEoru48my/LlYRWxiaSPKYhHFsQuZ2QjQRQ5Sg5bZjQyavyUl63fWa1dkyyVL3oAsPEExUHE9zHP+/vEVlL+MzkxJkL3Gj9qEeRHv3GQVkJk5DnVgDJhKwc2579PjQ0AapzlTU4KMFTtqlJsdFqD6nagABZ2BD8brWn60jSkLNmLYUUt1nnSrs1dsGeC9HC98UH8ZDmMQvN3Z6YVBx5/EAfmdhBzhWHFNceQSkdS22wEjzSCwoyRKmZBegQ5oSsY5UInM9MAKZCZ041N0HrW9I8K6Pd85P2Y42UZ3oxOUVaHRY95q4lWQ4HGbD3gfZnjdNy1LVK/lXqIZOgIABRp1j7zE7PFsCsLivUI4OEKXlGE3OB45tKNFJ0HK+cH5vCnhuTuFXTFUaNlKJ656bacGAK7pniWw6K7939/S5HaLV7P6ifWit2n9YWHd0CMVUjenTXPF6qHvqcJnlAmRUIOIQWzfhDQlPsCUnBSb9Sbdtf3swvw6Y777NpD9j0R9j0L8AmmQ0YYyG2TTMFqWKIIKXitkGxyRzScIxHduLqk4Ytz8z+Nkb/MKXORnH0oAChZu8PoAkqdii90S58PZnocSE9k7HqeUJlxCqz+XjSJRNU8afu/j+SwmqPdje+uyPVY6+rhaLgeFI8qtqZGy/Z/630QiNrGnyOCk7GWmlxs1XVz0abm/HnJlZKP4tXOnRnG4QpsTg2tD1HtmXx3HC2IVUUfzSOkt8esy8AyTRkT0xAxuRUL0xzJMHZejamYEvs6cxZWFkaXaLx2V5HKu5sKBVvRCreJaTivAmpONeUivNLKq8mFf+KUnHfhFTINaVCfknltaRC3CtKxf85UvH7WsFcyPOnyk5PihVrXk13THJ0X/81/iZx9c9wi4AFwdip73HhuZ793XIk3ogcl68kR7L4OTQ539vSrpr1H22CxaUrKem5ZZABT1Xe2nmngdYxyfc7KiWk95akZ09s+2v2OCg9aPivQ3lZSXjDDl28YSlOwrsXvFt5g507AogEmyYU+winAlt4jgFMq2V3Udg+5AmVKovxoP/gLrLTBXu56y2sbjcnyxppH6jJsJ9X2Dc0Cpw27/3KImrenpLb/wA= \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/ext-res/plugs/devices.jpg b/RPI Code/Meross_2/MerossIot_/ext-res/plugs/devices.jpg new file mode 100644 index 0000000..df1efb2 Binary files /dev/null and b/RPI Code/Meross_2/MerossIot_/ext-res/plugs/devices.jpg differ diff --git a/RPI Code/Meross_2/MerossIot_/ext-res/plugs/test-env.jpg b/RPI Code/Meross_2/MerossIot_/ext-res/plugs/test-env.jpg new file mode 100644 index 0000000..6e4e9f9 Binary files /dev/null and b/RPI Code/Meross_2/MerossIot_/ext-res/plugs/test-env.jpg differ diff --git a/RPI Code/Meross_2/MerossIot_/ext-res/plugs/testdevices.jpg b/RPI Code/Meross_2/MerossIot_/ext-res/plugs/testdevices.jpg new file mode 100644 index 0000000..821659a Binary files /dev/null and b/RPI Code/Meross_2/MerossIot_/ext-res/plugs/testdevices.jpg differ diff --git a/RPI Code/Meross_2/MerossIot_/ext-res/tests/package.json b/RPI Code/Meross_2/MerossIot_/ext-res/tests/package.json new file mode 100644 index 0000000..819c106 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/ext-res/tests/package.json @@ -0,0 +1,12 @@ +{ + "name": "lab-master-switch", + "version": "0.0.1", + "email": "albertogeniola@gmail.com", + "main": "switch.js", + "bin": "./switch.js", + "dependencies": { + "minimist": ">=1.2.0", + "tplink-cloud-api": ">=0.3.8", + "uuid": ">=3.3.2" + } +} diff --git a/RPI Code/Meross_2/MerossIot_/ext-res/tests/switch.js b/RPI Code/Meross_2/MerossIot_/ext-res/tests/switch.js new file mode 100644 index 0000000..100ef0c --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/ext-res/tests/switch.js @@ -0,0 +1,55 @@ +const minimist = require('minimist'); +const { login } = require("tplink-cloud-api"); +const uuidV4 = require("uuid/v4"); + +const TPLINK_USER = process.env.TPLINK_USER; +const TPLINK_PASS = process.env.TPLINK_PASS; +const TPLINK_TERM = process.env.TPLINK_TERM || uuidV4(); + +async function main() { + var args = minimist(process.argv.slice(2)); + + // Check args + var username; + if ('username' in args) { + username = args.username; + } else if (TPLINK_USER) { + username = TPLINK_USER; + } else { + console.error("Missing username. You can either set the user name to the environment variable TPLINK_USER or pass it via --username parameter"); + process.exit(1) + } + + var password; + if ('password' in args) { + password = args.password; + } else if (TPLINK_PASS) { + password = TPLINK_PASS; + } else { + console.error("Missing password. You can either set the user name to the environment variable TPLINK_PASS or pass it via --password parameter"); + process.exit(1) + } + + if (!'toggle' in args) { + console.error("Missing required parameter: --toggle"); + process.exit(1) + } + + if (!'dev' in args) { + console.error("Missing required parameter: --dev"); + process.exit(1) + } + + // log in to cloud, return a connected tplink object + const tplink = await login(username, password, TPLINK_TERM); + + let deviceList = await tplink.getDeviceList(); + + console.log("Controlling", args.dev); + if (args.toggle==='on' || args.toggle===1) + await tplink.getHS110(args.dev).powerOn(); + else if (args.toggle === 'off' || args.toggle===0) + await tplink.getHS110(args.dev).powerOff(); +} + +main(); \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/__init__.py b/RPI Code/Meross_2/MerossIot_/meross_iot/__init__.py new file mode 100644 index 0000000..e00300d --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/__init__.py @@ -0,0 +1 @@ +name = "meross_iot" diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/api.py b/RPI Code/Meross_2/MerossIot_/meross_iot/api.py new file mode 100644 index 0000000..6ae2eaf --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/api.py @@ -0,0 +1,133 @@ +import base64 +import hashlib +import json +import random +import string +import time +import requests +from meross_iot.credentials import MerossCloudCreds + + +# Appears to be used as a part of the signature algorithm as constant "salt" (kinda useless) +_SECRET = "23x17ahWarFH6w29" +_MEROSS_URL = "https://iot.meross.com" +_LOGIN_URL = "%s%s" % (_MEROSS_URL, "/v1/Auth/Login") +_LOG_URL = "%s%s" % (_MEROSS_URL, "/v1/log/user") +_DEV_LIST = "%s%s" % (_MEROSS_URL, "/v1/Device/devList") + + +class MerossHttpClient: + _cloud_creds = None + + _email = None + _password = None + _authenticated = False + + def __init__(self, email, password): + self._email = email + self._password = password + + def _authenticated_post(self, + url, # type: str + params_data # type: dict + ): + + nonce = self._generate_nonce(16) + timestamp_millis = int(round(time.time() * 1000)) + login_params = self._encode_params(params_data) + + # Generate the md5-hash (called signature) + m = hashlib.md5() + datatosign = '%s%s%s%s' % (_SECRET, timestamp_millis, nonce, login_params) + m.update(datatosign.encode("utf8")) + md5hash = m.hexdigest() + + headers = { + "Authorization": "Basic" if self._cloud_creds is None else "Basic %s" % self._cloud_creds.token, + "vender": "Meross", + "AppVersion": "1.3.0", + "AppLanguage": "EN", + "User-Agent": "okhttp/3.6.0" + } + + payload = { + 'params': login_params, + 'sign': md5hash, + 'timestamp': timestamp_millis, + 'nonce': nonce + } + + # Perform the request. + r = requests.post(url, data=payload, headers=headers) + + # Check if that is ok. + if r.status_code != 200: + raise AuthenticatedPostException() + + # Save returned value + jsondata = r.json() + # print(jsondata) + + if jsondata["info"].lower() != "success": + raise AuthenticatedPostException() + + return jsondata["data"] + + @staticmethod + def _encode_params(parameters # type: dict + ): + jsonstring = json.dumps(parameters) + return str(base64.b64encode(jsonstring.encode("utf8")), "utf8") + + @staticmethod + def _generate_nonce(length): + return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length)) + + def _login(self): + try: + data = {"email": self._email, "password": self._password} + response_data = self._authenticated_post(_LOGIN_URL, params_data=data) + creds = MerossCloudCreds() + creds.token = response_data["token"] + creds.key = response_data["key"] + creds.user_id = response_data["userid"] + creds.user_email = response_data["email"] + self._cloud_creds = creds + self._authenticated = True + except Exception as e: + # TODO: LOG + return False + + try: + # The protocol does not really need the following call. However we want to be nice do it anyways + self._log() + except: + # TODO: LOG + pass + + return True + + def _log(self): + data = {'extra': {}, 'model': 'Android,Android SDK built for x86_64', 'system': 'Android', + 'uuid': '493dd9174941ed58waitForOpenWifi', 'vendor': 'Meross', 'version': '6.0'} + self._authenticated_post(_LOG_URL, params_data=data) + + def list_devices(self): + if not self._authenticated and not self._login(): + raise UnauthorizedException() + + return self._authenticated_post(_DEV_LIST, {}) + + def get_cloud_credentials(self): + if not self._authenticated and not self._login(): + raise UnauthorizedException() + + return self._cloud_creds + + +class AuthenticatedPostException(Exception): + pass + + +class UnauthorizedException(Exception): + pass diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/__init__.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/abilities.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/abilities.py new file mode 100644 index 0000000..19f1365 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/abilities.py @@ -0,0 +1,22 @@ +# Common abilities +ALL = 'Appliance.System.All' +ABILITY = 'Appliance.System.Ability' +REPORT = 'Appliance.System.Report' +ONLINE = 'Appliance.System.Online' +WIFI_LIST = 'Appliance.Config.WifiList' +DEBUG = 'Appliance.System.Debug' +TRACE = 'Appliance.Config.Trace' + +# Power plug/bulbs abilities +TOGGLE = 'Appliance.Control.Toggle' +TOGGLEX = 'Appliance.Control.ToggleX' +TRIGGER = 'Appliance.Control.Trigger' +TRIGGERX = 'Appliance.Control.TriggerX' +ELECTRICITY = 'Appliance.Control.Electricity' +CONSUMPTIONX = 'Appliance.Control.ConsumptionX' + +# Garage opener abilities +GARAGE_DOOR_STATE = 'Appliance.GarageDoor.State' + +# Bulbs-only abilities +LIGHT = 'Appliance.Control.Light' diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/client.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/client.py new file mode 100644 index 0000000..0169f9c --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/client.py @@ -0,0 +1,356 @@ +from hashlib import md5 +import string +from threading import Event, RLock +import json +import uuid as UUID +import ssl +import random +import copy +import time +from meross_iot.cloud.timeouts import SHORT_TIMEOUT +from meross_iot.cloud.exceptions.CommandTimeoutException import CommandTimeoutException +from meross_iot.logger import CONNECTION_MANAGER_LOGGER as l +import paho.mqtt.client as mqtt +from meross_iot.credentials import MerossCloudCreds +from meross_iot.cloud.client_status import ClientStatus +from meross_iot.cloud.connection import ConnectionStatusManager +from meross_iot.utilities.synchronization import AtomicCounter +from meross_iot.logger import NETWORK_DATA as networkl + + +def build_client_request_topic(client_uuid): + return "/appliance/%s/subscribe" % client_uuid + + +class PendingMessageResponse(object): + """ + This class is used as an Handle for mqtt messages that expect an ACK back. + When a callback is passed to the constructor, this object is configured as an "async" waiter. + Instead, passing a None callback, makes this object to act as a synchronously waiter. + It is meant to be used internally by the library, in order to handle ACK waiting and callback calling. + Note that this object is not thread safe. + """ + _message_id = None + _callback = None + _event = None + _response = None + _error = None + + def __init__(self, message_id, callback=None): + self._message_id = message_id + + # Only instantiate an event if no callback has been specified + if callback is None: + self._event = Event() + else: + self._callback = callback + + def wait_for_response(self, timeout=SHORT_TIMEOUT): + """ + This method blocks until an ACK/RESPONSE message is received for the corresponding message_id that it refers + to. Note that this method only works when the user is synchronously waiting for the response message. + This method raises an exception if invoked when a callback was specified in the constructor. + :param timeout: + :return: + """ + if self._event is None: + raise Exception("Error: you can invoke this method only if you don't use a callback (i.e. sync invocation)") + + # Wait until we receive the message. + # If timeout occurs, return failure and None as received message. + success = self._event.wait(timeout=timeout) + return success, self._response + + def notify_message_received(self, error=None, response=None): + self._response = copy.deepcopy(response) + self._error = error + + if self._event is not None: + self._event.set() + elif self._callback is not None: + try: + self._callback(self._error, self._response) + except: + l.exception("Unhandled error occurred while executing the callback") + + +class MerossCloudClient(object): + # Meross Cloud credentials, which are provided by the HTTP Api. + _cloud_creds = None + + # Connection info + connection_status = None + _domain = None + _port = 2001 + _ca_cert = None + + # App and client ID + _app_id = None + _client_id = None + + # Paho mqtt client object + _mqtt_client = None + + # Callback to be invoked every time a push notification is received from the MQTT broker + _push_message_callback = None + + # This dictionary is used to keep track of messages issued to the broker that are waiting for an ACK + # The key is the message_id, the value is the PendingMessageResponse object. + # Access to this resource is protected with exclusive locking + _pending_response_messages = None + _pending_responses_lock = None + + def __init__(self, + cloud_credentials, # type: MerossCloudCreds + push_message_callback=None, # type: callable + **kwords): + + self.connection_status = ConnectionStatusManager() + self._cloud_creds = cloud_credentials + self._pending_response_messages = dict() + self._pending_responses_lock = RLock() + self._push_message_callback = push_message_callback + self._subscription_count = AtomicCounter(0) + + if "domain" in kwords: + self._domain = kwords['domain'] + else: + self._domain = "iot.meross.com" + + # Lookup port and certificate for MQTT server + self._port = kwords.get('port', MerossCloudClient._port) + self._ca_cert = kwords.get('ca_cert', None) + + self._generate_client_and_app_id() + + # Password is calculated as the MD5 of USERID concatenated with KEY + md5_hash = md5() + clearpwd = "%s%s" % (self._cloud_creds.user_id, self._cloud_creds.key) + md5_hash.update(clearpwd.encode("utf8")) + hashed_password = md5_hash.hexdigest() + + # Start the mqtt client + self._mqtt_client = mqtt.Client(client_id=self._client_id, + protocol=mqtt.MQTTv311) # ex. app-id -> app:08d4c9f99da40203ebc798a76512ec14 + self._mqtt_client.on_connect = self._on_connect + self._mqtt_client.on_message = self._on_message + self._mqtt_client.on_disconnect = self._on_disconnect + self._mqtt_client.on_subscribe = self._on_subscribe + + # Avoid login if user_id is None + if self._cloud_creds.user_id is not None: + self._mqtt_client.username_pw_set(username=self._cloud_creds.user_id, + password=hashed_password) + self._mqtt_client.tls_set(ca_certs=self._ca_cert, certfile=None, + keyfile=None, cert_reqs=ssl.CERT_REQUIRED, + tls_version=ssl.PROTOCOL_TLS, + ciphers=None) + + def close(self): + l.info("Closing the MQTT connection...") + self._mqtt_client.disconnect() + l.debug("Waiting for the client to disconnect...") + self.connection_status.wait_for_status(ClientStatus.CONNECTION_DROPPED) + + # Starts a new thread that handles mqtt protocol and calls us back via callbacks + l.debug("Stopping the MQTT looper.") + self._mqtt_client.loop_stop(True) + + l.info("Client has been fully disconnected.") + + def connect(self): + """ + Starts the connection to the MQTT broker + :return: + """ + l.info("Initializing the MQTT connection...") + self._mqtt_client.connect(self._domain, self._port, keepalive=30) + self.connection_status.update_status(ClientStatus.CONNECTING) + + # Starts a new thread that handles mqtt protocol and calls us back via callbacks + l.debug("(Re)Starting the MQTT looper.") + self._mqtt_client.loop_stop(True) + self._mqtt_client.loop_start() + + l.debug("Waiting for the client to connect...") + self.connection_status.wait_for_status(ClientStatus.SUBSCRIBED) + l.info("Client connected to MQTT broker and subscribed to relevant topics.") + + # ------------------------------------------------------------------------------------------------ + # MQTT Handlers + # ------------------------------------------------------------------------------------------------ + def _on_disconnect(self, client, userdata, rc): + l.info("Disconnection detected. Reason: %s" % str(rc)) + + # When the mqtt connection is dropped, we need to reset the subscription counter. + self._subscription_count = AtomicCounter(0) + self.connection_status.update_status(ClientStatus.CONNECTION_DROPPED) + + # TODO: should we handle disconnection in some way at this level? + + if rc == mqtt.MQTT_ERR_SUCCESS: + pass + else: + client.loop_stop(True) + + def _on_unsubscribe(self): + l.debug("Unsubscribed from topic") + self._subscription_count.dec() + + def _on_subscribe(self, client, userdata, mid, granted_qos): + l.debug("Succesfully subscribed to topic. Subscription count: %d" % self._subscription_count.get()) + if self._subscription_count.inc() == 2: + self.connection_status.update_status(ClientStatus.SUBSCRIBED) + + def _on_connect(self, client, userdata, rc, other): + l.debug("Connected with result code %s" % str(rc)) + self.connection_status.update_status(ClientStatus.CONNECTED) + + self._client_response_topic = "/app/%s-%s/subscribe" % (self._cloud_creds.user_id, self._app_id) + self._user_topic = "/app/%s/subscribe" % self._cloud_creds.user_id + + # Subscribe to the relevant topics + l.debug("Subscribing to topics...") + client.subscribe(self._user_topic) + client.subscribe(self._client_response_topic) + + def _on_message(self, client, userdata, msg): + """ + This handler is called when a message is received from the MQTT broker, on the subscribed topics. + The current implementation checks the validity of the message itself, by verifying its signature. + + :param client: is the MQTT client reference, useful to respond back + :param userdata: metadata about the received data + :param msg: message that was received + :return: nothing, it simply handles the message accordingly. + """ + networkl.debug(msg.topic + " --> " + str(msg.payload)) + + try: + message = json.loads(str(msg.payload, "utf8")) + header = message['header'] + + message_hash = md5() + strtohash = "%s%s%s" % (header['messageId'], self._cloud_creds.key, header['timestamp']) + message_hash.update(strtohash.encode("utf8")) + expected_signature = message_hash.hexdigest().lower() + + if header['sign'] != expected_signature: + # TODO: custom exception for invalid signature + raise Exception('The signature did not match!') + + # Check if there is any thread waiting for this message or if there is a callback that we need to invoke. + # If so, do it here. + handle = None + with self._pending_responses_lock: + msg_id = header['messageId'] + handle = self._pending_response_messages.get(msg_id) + + from_myself = False + if handle is not None: + # There was a handle for this message-id. It means it is a response message to some + # request performed by the library itself. + from_myself = True + try: + l.debug("Calling handle event handler for message %s" % msg_id) + # Call the handler + handle.notify_message_received(error=None, response=message) + l.debug("Done handler for message %s" % msg_id) + + # Remove the message from the pending queue + with self._pending_responses_lock: + del self._pending_response_messages[msg_id] + except: + l.exception("Error occurred while invoking message handler") + + # Let's also catch all the "PUSH" notifications and dispatch them to the push_notification_callback. + if self._push_message_callback is not None and header['method'] == "PUSH" and 'namespace' in header: + self._push_message_callback(message, from_myself=from_myself) + + except Exception: + l.exception("Failed to process message.") + + # ------------------------------------------------------------------------------------------------ + # Protocol Handlers + # ------------------------------------------------------------------------------------------------ + def execute_cmd(self, dst_dev_uuid, method, namespace, payload, callback=None, timeout=SHORT_TIMEOUT): + start = time.time() + # Build the mqtt message we will send to the broker + message, message_id = self._build_mqtt_message(method, namespace, payload) + + # Register the waiting handler for that message + handle = PendingMessageResponse(message_id=message_id, callback=callback) + with self._pending_responses_lock: + self._pending_response_messages[message_id] = handle + + # Send the message to the broker + l.debug("Executing message-id %s, %s on %s command for device %s" % (message_id, method, + namespace, dst_dev_uuid)) + self._mqtt_client.publish(topic=build_client_request_topic(dst_dev_uuid), payload=message) + + # If the caller has specified a callback, we don't need to actrively wait for the message ACK. So we can + # immediately return. + if callback is not None: + return None + + # Otherwise, we need to wait until the message is received. + l.debug("Waiting for response to message-id %s" % message_id) + success, resp = handle.wait_for_response(timeout=timeout) + if not success: + raise CommandTimeoutException("A timeout occurred while waiting for the ACK: %d" % timeout) + + elapsed = time.time() - start + + l.debug("Message-id: %s, command %s-%s command for device %s took %s" % (message_id, method, + namespace, dst_dev_uuid, str(elapsed))) + return resp['payload'] + + # ------------------------------------------------------------------------------------------------ + # Protocol utilities + # ------------------------------------------------------------------------------------------------ + def _build_mqtt_message(self, method, namespace, payload): + """ + Sends a message to the Meross MQTT broker, respecting the protocol payload. + :param method: + :param namespace: + :param payload: + :return: + """ + + # Generate a random 16 byte string + randomstring = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(16)) + + # Hash it as md5 + md5_hash = md5() + md5_hash.update(randomstring.encode('utf8')) + messageId = md5_hash.hexdigest().lower() + timestamp = int(round(time.time())) + + # Hash the messageId, the key and the timestamp + md5_hash = md5() + strtohash = "%s%s%s" % (messageId, self._cloud_creds.key, timestamp) + md5_hash.update(strtohash.encode("utf8")) + signature = md5_hash.hexdigest().lower() + + data = { + "header": + { + "from": self._client_response_topic, + "messageId": messageId, # Example: "122e3e47835fefcd8aaf22d13ce21859" + "method": method, # Example: "GET", + "namespace": namespace, # Example: "Appliance.System.All", + "payloadVersion": 1, + "sign": signature, # Example: "b4236ac6fb399e70c3d61e98fcb68b74", + "timestamp": timestamp + }, + "payload": payload + } + strdata = json.dumps(data) + return strdata.encode("utf-8"), messageId + + def _generate_client_and_app_id(self): + md5_hash = md5() + rnd_uuid = UUID.uuid4() + md5_hash.update(("%s%s" % ("API", rnd_uuid)).encode("utf8")) + self._app_id = md5_hash.hexdigest() + self._client_id = 'app:%s' % md5_hash.hexdigest() diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/client_status.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/client_status.py new file mode 100644 index 0000000..5b97bed --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/client_status.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class ClientStatus(Enum): + INITIALIZED = 1 + CONNECTING = 2 + CONNECTED = 3 + SUBSCRIBED = 4 + CONNECTION_DROPPED = 5 diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/connection.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/connection.py new file mode 100644 index 0000000..c1cc576 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/connection.py @@ -0,0 +1,95 @@ +import datetime +from threading import RLock, Condition + +from meross_iot.cloud.client_status import ClientStatus +from meross_iot.cloud.exceptions.StatusTimeoutException import StatusTimeoutException +from meross_iot.cloud.timeouts import SHORT_TIMEOUT +from meross_iot.meross_event import ClientConnectionEvent +from meross_iot.logger import CONNECTION_MANAGER_LOGGER as l + + +class ConnectionStatusManager(object): + # The connection status of the device is represented by the following variable. + # It is protected by the following variable, called _client_connection_status_lock. + # The child classes should never change/access these variables directly, though. + _status = None + _lock = None + + # List of callbacks that should be called when an event occurs + _connection_event_callbacks = None + _connection_event_callbacks_lock = None + + # This condition object is used to synchronize multiple threads waiting for the connection to + # get into a specific state. + _status_condition = None + + def __init__(self): + self._connection_event_callbacks_lock = RLock() + self._connection_event_callbacks = [] + + self._lock = RLock() + self._status_condition = Condition(self._lock) + self._status = ClientStatus.INITIALIZED + + def get_status(self): + with self._status_condition: + return self._status + + def update_status(self, status): + old_status = None + new_status = None + with self._status_condition: + old_status = self._status + new_status = status + self._status = status + self._status_condition.notify_all() + + # If the connection status has changed, fire the event. + if old_status != new_status: + self._fire_connection_event(new_status) + + def check_status(self, expected_status): + with self._lock: + return expected_status == self._status + + def check_status_in(self, expected_statuses): + with self._lock: + return self._status in expected_statuses + + def wait_for_status(self, expected_status, timeout=SHORT_TIMEOUT): + start_time = datetime.datetime.now() + with self._status_condition: + while self._status != expected_status: + elapsed = datetime.datetime.now().timestamp() - start_time.timestamp() + to = timeout - elapsed + timeout_hit = to < 0 or not self._status_condition.wait(to) + + if timeout_hit: + # An error has occurred + raise StatusTimeoutException("Error while waiting for status %s. Last status is: %s" % + (expected_status, self._status)) + + # ------------------------------------------------------------------------------------------------ + # Event Handling + # ------------------------------------------------------------------------------------------------ + def register_connection_event_callback(self, callback): + with self._connection_event_callbacks_lock: + if callback not in self._connection_event_callbacks: + self._connection_event_callbacks.append(callback) + else: + l.debug("Callback was already registered.") + + def unregister_connection_event_callback(self, callback): + with self._connection_event_callbacks_lock: + if callback in self._connection_event_callbacks: + self._connection_event_callbacks.remove(callback) + else: + l.debug("Callback was present: nothing to unregister.") + + def _fire_connection_event(self, connection_status): + evt = ClientConnectionEvent(current_status=connection_status) + for c in self._connection_event_callbacks: + try: + c(evt) + except: + l.exception("Unhandled error occurred while executing event handler") diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/device.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/device.py new file mode 100644 index 0000000..47b7e6b --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/device.py @@ -0,0 +1,187 @@ +from abc import ABC, abstractmethod +from threading import RLock + +from meross_iot.cloud.exceptions.OfflineDeviceException import OfflineDeviceException +from meross_iot.cloud.timeouts import LONG_TIMEOUT, SHORT_TIMEOUT +from meross_iot.cloud.abilities import * +from meross_iot.logger import DEVICE_LOGGER as l +from meross_iot.meross_event import DeviceOnlineStatusEvent + + +class AbstractMerossDevice(ABC): + # Device status + lock to protect concurrent access + _state_lock = None + online = False + + # Device info and connection parameters + uuid = None + app_id = None + name = None + type = None + hwversion = None + fwversion = None + + # Cached list of abilities + _abilities = None + + # Cloud client: the object that handles mqtt communication with the Meross Cloud + __cloud_client = None + + # Data structure for firing events. + __event_handlers_lock = None + __event_handlers = None + + def __init__(self, cloud_client, device_uuid, **kwargs): + self.__cloud_client = cloud_client + self._state_lock = RLock() + self.__event_handlers_lock = RLock() + self.__event_handlers = [] + + self.uuid = device_uuid + + if "channels" in kwargs: + self._channels = kwargs['channels'] + + # Information about device + if "devName" in kwargs: + self.name = kwargs['devName'] + if "deviceType" in kwargs: + self.type = kwargs['deviceType'] + if "fmwareVersion" in kwargs: + self.fwversion = kwargs['fmwareVersion'] + if "hdwareVersion" in kwargs: + self.hwversion = kwargs['hdwareVersion'] + if "onlineStatus" in kwargs: + self.online = kwargs['onlineStatus'] == 1 + + def handle_push_notification(self, namespace, payload, from_myself=False): + # Handle the ONLINE push notification + # Leave the rest to the specific implementation + if namespace == ONLINE: + old_online_status = self.online + status = payload['online']['status'] + if status == 2: + with self._state_lock: + self.online = False + elif status == 1: + with self._state_lock: + self.online = True + else: + l.error("Unknown online status has been reported from the device: %d" % status) + + # If the online status changed, fire the corresponding event + if old_online_status != self.online: + evt = DeviceOnlineStatusEvent(self, self.online) + self.fire_event(evt) + else: + self._handle_push_notification(namespace, payload, from_myself=from_myself) + + def register_event_callback(self, callback): + with self.__event_handlers_lock: + if callback not in self.__event_handlers: + self.__event_handlers.append(callback) + else: + l.debug("The callback you tried to register is already present.") + pass + + def unregister_event_callback(self, callback): + with self.__event_handlers_lock: + if callback in self.__event_handlers: + self.__event_handlers.remove(callback) + else: + l.debug("The callback you tried to unregister is not present.") + pass + + def fire_event(self, eventobj): + for c in self.__event_handlers: + try: + c(eventobj) + except: + l.exception("Unhandled error occurred while executing the registered event-callback") + + @abstractmethod + def _handle_push_notification(self, namespace, payload, from_myself=False): + """ + Handles push messages for this device. This method should be implemented by the base class in order + to catch status changes issued by other clients (i.e. the Meross app on the user's device). + :param namespace: + :param message: + :param from_myself: boolean flag. When true, it means that the notification is generated in response to a + command that was issued by this client. When false, it means that another client generated the event. + :return: + """ + pass + + @abstractmethod + def get_status(self): + pass + + def execute_command(self, command, namespace, payload, callback=None, timeout=SHORT_TIMEOUT): + with self._state_lock: + # If the device is not online, what's the point of issuing the command? + if not self.online: + raise OfflineDeviceException("The device %s (%s) is offline. The command cannot be executed" % + (self.name, self.uuid)) + + return self.__cloud_client.execute_cmd(self.uuid, command, namespace, payload, callback=callback, timeout=timeout) + + def get_sys_data(self): + return self.execute_command("GET", ALL, {}) + + def get_abilities(self): + # TODO: Make this cached value expire after a bit... + if self._abilities is None: + self._abilities = self.execute_command("GET", ABILITY, {})['ability'] + return self._abilities + + def get_report(self): + return self.execute_command("GET", REPORT, {}) + + def get_wifi_list(self): + if WIFI_LIST in self.get_abilities(): + return self.execute_command("GET", WIFI_LIST, {}, timeout=LONG_TIMEOUT) + else: + l.error("This device does not support the WIFI_LIST ability") + return None + + def get_trace(self): + if TRACE in self.get_abilities(): + return self.execute_command("GET", TRACE, {}) + else: + l.error("This device does not support the TRACE ability") + return None + + def get_debug(self): + if DEBUG in self.get_abilities(): + return self.execute_command("GET", DEBUG, {}) + else: + l.error("This device does not support the DEBUG ability") + return None + + def supports_consumption_reading(self): + return CONSUMPTIONX in self.get_abilities() + + def supports_electricity_reading(self): + return ELECTRICITY in self.get_abilities() + + def supports_light_control(self): + return LIGHT in self.get_abilities() + + @abstractmethod + def get_power_consumption(self): + pass + + @abstractmethod + def get_electricity(self): + pass + + def __str__(self): + basic_info = "%s: %s (%s, HW %s, FW %s): " % ( + self.__class__.name, + self.name, + self.type, + self.hwversion, + self.fwversion + ) + + return basic_info diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/device_factory.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/device_factory.py new file mode 100644 index 0000000..0d12393 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/device_factory.py @@ -0,0 +1,19 @@ +from meross_iot.cloud.devices.power_plugs import GenericPlug +from meross_iot.cloud.devices.light_bulbs import GenericBulb +from meross_iot.cloud.devices.door_openers import GenericGarageDoorOpener + + +def build_wrapper( + cloud_client, + device_type, # type: str + device_uuid, # type: str + device_specs # type: dict +): + if device_type.startswith('msl') or device_type.startswith('mss560m'): + return GenericBulb(cloud_client, device_uuid=device_uuid, **device_specs) + elif device_type.startswith('mss'): + return GenericPlug(cloud_client, device_uuid=device_uuid, **device_specs) + elif device_type.startswith('msg'): + return GenericGarageDoorOpener(cloud_client, device_uuid=device_uuid, **device_specs) + else: + return GenericPlug(cloud_client, device_uuid=device_uuid, **device_specs) diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/__init__.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/door_openers.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/door_openers.py new file mode 100644 index 0000000..9cc37a4 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/door_openers.py @@ -0,0 +1,173 @@ +from meross_iot.cloud.abilities import * +from meross_iot.cloud.device import AbstractMerossDevice +from meross_iot.logger import POWER_PLUGS_LOGGER as l +from meross_iot.meross_event import DeviceDoorStatusEvent +from threading import Event + + +def parse_state(state): + if isinstance(state, str): + strstate = state.strip().lower() + if strstate == 'open': + return True + elif strstate == 'closed': + return False + else: + raise ValueError("Invalid state provided.") + + elif isinstance(state, bool): + return state + + elif isinstance(state, int): + if state == 0: + return False + elif state == 1: + return True + else: + return ValueError("Invalid state provided.") + + +def compare_same_states(state1, state2): + s1 = parse_state(state1) + s2 = parse_state(state2) + return s1 == s2 + + +class GenericGarageDoorOpener(AbstractMerossDevice): + # Channels + _channels = [] + + # Dictionary {channel_id (door) -> status} + _door_state = None + + def __init__(self, cloud_client, device_uuid, **kwords): + super(GenericGarageDoorOpener, self).__init__(cloud_client, device_uuid, **kwords) + + def get_status(self): + with self._state_lock: + if self._door_state is None: + self._get_status_impl() + + return self._door_state + + def _handle_push_notification(self, namespace, payload, from_myself=False): + def fire_garage_door_state_change(dev, channel_id, o_state, n_state, f_myself): + if o_state != n_state: + evt = DeviceDoorStatusEvent(dev=dev, channel_id=channel_id, door_state=n_state, + generated_by_myself=f_myself) + self.fire_event(evt) + + with self._state_lock: + if namespace == GARAGE_DOOR_STATE: + for door in payload['state']: + channel_index = door['channel'] + state = door['open'] == 1 + old_state = self._door_state[channel_index] + self._door_state[channel_index] = state + fire_garage_door_state_change(self, channel_index, old_state, state, from_myself) + + elif namespace == REPORT: + # For now, we simply ignore push notification of these kind. + # In the future, we might think of handling such notification by caching them + # and avoid the network round-trip when asking for power consumption (if the latest report is + # recent enough) + pass + + else: + l.error("Unknown/Unsupported namespace/command: %s" % namespace) + + def _get_status_impl(self): + if self._door_state is None: + self._door_state = {} + data = self.get_sys_data()['all'] + if 'digest' in data: + for c in data['digest']['garageDoor']: + self._door_state[c['channel']] = c['open'] == 1 + return self._door_state + + def _get_channel_id(self, channel): + # Otherwise, if the passed channel looks like the channel spec, lookup its array indexindex + if channel in self._channels: + return self._channels.index(channel) + + # if a channel name is given, lookup the channel id from the name + if isinstance(channel, str): + for i, c in enumerate(self.get_channels()): + if c['devName'] == channel: + return c['channel'] + + # If an integer is given assume that is the channel ID + elif isinstance(channel, int): + return channel + + # In other cases return an error + raise Exception("Invalid channel specified.") + + def _operate_door(self, channel, state, callback, wait_for_sensor_confirmation): + # If the door is already in the target status, do not execute the command. + already_in_state = False + with self._state_lock: + already_in_state = self.get_status()[channel] == state + + if already_in_state and callback is None: + l.info("Command was not executed: the door state is already %s" % ("open" if state else "closed")) + return + elif already_in_state and callback is not None: + callback(None, self._door_state[channel]) + return + + payload = {"state": {"channel": channel, "open": state, "uuid": self.uuid}} + if wait_for_sensor_confirmation: + door_event = None + if callback is None: + door_event = Event() + + def waiter(data): + self.unregister_event_callback(waiter) + if data.channel != channel: + return + if callback is None: + door_event.set() + else: + if not compare_same_states(data.door_state, state): + callback("Operation failed", data.door_state) + else: + callback(None, data.door_state) + + self.register_event_callback(waiter) + self.execute_command(command="SET", namespace=GARAGE_DOOR_STATE, payload=payload, callback=None) + + if callback is None: + door_event.wait() + current_state = self._door_state[channel] + if current_state != state: + raise Exception("Operation failed.") + + else: + self.execute_command(command="SET", namespace=GARAGE_DOOR_STATE, payload=payload, callback=callback) + + def open_door(self, channel=0, callback=None, ensure_opened=True): + c = self._get_channel_id(channel) + return self._operate_door(c, 1, callback=callback, wait_for_sensor_confirmation=ensure_opened) + + def close_door(self, channel=0, callback=None, ensure_closed=True): + c = self._get_channel_id(channel) + return self._operate_door(c, 0, callback=callback, wait_for_sensor_confirmation=ensure_closed) + + def get_channels(self): + return self._channels + + def get_power_consumption(self): + return None + + def get_electricity(self): + return None + + def __str__(self): + base_str = super().__str__() + with self._state_lock: + if not self.online: + return base_str + doors = "Doors -> " + doors += ",".join(["%d = %s" % (k, "OPEN" if v else "CLOSED") for k, v in enumerate(self.get_status())]) + return base_str + doors diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/light_bulbs.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/light_bulbs.py new file mode 100644 index 0000000..55196a4 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/light_bulbs.py @@ -0,0 +1,247 @@ +from meross_iot.cloud.abilities import * +from meross_iot.cloud.device import AbstractMerossDevice +from meross_iot.logger import BULBS_LOGGER as l +from meross_iot.meross_event import BulbSwitchStateChangeEvent, BulbLightStateChangeEvent + + +def to_rgb(rgb): + if rgb is None: + return None + elif isinstance(rgb, int): + return rgb + elif isinstance(rgb, tuple): + red, green, blue = rgb + elif isinstance(rgb, dict): + red = rgb['red'] + green = rgb['green'] + blue = rgb['blue'] + else: + raise Exception("Invalid value for RGB!") + + r = red << 16 + g = green << 8 + b = blue + + return r+g+b + + +class GenericBulb(AbstractMerossDevice): + # Bulb state: dictionary of channel-id/bulb-state + _state = None + + def __init__(self, cloud_client, device_uuid, **kwords): + self._state = {} + super(GenericBulb, self).__init__(cloud_client, device_uuid, **kwords) + + def _channel_control_impl(self, channel, status): + if TOGGLE in self.get_abilities(): + return self._toggle(status) + elif TOGGLEX in self.get_abilities(): + return self._togglex(channel, status) + else: + raise Exception("The current device does not support neither TOGGLE nor TOGGLEX.") + + def _update_state(self, channel, **kwargs): + with self._state_lock: + if channel not in self._state: + self._state[channel] = {} + for k in kwargs: + if k == 'onoff': + self._state[channel]['onoff'] = kwargs[k] + elif kwargs[k] is not None: + self._state[channel][k] = kwargs[k] + + def _toggle(self, status): + payload = {"channel": 0, "toggle": {"onoff": status}} + return self.execute_command("SET", TOGGLE, payload) + + def _togglex(self, channel, status): + payload = {'togglex': {"onoff": status, "channel": channel}} + return self.execute_command("SET", TOGGLEX, payload) + + def _handle_push_notification(self, namespace, payload, from_myself=False): + def fire_bulb_switch_state_change(dev, channel_id, o_state, n_state, f_myself): + if o_state != n_state: + evt = BulbSwitchStateChangeEvent(dev=dev, channel_id=channel_id, is_on=n_state, + generated_by_myself=f_myself) + self.fire_event(evt) + + def fire_bulb_light_state_change(dev, channel_id, o_state, n_state, f_myself): + if o_state != n_state: + evt = BulbLightStateChangeEvent(dev=dev, channel_id=channel_id, light_state=n_state, + generated_by_myself=f_myself) + self.fire_event(evt) + + with self._state_lock: + if namespace == TOGGLE: + on_status=payload['toggle']['onoff'] == 1 + channel_index = 0 + old_state = self._state.get(channel_index) + new_state = on_status + self._update_state(channel=0, onoff=on_status) + fire_bulb_switch_state_change(self, channel_id=0, o_state=old_state, n_state=new_state, + f_myself=from_myself) + + elif namespace == TOGGLEX: + if isinstance(payload['togglex'], list): + for c in payload['togglex']: + channel_index = c['channel'] + on_status = c['onoff'] == 1 + old_state = self._state.get(channel_index) + if old_state is not None: + old_state = old_state.get('onoff') + self._update_state(channel=channel_index, onoff=on_status) + fire_bulb_switch_state_change(self, channel_id=channel_index, o_state=old_state, + n_state=on_status, f_myself=from_myself) + elif isinstance(payload['togglex'], dict): + channel_index = payload['togglex']['channel'] + on_status = payload['togglex']['onoff'] == 1 + old_state = self._state.get(channel_index).get('onoff') + if old_state is not None: + old_state = old_state.get('onoff') + self._update_state(channel=channel_index, onoff=on_status) + fire_bulb_switch_state_change(self, channel_id=channel_index, o_state=old_state, n_state=on_status, + f_myself=from_myself) + + elif namespace == LIGHT: + channel_index = payload['light']['channel'] + old_state = self._state.get(channel_index) + new_state = payload['light'] + del new_state['channel'] + self._update_state(channel=channel_index, **new_state) + fire_bulb_light_state_change(self, channel_id=channel_index, o_state=old_state, n_state=new_state, + f_myself=from_myself) + + elif namespace == REPORT: + # For now, we simply ignore push notification of these kind. + # In the future, we might think of handling such notification by caching them + # and avoid the network round-trip when asking for power consumption (if the latest report is + # recent enough) + pass + + else: + l.error("Unknown/Unsupported namespace/command: %s" % namespace) + + def _get_status_impl(self): + res = {} + data = self.get_sys_data()['all'] + if 'digest' in data: + light_channel = data['digest']['light']['channel'] + res[light_channel] = data['digest']['light'] + + for c in data['digest']['togglex']: + res[c['channel']]['onoff'] = c['onoff'] == 1 + elif 'control' in data: + res[0]['onoff'] = data['control']['toggle']['onoff'] == 1 + return res + + def _get_channel_id(self, channel): + # Otherwise, if the passed channel looks like the channel spec, lookup its array indexindex + if channel in self._channels: + return self._channels.index(channel) + + # if a channel name is given, lookup the channel id from the name + if isinstance(channel, str): + for i, c in enumerate(self.get_channels()): + if c['devName'] == channel: + return c['channel'] + + # If an integer is given assume that is the channel ID + elif isinstance(channel, int): + return channel + + # In other cases return an error + raise Exception("Invalid channel specified.") + + def get_status(self, channel=0): + # In order to optimize the network traffic, we don't call the get_status() api at every request. + # On the contrary, we call it the first time. Then, the rest of the API will silently listen + # for state changes and will automatically update the self._state structure listening for + # messages of the device. + # Such approach, however, has a side effect. If we call TOGGLE/TOGGLEX and immediately after we call + # get_status(), the reported status will be still the old one. This is a race condition because the + # "status" RESPONSE will be delivered some time after the TOGGLE REQUEST. It's not a big issue for now, + # and synchronizing the two things would be inefficient and probably not very useful. + # Just remember to wait some time before testing the status of the item after a toggle. + c = self._get_channel_id(channel) + if self._state == {}: + current_state = self._get_status_impl() + with self._state_lock: + self._state = current_state + return self._state[c] + + def get_channels(self): + return self._channels + + def get_channel_status(self, channel): + ch_id = self._get_channel_id(channel) + c = self._get_channel_id(ch_id) + return self.get_status(c) + + def turn_on_channel(self, channel): + ch_id = self._get_channel_id(channel) + c = self._get_channel_id(ch_id) + return self._channel_control_impl(c, 1) + + def turn_off_channel(self, channel): + ch_id = self._get_channel_id(channel) + c = self._get_channel_id(ch_id) + return self._channel_control_impl(c, 0) + + def turn_on(self, channel=0): + ch_id = self._get_channel_id(channel) + c = self._get_channel_id(ch_id) + return self._channel_control_impl(c, 1) + + def turn_off(self, channel=0): + ch_id = self._get_channel_id(channel) + c = self._get_channel_id(ch_id) + return self._channel_control_impl(c, 0) + + def set_light_color(self, channel=0, rgb=None, luminance=100, temperature=100, capacity=5): + ch_id = self._get_channel_id(channel) + + # Convert the RGB to integer + color = to_rgb(rgb) + + payload = { + 'light': { + 'capacity': capacity, + 'channel': ch_id, + 'gradual': 0, + 'luminance': luminance, + 'rgb': color, + 'temperature': temperature + } + } + + # TODO: fix this as soon as we get hands on a real MSS560 and see what the payload looks like... + # handle mss560m differently + if self.type.lower() == 'mss560m': + pl = { + 'light': self.get_light_color() + } + pl['light']['channel'] = channel + pl['light']['luminance'] = luminance + payload = pl + + self.execute_command(command='SET', namespace=LIGHT, payload=payload) + + def get_light_color(self, channel=0): + ch_id = self._get_channel_id(channel) + return self.get_status(channel=ch_id) + + def get_power_consumption(self): + return None + + def get_electricity(self): + return None + + def __str__(self): + base_str = super().__str__() + with self._state_lock: + if not self.online: + return base_str + channels = "Channels: " + channels += ",".join(["%d = %s" % (k, "ON" if v else "OFF") for k, v in enumerate(self._state)]) + return base_str + "\n" + "\n" + channels diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/power_plugs.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/power_plugs.py new file mode 100644 index 0000000..020f0bd --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/devices/power_plugs.py @@ -0,0 +1,199 @@ +from meross_iot.cloud.abilities import * +from meross_iot.cloud.device import AbstractMerossDevice +from meross_iot.logger import POWER_PLUGS_LOGGER as l +from meross_iot.meross_event import DeviceSwitchStatusEvent + + +class GenericPlug(AbstractMerossDevice): + # Channels + _channels = [] + + # Dictionary {channel->status} + _state = {} + + def __init__(self, cloud_client, device_uuid, **kwords): + super(GenericPlug, self).__init__(cloud_client, device_uuid, **kwords) + + def _get_consumptionx(self): + return self.execute_command("GET", CONSUMPTIONX, {}) + + def _get_electricity(self): + return self.execute_command("GET", ELECTRICITY, {}) + + def _toggle(self, status, callback=None): + payload = {"channel": 0, "toggle": {"onoff": status}} + return self.execute_command("SET", TOGGLE, payload, callback=callback) + + def _togglex(self, channel, status, callback=None): + payload = {'togglex': {"onoff": status, "channel": channel}} + return self.execute_command("SET", TOGGLEX, payload, callback=callback) + + def _channel_control_impl(self, channel, status, callback=None): + if TOGGLE in self.get_abilities(): + return self._toggle(status, callback=callback) + elif TOGGLEX in self.get_abilities(): + return self._togglex(channel, status, callback=callback) + else: + raise Exception("The current device does not support neither TOGGLE nor TOGGLEX.") + + def _handle_push_notification(self, namespace, payload, from_myself=False): + def fire_switch_state_change(dev, channel_id, o_state, n_state, f_myself): + if o_state != n_state: + evt = DeviceSwitchStatusEvent(dev=dev, channel_id=channel_id, switch_state=n_state, + generated_by_myself=f_myself) + self.fire_event(evt) + + with self._state_lock: + if namespace == TOGGLE: + # Update the local state and fire the event only if the state actually changed + channel_index = 0 + old_switch_state = self._state.get(channel_index) + switch_state = payload['toggle']['onoff'] == 1 + self._state[channel_index] = switch_state + fire_switch_state_change(self, channel_index, old_switch_state, switch_state, from_myself) + + elif namespace == TOGGLEX: + if isinstance(payload['togglex'], list): + for c in payload['togglex']: + # Update the local state and fire the event only if the state actually changed + channel_index = c['channel'] + old_switch_state = self._state.get(channel_index) + switch_state = c['onoff'] == 1 + self._state[channel_index] = switch_state + fire_switch_state_change(self, channel_index, old_switch_state, switch_state, from_myself) + + elif isinstance(payload['togglex'], dict): + # Update the local state and fire the event only if the state actually changed + channel_index = payload['togglex']['channel'] + old_switch_state = self._state.get(channel_index) + switch_state = payload['togglex']['onoff'] == 1 + self._state[channel_index] = switch_state + fire_switch_state_change(self, channel_index, old_switch_state, switch_state, from_myself) + + elif namespace == REPORT or namespace == CONSUMPTIONX: + # For now, we simply ignore push notification of these kind. + # In the future, we might think of handling such notification by caching them + # and avoid the network round-trip when asking for power consumption (if the latest report is + # recent enough) + pass + + else: + l.error("Unknown/Unsupported namespace/command: %s" % namespace) + + def _get_status_impl(self): + res = {} + data = self.get_sys_data()['all'] + if 'digest' in data: + for c in data['digest']['togglex']: + res[c['channel']] = c['onoff'] == 1 + elif 'control' in data: + res[0] = data['control']['toggle']['onoff'] == 1 + return res + + def _get_channel_id(self, channel): + # Otherwise, if the passed channel looks like the channel spec, lookup its array indexindex + if channel in self._channels: + return self._channels.index(channel) + + # if a channel name is given, lookup the channel id from the name + if isinstance(channel, str): + for i, c in enumerate(self.get_channels()): + if c['devName'] == channel: + return c['channel'] + + # If an integer is given assume that is the channel ID + elif isinstance(channel, int): + return channel + + # In other cases return an error + raise Exception("Invalid channel specified.") + + def get_status(self, channel=0): + # In order to optimize the network traffic, we don't call the get_status() api at every request. + # On the contrary, we only call it the first time. Then, the rest of the API will silently listen + # for state changes and will automatically update the self._state structure listening for + # messages of the device. + # Such approach, however, has a side effect. If we call TOGGLE/TOGGLEX and immediately after we call + # get_status(), the reported status will be still the old one. This is a race condition because the + # "status" RESPONSE will be delivered some time after the TOGGLE REQUEST. It's not a big issue for now, + # and synchronizing the two things would be inefficient and probably not very useful. + # Just remember to wait some time before testing the status of the item after a toggle. + with self._state_lock: + c = self._get_channel_id(channel) + if self._state == {}: + self._state = self._get_status_impl() + return self._state[c] + + def get_power_consumption(self): + if CONSUMPTIONX in self.get_abilities(): + return self._get_consumptionx()['consumptionx'] + else: + # Not supported! + return None + + def get_electricity(self): + if ELECTRICITY in self.get_abilities(): + return self._get_electricity()['electricity'] + else: + # Not supported! + return None + + def get_channels(self): + return self._channels + + def get_channel_status(self, channel): + c = self._get_channel_id(channel) + return self.get_status(c) + + def turn_on_channel(self, channel, callback=None): + c = self._get_channel_id(channel) + return self._channel_control_impl(c, 1, callback=callback) + + def turn_off_channel(self, channel, callback=None): + c = self._get_channel_id(channel) + return self._channel_control_impl(c, 0, callback=callback) + + def turn_on(self, channel=0, callback=None): + c = self._get_channel_id(channel) + return self._channel_control_impl(c, 1, callback=callback) + + def turn_off(self, channel=0, callback=None): + c = self._get_channel_id(channel) + return self._channel_control_impl(c, 0, callback=callback) + + def get_usb_channel_index(self): + # Look for the usb channel + for i, c in enumerate(self.get_channels()): + if 'type' in c and c['type'] == 'USB': + return i + return None + + def enable_usb(self, callback=None): + c = self.get_usb_channel_index() + if c is None: + return + else: + return self.turn_on_channel(c, callback=callback) + + def disable_usb(self, callback=None): + c = self.get_usb_channel_index() + if c is None: + return + else: + return self.turn_off_channel(c, callback=callback) + + def get_usb_status(self): + c = self.get_usb_channel_index() + if c is None: + return + else: + return self.get_channel_status(c) + + def __str__(self): + base_str = super().__str__() + with self._state_lock: + if not self.online: + return base_str + channels = "Channels: " + channels += ",".join(["%d = %s" % (k, "ON" if v else "OFF") for k, v in enumerate(self._state)]) + return base_str + "\n" + "\n" + channels diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/CommandTimeoutException.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/CommandTimeoutException.py new file mode 100644 index 0000000..6786d2b --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/CommandTimeoutException.py @@ -0,0 +1,2 @@ +class CommandTimeoutException(Exception): + pass \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/ConnectionDroppedException.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/ConnectionDroppedException.py new file mode 100644 index 0000000..3a79ae1 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/ConnectionDroppedException.py @@ -0,0 +1,2 @@ +class ConnectionDroppedException(Exception): + pass \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/OfflineDeviceException.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/OfflineDeviceException.py new file mode 100644 index 0000000..41c4163 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/OfflineDeviceException.py @@ -0,0 +1,2 @@ +class OfflineDeviceException(Exception): + pass \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/StatusTimeoutException.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/StatusTimeoutException.py new file mode 100644 index 0000000..4ca4660 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/StatusTimeoutException.py @@ -0,0 +1,2 @@ +class StatusTimeoutException(Exception): + pass \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/__init__.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/exceptions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/timeouts.py b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/timeouts.py new file mode 100644 index 0000000..d2e77b0 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/cloud/timeouts.py @@ -0,0 +1,2 @@ +LONG_TIMEOUT = 30.0 # For wifi scan +SHORT_TIMEOUT = 10.0 # For any other command \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/credentials.py b/RPI Code/Meross_2/MerossIot_/meross_iot/credentials.py new file mode 100644 index 0000000..08225ff --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/credentials.py @@ -0,0 +1,5 @@ +class MerossCloudCreds(object): + token = None + key = None + user_id = None + user_email = None \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/logger.py b/RPI Code/Meross_2/MerossIot_/meross_iot/logger.py new file mode 100644 index 0000000..3ca6492 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/logger.py @@ -0,0 +1,24 @@ +import logging +from logging import StreamHandler +from sys import stdout + +ROOT_MEROSS_LOGGER = logging.getLogger("meross") +MANAGER_LOGGER = ROOT_MEROSS_LOGGER.getChild("manager") +CONNECTION_MANAGER_LOGGER = ROOT_MEROSS_LOGGER.getChild("connection") +NETWORK_DATA = ROOT_MEROSS_LOGGER.getChild("network_data") +POWER_PLUGS_LOGGER = ROOT_MEROSS_LOGGER.getChild("power_plugs") +BULBS_LOGGER = ROOT_MEROSS_LOGGER.getChild("light_bulbs") +DEVICE_LOGGER = ROOT_MEROSS_LOGGER.getChild("generic_device") + + +h = StreamHandler(stream=stdout) +ROOT_MEROSS_LOGGER.addHandler(h) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +h.setFormatter(formatter) + + +# Call this module to adjust the verbosity of the stream output. By default, only INFO is written to STDOUT log. +def set_log_level(root=logging.DEBUG, connection=logging.INFO, network=logging.INFO): + ROOT_MEROSS_LOGGER.setLevel(root) + CONNECTION_MANAGER_LOGGER.setLevel(connection) + NETWORK_DATA.setLevel(network) diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/manager.py b/RPI Code/Meross_2/MerossIot_/meross_iot/manager.py new file mode 100644 index 0000000..0f66268 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/manager.py @@ -0,0 +1,177 @@ +from meross_iot.api import MerossHttpClient +from meross_iot.cloud.client import MerossCloudClient +from meross_iot.cloud.device_factory import build_wrapper +from threading import RLock +from meross_iot.logger import MANAGER_LOGGER as l +from meross_iot.meross_event import DeviceOnlineStatusEvent + + +class MerossManager(object): + # HTTPClient object used to discover devices + _http_client = None + + # Dictionary of devices that are currently handled by this manager + # as UUID -> Device + _devices = None + + # Lock item used to protect access to the device collection + _devices_lock = None + + # Cloud credentials to be used against the Meross MQTT cloud + _cloud_creds = None + + _cloud_client = None + + # List of callbacks that should be called when an event occurs + _event_callbacks = None + _event_callbacks_lock = None + + def __init__(self, meross_email, meross_password): + self._devices_lock = RLock() + self._devices = dict() + self._event_callbacks_lock = RLock() + self._event_callbacks = [] + + self._http_client = MerossHttpClient(email=meross_email, password=meross_password) + self._cloud_creds = self._http_client.get_cloud_credentials() + + # Instantiate the mqtt cloud client + self._cloud_client = MerossCloudClient(cloud_credentials=self._cloud_creds, + push_message_callback=self._dispatch_push_notification) + self._cloud_client.connection_status.register_connection_event_callback(callback=self._fire_event) + + def start(self): + # Connect to the mqtt broker + self._cloud_client.connect() + self._discover_devices() + + def stop(self): + self._cloud_client.close() + + def register_event_handler(self, callback): + with self._event_callbacks_lock: + if callback in self._event_callbacks: + pass + else: + self._event_callbacks.append(callback) + + def unregister_event_handler(self, callback): + with self._event_callbacks_lock: + if callback not in self._event_callbacks: + pass + else: + self._event_callbacks.remove(callback) + + def get_device_by_uuid(self, uuid): + dev = None + with self._devices_lock: + dev = self._devices.get(uuid) + + return dev + + def get_device_by_name(self, name): + with self._devices_lock: + for k, v in self._devices.items(): + if v.name.lower() == name.lower(): + return v + return None + + def get_supported_devices(self): + return [x for k, x in self._devices.items()] + + def get_devices_by_kind(self, clazz): + res = [] + with self._devices_lock: + for k, v in self._devices.items(): + if isinstance(v, clazz): + res.append(v) + return res + + def get_devices_by_type(self, type_name): + res = [] + with self._devices_lock: + for k, v in self._devices.items(): + if v.type.lower() == type_name.lower(): + res.append(v) + return res + + def _dispatch_push_notification(self, message, from_myself=False): + """ + When a push notification is received from the MQTT client, it needs to be delivered to the + corresponding device. This method serves that scope. + :param message: + :param from_myself: boolean flag. When True, it means that the message received is related to a + previous request issued by this client. When is false, it means the message is related to some other + client. + :return: + """ + header = message['header'] # type: dict + payload = message['payload'] # type: dict + + # Identify the UUID of the target device by looking at the FROM field of the message header + dev_uuid = header['from'].split('/')[2] + device = None + with self._devices_lock: + device = self._devices.get(dev_uuid) + + if device is not None: + namespace = header['namespace'] + device.handle_push_notification(namespace, payload, from_myself=from_myself) + else: + # If we receive a push notification from a device that is not yet contained into our registry, + # it probably means a new one has just been registered with the meross cloud. + # Therefor, let's retrieve info from the HTTP api. + self._discover_devices() + + def _discover_devices(self, online_only=False): + """ + Discovers the devices that are visible via HTTP API and update the internal list of + managed devices accordingly. + :return: + """ + for dev in self._http_client.list_devices(): + online = dev['onlineStatus'] + + if online_only and online != 1: + # The device is not online, so we skip it. + continue + + # If the device we have discovered is not in the list we already handle, we need to add it. + self._handle_device_discovered(dev) + + return self._devices + + def _handle_device_discovered(self, dev): + d_type = dev['deviceType'] + d_uuid = dev['uuid'] + device = build_wrapper(device_type=d_type, device_uuid=d_uuid, cloud_client=self._cloud_client, + device_specs=dev) + + if device is not None: + # Check if the discovered device is already in the list of handled devices. + # If not, add it right away. Otherwise, ignore it. + is_new = False + new_dev = None + with self._devices_lock: + if d_uuid not in self._devices: + is_new = True + new_dev = build_wrapper(cloud_client=self._cloud_client, + device_type=d_type, device_uuid=d_uuid, device_specs=dev) + self._devices[d_uuid] = new_dev + + # If this is new device, register the event handler for it and fire the ONLINE event. + if is_new: + with self._event_callbacks_lock: + for c in self._event_callbacks: + new_dev.register_event_callback(c) + + evt = DeviceOnlineStatusEvent(new_dev, new_dev.online) + self._fire_event(evt) + + def _fire_event(self, eventobj): + for c in self._event_callbacks: + try: + c(eventobj) + except: + l.exception("An unhandled error occurred while invoking callback") + diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/meross_event.py b/RPI Code/Meross_2/MerossIot_/meross_iot/meross_event.py new file mode 100644 index 0000000..cdb5bc2 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/meross_event.py @@ -0,0 +1,103 @@ +from enum import Enum + + +class MerossEventType(Enum): + # Fired when the MQTT client connects/disconnects to the MQTT broker + CLIENT_CONNECTION = 10 + DEVICE_ONLINE_STATUS = 100 + DEVICE_SWITCH_STATUS = 1000 + DEVICE_BULB_SWITCH_STATE = 2000 + DEVICE_BULB_STATE = 2001 + GARAGE_DOOR_STATUS = 2000 + + +class MerossEvent(object): + event_type = None # type: MerossEventType + + def __init__(self, event_type): + self.event_type = event_type + + +class ClientConnectionEvent(MerossEvent): + status = None + + def __init__(self, current_status): + super(ClientConnectionEvent, self).__init__(MerossEventType.CLIENT_CONNECTION) + self.status = current_status + + +class DeviceOnlineStatusEvent(MerossEvent): + # Pointer to the device object + device = None + + # Current status of the device + status = None + + def __init__(self, dev, current_status): + super(DeviceOnlineStatusEvent, self).__init__(MerossEventType.DEVICE_ONLINE_STATUS) + self.device = dev + self.status = "online" if current_status else "offline" + + +class DeviceSwitchStatusEvent(MerossEvent): + # Pointer to the device object + device = None + + # Channel ID where the event occurred + channel_id = None + + # Current state of the switch where the event occurred + switch_state = None + + # Indicates id the event was generated by a command issued by the library itself. + # This is particularly useful in the case the user handler wants only to react + # to events generated by third parties. + generated_by_myself = None + + def __init__(self, dev, channel_id, switch_state, generated_by_myself): + super(DeviceSwitchStatusEvent, self).__init__(MerossEventType.DEVICE_SWITCH_STATUS) + self.device = dev + self.channel_id = channel_id + self.switch_state = switch_state + self.generated_by_myself = generated_by_myself + + +class DeviceDoorStatusEvent(MerossEvent): + # Pointer to the device object + device = None + + # Current state of the door + door_state = None + + # Channel related to the door controller + channel = None + + # Indicates id the event was generated by a command issued by the library itself. + # This is particularly useful in the case the user handler wants only to react + # to events generated by third parties. + generated_by_myself = None + + def __init__(self, dev, channel_id, door_state, generated_by_myself): + super(DeviceDoorStatusEvent, self).__init__(MerossEventType.GARAGE_DOOR_STATUS) + self.device = dev + self.channel = channel_id + self.door_state = "open" if door_state else "closed" + self.generated_by_myself = generated_by_myself + + +class BulbSwitchStateChangeEvent(MerossEvent): + def __init__(self, dev, channel_id, is_on, generated_by_myself): + super(BulbSwitchStateChangeEvent, self).__init__(MerossEventType.DEVICE_BULB_SWITCH_STATE) + self.device = dev + self.channel = channel_id + self.is_on = is_on + self.generated_by_myself = generated_by_myself + + +class BulbLightStateChangeEvent(MerossEvent): + def __init__(self, dev, channel_id, light_state, generated_by_myself): + super(BulbLightStateChangeEvent, self).__init__(MerossEventType.DEVICE_BULB_STATE) + self.device = dev + self.channel = channel_id + self.light_state = light_state + self.generated_by_myself = generated_by_myself diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/utilities/__init__.py b/RPI Code/Meross_2/MerossIot_/meross_iot/utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_2/MerossIot_/meross_iot/utilities/synchronization.py b/RPI Code/Meross_2/MerossIot_/meross_iot/utilities/synchronization.py new file mode 100644 index 0000000..ee76adc --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/meross_iot/utilities/synchronization.py @@ -0,0 +1,23 @@ +from threading import RLock + + +class AtomicCounter(object): + _lock = None + + def __init__(self, initialValue): + self._lock = RLock() + self._val = initialValue + + def dec(self): + with self._lock: + self._val -= 1 + return self._val + + def inc(self): + with self._lock: + self._val += 1 + return self._val + + def get(self): + with self._lock: + return self._val \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/requirements.txt b/RPI Code/Meross_2/MerossIot_/requirements.txt new file mode 100644 index 0000000..f85dbc1 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/requirements.txt @@ -0,0 +1,3 @@ +paho-mqtt>=1.3.1 +requests>=2.19.1 +retrying>=1.3.3 \ No newline at end of file diff --git a/RPI Code/Meross_2/MerossIot_/setup.py b/RPI Code/Meross_2/MerossIot_/setup.py new file mode 100644 index 0000000..0efcfcd --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/setup.py @@ -0,0 +1,43 @@ +from os import path + +from setuptools import setup, find_packages + +here = path.abspath(path.dirname(__file__)) + +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name='meross_iot', + version='0.3.1.3', + packages=find_packages(exclude=('tests',)), + url='https://github.com/albertogeniola/MerossIot', + license='MIT', + author='Alberto Geniola', + author_email='albertogeniola@gmail.com', + classifiers=[ + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 3', + 'Operating System :: OS Independent' + ], + description='A simple library to deal with Meross devices. At the moment MSS110, MSS210, MSS310, MSS310H ' + 'smart plugs and the MSS425E power strip. Other meross device might work out of the box with limited ' + 'functionality. Give it a try and, in case of problems, let the developer know by opening an issue ' + 'on Github.', + long_description=long_description, + long_description_content_type='text/markdown', + keywords='meross smartplug smartbulb iot mqtt domotic switch mss310 mss210 mss110 mss425e msl20 msg100', + project_urls={ + 'Documentation': 'https://github.com/albertogeniola/MerossIot', + 'Funding': 'https://donate.pypi.org', + 'Source': 'https://github.com/albertogeniola/MerossIot', + 'Tracker': 'https://github.com/albertogeniola/MerossIot/issues', + }, + install_requires=[ + 'paho-mqtt>=1.3.1', + 'requests>=2.19.1', + 'retrying>=1.3.3', + ], + python_requires='>=3.5', + test_suite='tests' +) diff --git a/RPI Code/Meross_2/MerossIot_/tests/__init__.py b/RPI Code/Meross_2/MerossIot_/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/Meross_2/MerossIot_/tests/readme.py b/RPI Code/Meross_2/MerossIot_/tests/readme.py new file mode 100644 index 0000000..a471b7b --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/tests/readme.py @@ -0,0 +1,143 @@ +from meross_iot.manager import MerossManager +from meross_iot.meross_event import MerossEventType +from meross_iot.cloud.devices.light_bulbs import GenericBulb +from meross_iot.cloud.devices.power_plugs import GenericPlug +from meross_iot.cloud.devices.door_openers import GenericGarageDoorOpener +import time +import os + + +EMAIL = os.environ.get('MEROSS_EMAIL') or "YOUR_MEROSS_CLOUD_EMAIL" +PASSWORD = os.environ.get('MEROSS_PASSWORD') or "YOUR_MEROSS_CLOUD_PASSWORD" + + +def event_handler(eventobj): + if eventobj.event_type == MerossEventType.DEVICE_ONLINE_STATUS: + print("Device online status changed: %s went %s" % (eventobj.device.name, eventobj.status)) + pass + + elif eventobj.event_type == MerossEventType.DEVICE_SWITCH_STATUS: + print("Switch state changed: Device %s (channel %d) went %s" % (eventobj.device.name, eventobj.channel_id, + eventobj.switch_state)) + elif eventobj.event_type == MerossEventType.CLIENT_CONNECTION: + print("MQTT connection state changed: client went %s" % eventobj.status) + + # TODO: Give example of reconnection? + + elif eventobj.event_type == MerossEventType.GARAGE_DOOR_STATUS: + print("Garage door is now %s" % eventobj.door_state) + + else: + print("Unknown event!") + + +if __name__ == '__main__': + # Initiates the Meross Cloud Manager. This is in charge of handling the communication with the remote endpoint + manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + + # Register event handlers for the manager... + manager.register_event_handler(event_handler) + + # Starts the manager + manager.start() + + # You can retrieve the device you are looking for in various ways: + # By kind + bulbs = manager.get_devices_by_kind(GenericBulb) + plugs = manager.get_devices_by_kind(GenericPlug) + door_openers = manager.get_devices_by_kind(GenericGarageDoorOpener) + all_devices = manager.get_supported_devices() + + # Print some basic specs about the discovered devices + print("All the bulbs I found:") + for b in bulbs: + print(b) + + print("All the plugs I found:") + for p in plugs: + print(p) + + print("All the garage openers I found:") + for g in door_openers: + print(g) + + print("All the supported devices I found:") + for d in all_devices: + print(d) + + # You can also retrieve devices by the UUID/name + # a_device = manager.get_device_by_name("My Plug") + # a_device = manager.get_device_by_uuid("My Plug") + + # Or you can retrieve all the device by the HW type + # all_mss310 = manager.get_devices_by_type("mss310") + + # ------------------------------ + # Let's play the garage openers. + # ------------------------------ + for g in door_openers: + if not g.online: + print("The garage controller %s seems to be offline. Cannot play with that..." % g.name) + continue + + print("Opening door %s..." % g.name) + g.open_door() + print("Closing door %s..." % g.name) + g.close_door() + + # --------------------- + # Let's play with bulbs + # --------------------- + for b in bulbs: # type: GenericBulb + if not b.online: + print("The bulb %s seems to be offline. Cannot play with that..." % b.name) + continue + + print("Let's play with bulb %s" % b.name) + if not b.supports_light_control(): + print("Too bad bulb %s does not support light control %s" % b.name) + else: + # Let's make it red! + b.set_light_color(rgb=(255, 0, 0)) + + b.turn_on() + time.sleep(1) + b.turn_off() + + # --------------------------- + # Let's play with smart plugs + # --------------------------- + for p in plugs: # type: GenericPlug + if not p.online: + print("The plug %s seems to be offline. Cannot play with that..." % p.name) + continue + + print("Let's play with smart plug %s" % p.name) + + channels = len(p.get_channels()) + print("The plug %s supports %d channels." % (p.name, channels)) + for i in range(0, channels): + print("Turning on channel %d of %s" % (i, p.name)) + p.turn_on_channel(i) + + time.sleep(1) + + print("Turning off channel %d of %s" % (i, p.name)) + p.turn_off_channel(i) + + usb_channel = p.get_usb_channel_index() + if usb_channel is not None: + print("Awesome! This device also supports USB power.") + p.enable_usb() + time.sleep(1) + p.disable_usb() + + if p.supports_electricity_reading(): + print("Awesome! This device also supports power consumption reading.") + print("Current consumption is: %s" % str(p.get_electricity())) + + # At this point, we are all done playing with the library, so we gracefully disconnect and clean resources. + print("We are done playing. Cleaning resources...") + manager.stop() + + print("Bye bye!") diff --git a/RPI Code/Meross_2/MerossIot_/tests/test_async.py b/RPI Code/Meross_2/MerossIot_/tests/test_async.py new file mode 100644 index 0000000..9a0d2eb --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/tests/test_async.py @@ -0,0 +1,61 @@ +import os +import time +import unittest +from meross_iot.manager import MerossManager +from threading import Thread, current_thread +import random +from meross_iot.logger import set_log_level +from logging import DEBUG, INFO + +from meross_iot.utilities.synchronization import AtomicCounter + +EMAIL = os.environ.get('MEROSS_EMAIL') +PASSWORD = os.environ.get('MEROSS_PASSWORD') + + +class TestMSS425ETest(unittest.TestCase): + def setUp(self): + self.counter = AtomicCounter(0) + set_log_level(INFO, INFO) + self.manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + self.manager.start() + + # Retrieves the list of supported devices + devices = self.manager.get_devices_by_type('mss425e') + if len(devices) > 0: + self.device = devices[0] + else: + raise Exception("Could not find device mss425e") + + def print_result(self, error, res): + # TODO: assertions + print("Error: %s, Result: %s" % (error, res)) + print("Counter=%d" % self.counter.inc()) + + # TODO: This fails. We need to investigate why. + """ + def test_async(self): + for i in range(0, 40): + op = bool(random.getrandbits(1)) + channel = random.randrange(0, len(self.device.get_channels())) + if not op: + self.device.turn_off_channel(channel, callback=self.print_result) + else: + self.device.turn_on_channel(channel, callback=self.print_result) + while self.counter.get() < 40: + time.sleep(1) + + def test_sync(self): + for i in range(0, 30): + print("Executing command %d" % i) + time.sleep(0.01) + channel = random.randrange(0, len(self.device.get_channels())) + self.device.turn_off_channel(channel) + time.sleep(0.01) + self.device.turn_on_channel(channel) + time.sleep(0.01) + print("Done command %d" % i) + """ + + def tearDown(self): + self.manager.stop() diff --git a/RPI Code/Meross_2/MerossIot_/tests/test_bulbs.py b/RPI Code/Meross_2/MerossIot_/tests/test_bulbs.py new file mode 100644 index 0000000..e0a0042 --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/tests/test_bulbs.py @@ -0,0 +1,54 @@ +from meross_iot.manager import MerossManager +import os +import time +import unittest +import random + + +EMAIL = os.environ.get('MEROSS_EMAIL') +PASSWORD = os.environ.get('MEROSS_PASSWORD') + + +class TestMSL120Test(unittest.TestCase): + def setUp(self): + self.manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + self.manager.start() + + # Retrieves the list of supported devices + devices = self.manager.get_devices_by_type('msl120') + if len(devices) > 0: + self.device = devices[0] + else: + raise Exception("Could not find device msl120") + + def test_power_cycle(self): + time.sleep(2) + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()['onoff']) + + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()['onoff']) + + self.device.turn_on() + time.sleep(2) + + self.assertTrue(self.device.get_status()['onoff']) + + def test_get_info(self): + state = self.device.get_status() + assert state is not None + + def test_set_light_color(self): + r = int(random.random() * 255) + g = int(random.random() * 255) + b = int(random.random() * 255) + self.device.set_light_color(channel=0, rgb=(r, g, b)) + time.sleep(5) + bulb_state = self.device.get_light_color(channel=0) + # TODO: RGB state is somehow normalized on the server side. We need to investigate the logic behind that... + + def tearDown(self): + self.device.turn_off() + self.manager.stop() diff --git a/RPI Code/Meross_2/MerossIot_/tests/test_http.py b/RPI Code/Meross_2/MerossIot_/tests/test_http.py new file mode 100644 index 0000000..a05e9dc --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/tests/test_http.py @@ -0,0 +1,22 @@ +import os +import unittest + +from meross_iot.api import MerossHttpClient + +EMAIL = os.environ.get('MEROSS_EMAIL') +PASSWORD = os.environ.get('MEROSS_PASSWORD') + + +class TestHttpMethods(unittest.TestCase): + def setUp(self): + self.client = MerossHttpClient(email=EMAIL, password=PASSWORD) + + def test_device_listing(self): + devices = self.client.list_devices() + assert devices is not None + assert len(devices) > 0 + + def test_supported_device_listing(self): + devices = self.client.list_devices() + assert devices is not None + assert len(devices) > 0 diff --git a/RPI Code/Meross_2/MerossIot_/tests/test_power_plugs.py b/RPI Code/Meross_2/MerossIot_/tests/test_power_plugs.py new file mode 100644 index 0000000..9b7feba --- /dev/null +++ b/RPI Code/Meross_2/MerossIot_/tests/test_power_plugs.py @@ -0,0 +1,212 @@ +import os +import time +import unittest +from meross_iot.manager import MerossManager + +EMAIL = os.environ.get('MEROSS_EMAIL') +PASSWORD = os.environ.get('MEROSS_PASSWORD') + + +class TestMSS210Test(unittest.TestCase): + def setUp(self): + self.manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + self.manager.start() + + # Retrieves the list of supported devices + devices = self.manager.get_devices_by_type('mss210') + if len(devices) > 0: + self.device = devices[0] + else: + raise Exception("Could not find device ms210") + + def test_power_cycle(self): + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()) + + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()) + + self.device.turn_on() + time.sleep(2) + + self.assertTrue(self.device.get_status()) + + def test_get_info(self): + state = self.device.get_status() + assert state is not None + + wifi_list = self.device.get_wifi_list() + assert wifi_list is not None + + trace = self.device.get_trace() + assert trace is not None + + debug = self.device.get_debug() + assert debug is not None + + def tearDown(self): + self.manager.stop() + + +class TestMSS310Test(unittest.TestCase): + def setUp(self): + self.manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + self.manager.start() + + # Retrieves the list of supported devices + devices = self.manager.get_devices_by_type('mss310') + if len(devices) > 0: + self.device = devices[0] + else: + raise Exception("Could not find device mss310") + + def test_power_cycle(self): + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()) + + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()) + + self.device.turn_on() + time.sleep(2) + + self.assertTrue(self.device.get_status()) + + def test_get_info(self): + consumption = self.device.get_power_consumption() + assert consumption is not None + + wifi_list = self.device.get_wifi_list() + assert wifi_list is not None + + trace = self.device.get_trace() + assert trace is not None + + debug = self.device.get_debug() + assert debug is not None + + abilities = self.device.get_abilities() + assert abilities is not None + + electricity = self.device.get_electricity() + assert electricity is not None + + def tearDown(self): + self.manager.stop() + + +class TestMSS425ETest(unittest.TestCase): + def setUp(self): + self.manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + self.manager.start() + + # Retrieves the list of supported devices + devices = self.manager.get_devices_by_type('mss425e') + if len(devices) > 0: + self.device = devices[0] + else: + raise Exception("Could not find device mss425e") + + def test_power_cycle(self): + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()) + + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()) + + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()) + + def test_usb(self): + self.device.enable_usb() + time.sleep(2) + self.assertTrue(self.device.get_usb_status()) + + self.device.enable_usb() + time.sleep(2) + self.assertTrue(self.device.get_usb_status()) + + def test_channels(self): + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()) + + # Test each channel one by one + for c in self.device.get_channels(): + self.device.turn_on_channel(c) + time.sleep(2) + self.assertTrue(self.device.get_channel_status(c)) + + time.sleep(2) + self.device.turn_off_channel(c) + time.sleep(2) + self.assertFalse(self.device.get_channel_status(c)) + + def test_get_info(self): + state = self.device.get_status() + assert state is not None + + wifi_list = self.device.get_wifi_list() + assert wifi_list is not None + + trace = self.device.get_trace() + assert trace is not None + + debug = self.device.get_debug() + assert debug is not None + + def tearDown(self): + self.manager.stop() + + +class TestMSS530HTest(unittest.TestCase): + def setUp(self): + self.manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD) + self.manager.start() + + # Retrieves the list of supported devices + devices = self.manager.get_devices_by_type('mss530h') + if len(devices) > 0: + self.device = devices[0] + else: + raise Exception("Could not find device mss530h") + + def test_power_cycle(self): + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()) + + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()) + + self.device.turn_on() + time.sleep(2) + self.assertTrue(self.device.get_status()) + + self.device.turn_off() + time.sleep(2) + self.assertFalse(self.device.get_status()) + + def test_get_info(self): + state = self.device.get_status() + assert state is not None + + wifi_list = self.device.get_wifi_list() + assert wifi_list is not None + + trace = self.device.get_trace() + assert trace is not None + + debug = self.device.get_debug() + assert debug is not None + + def tearDown(self): + self.manager.stop() diff --git a/RPI Code/pyparrot/pyparrot b/RPI Code/pyparrot/pyparrot deleted file mode 160000 index 85c6759..0000000 --- a/RPI Code/pyparrot/pyparrot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 85c67593dba9bb2df42681ba042e30fb0f3c719c diff --git a/RPI Code/pyparrot_/pyparrot/.gitignore b/RPI Code/pyparrot_/pyparrot/.gitignore new file mode 100644 index 0000000..ff77194 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/.gitignore @@ -0,0 +1,105 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + diff --git a/RPI Code/pyparrot_/pyparrot/LICENSE b/RPI Code/pyparrot_/pyparrot/LICENSE new file mode 100644 index 0000000..3459257 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 amymcgovern + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/RPI Code/pyparrot_/pyparrot/MANIFEST.in b/RPI Code/pyparrot_/pyparrot/MANIFEST.in new file mode 100644 index 0000000..058489b --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/MANIFEST.in @@ -0,0 +1,2 @@ +include pyparrot/commandsandsensors/*.xml +include pyparrot/utils/*.sdp diff --git a/RPI Code/pyparrot_/pyparrot/README.md b/RPI Code/pyparrot_/pyparrot/README.md new file mode 100644 index 0000000..190c393 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/README.md @@ -0,0 +1,69 @@ +# pyparrot +Python interface for Parrot Drones + +pyparrot was designed and implemented by Dr. Amy McGovern to program Parrot Mambo and Parrot Bebop 2 +drones using python. This interface was developed to teach K-20 STEM concepts +(programming, math, and more) by programming a drone to fly autonomously. +Anyone can use it who is interested in autonomous drone programming! + +# Installation, Quick-start, Documentation, FAQs + +Extensive documentation is available at [https://pyparrot.readthedocs.io](https://pyparrot.readthedocs.io) + +# Major updates and releases: +* 03/02/2019: Version 1.5.14: Fixed ffmpeg vision bug where it wasn't properly killing the ffmpeg subprocess +* 03/02/2019: Version 1.5.13: Added removal of old files in images directory by default to ffmpeg vision (can turn it off with a parameter) +* 02/19/2019: Version 1.5.12: Added pull request of wificonnection parameters and added ability to specify IP address (default uses mDNS still) +* 01/25/2019: Version 1.5.11: Added an example of using a cv2.namedWindow to show two vision windows (but it has issues on mac os 10.14 because it isn't a main thread) +* 10/29/2018: Version 1.5.10: Updated the groundcam to not break on disconnect with BLE. Also have updated documentation with slides from workshop and windows FAQs. +* 10/21/2018: Version 1.5.9: Fixed the wifiConnection without mDNS to work for Bebop (mDNS still works on bebop!). Verified that mambo and bebop work now with latest firmware. +* 10/19/2018: Version 1.5.8: Parrot broke mDNS in firmware 3.0.26 (and ftp is still broken). Disabled the groundcam and hard-coded the IP address and ports for the mambo. Long term we want mDNS back. tested backwards compatiblity on older firmware and it works. +* 10/13/2018: Version 1.5.7: Parrot released a security update/firmware upgrade to 3.0.25 that breaks ftp login for Mambo. pyparrot now allows the mambo to still connect without errors but the groundcam will not work until we hear from parrot. Also added example for joystick for the parrot swing from victor804 +* 10/05/2018: Version 1.5.6: Removed a bug in the library on pypi where an old file was hanging around +* 09/19/2018: Version 1.5.5: Added joystink demo for swing (thanks Victor804) +* 09/06/2018: Version 1.5.4: Removed wait in indoor mode for bebop 1 +* 09/06/2018: Version 1.5.3: Added indoor mode for bebop 1 +* 8/30/2018: Version 1.5.2: Updated camera pan_tilt for Bebop 1 (thanks Victor804) +* 8/21/2018: Version 1.5.1: fixed small fix for typo in minidrones (for swing) +* 8/18/2018: Version 1.5.0: major update to suppport parrot swing drones (thank you Victor804). This does break a small backwards compatibility in that you need to import Mambo from Minidrone instead of Mambo. Everything else remains the same. +* 8/9/2018: Version 1.4.31: hard-coded name for vision stream on windows +* 8/9/2018: Version 1.4.30: fixed vision bug in windows using VLC (tempfile issues) and also made fps a parameter for VLC vision +* 7/16/2018: Version 1.4.29: added bebop user sensor callback function to match mambo +* 7/15/2018: Version 1.4.28: added bebop battery state to default state variables (was in the dictionary only before) +* 7/13/2018: Version 1.4.27: updated Mambo() initialization to not require address for wifi mode and also updated groundcam demo for Mambo +* 7/12/2018: Version 1.4.26: added new Bebop commands (mostly setting max limits for the bebop) +* 7/11/2018: Version 1.4.25: fixed groundcam pictures for Mambo +* 7/8/2018: Version 1.4.24: switched tempfile to back to NamedTemporaryFile in DroneVisionGUI due to OS incompatibilities +* 7/8/2018: Version 1.4.23: switched tempfile to SpooledTemporaryFile in DroneVisionGUI to make it faster (uses memory instead of disk) +* 7/6/2018: Version 1.4.22: Added a wait in flat_trim for Bebop until it is received (optional) +* 7/5/2018: Version 1.4.21: Added max_tilt and max_altitude to the Bebop commands. +* 7/4/2018: Version 1.4.20: While move_relative is implemented, it seems to have a firmware bug so DO NOT USE. +* 7/4/2018: Version 1.4.19: Added move_relative command to the Bebop API. For now, only dx, dy, and dradians should be used as there seems to be a bug internal to the firmware on dz. +* 6/17/2018: Version 1.4.18 Added landed button status to the Drone Vision GUI for safety in user code +* 6/16/2018: Version 1.4.17 Added flat trim to mambo also +* 6/16/2018: Version 1.4.16 Added flat trim to bebop +* 6/15/2018: Version 1.4.15 Removed a stray print, updated documentation, cast turn_degrees arguments to an int in Mambo. +* 6/11/2018: Version 1.4.14 Added bebop sdp file to the release on pip +* 6/7/2018: Version 1.4.13 Fixed duration in PCMD to use milliseconds instead of integer seconds +* 6/7/2018: Version 1.4.12 Added an option to fly_direct to allow the command to be sent once +* 6/6/2018: Version 1.4.11 Fixed a stray import statment not fixed from the move to pip +* 5/31/2018: Version 1.4.10 Documentation updated significantly and moved to readthedocs +* 5/30/2018: Version 1.4.7 and 1.4.8 and 1.4.9 fixed scripts location to release find_mambo script and added readthedocs documents +* 5/29/2018: Version 1.4.6 Accepted fixes for Bebop 1 compatibility +* 5/28/2018: Version 1.4.5 Fixed imports for new pypi structure and added xml files to pypi. +* 5/25/2018: Version 1.4.3. Uploaded to pypi so pyparrot can now be installed directory from pip. Updated documentation for new vision. +* 5/23/2018: Updated function (contributed) to download pictures from Mambo's downward facing camera. +* 3/25/2018: Added DroneVisionGUI which is a version of the vision that shows the video stream (for Bebop or Mambo) in real time. +* 2/22/2018: Version 1.3.2. Updated DroneVision to make the vision processing faster. Interface changed to only have the user call open_vision and close_vision (and not start_video_buffering) +* 2/10/2018: Version 1.3.1. Updated DroneVision to work on Windows. +* 2/8/2018: Version 1.3. Vision is working for both the Mambo and Bebop in a general interface called DroneVision. Major documenation updates as well. +* 2/6/2018: Updated Mambo to add speed settings for tilt & vertical. Needed for class. +* 2/4/2018: Unofficial updates to add ffmpeg support to the vision (will make an official release with examples soon) +* 12/09/2017: Version 1.2. Mambo now gives estimated orientation using quaternions. Bebop now streams vision, which is accessible via VLC or other video clients. Coming soon: opencv hooks into the vision. +* 12/02/2017: Version 1.1. Fixed sensors with multiple values for Mambo and Bebop. +* 11/26/2017: Initial release, version 1.0. Working wifi and BLE for Mambo, initial flight for Bebop. + +# Programming and using your drones responsibly + +It is your job to program and use your drones responsibly! We are not responsible for any losses or damages of your drones or injuries. Please fly safely and obey all laws. + diff --git a/RPI Code/pyparrot_/pyparrot/coursework/droneMapGUI.py b/RPI Code/pyparrot_/pyparrot/coursework/droneMapGUI.py new file mode 100644 index 0000000..a7ebe60 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/coursework/droneMapGUI.py @@ -0,0 +1,322 @@ +""" +GUI for AI class using drones. Allows you to quickly create a map of +a room with obstacles for navigation and search. + +Amy McGovern dramymcgovern@gmail.com +""" + +from tkinter import * +import numpy as np +from tkinter import filedialog +import os +import pickle + +class DroneGUI: + def __init__(self): + self.root = Tk() + self.room_map = None + self.obstacle_ids = None + self.factor = None + self.obstacle_color = "#7575a3" + self.goal_color = "green" + self.start_color = "red" + self.start_id = 2 + self.goal_id = 3 + self.obstacle_id = 1 + + def translate_click(self, event): + """ + Translate the click event into room coordinates and map coordinates + :param event: + :return: + """ + print("clicked at", event.x, event.y) + center_x = event.x + center_y = event.y + + # calculate the lower left corner of the box + factor = 10 * self.scale_val + lower_x = int(center_x / factor) * factor + lower_y = int(center_y / factor) * factor + #print("lower x and y are ", lower_x, lower_y) + + # translate the click into the map + map_x = int(center_x / self.factor) + map_y = self.room_map.shape[1] - int(center_y / self.factor) - 1 + #print("map x and y are ", map_x, map_y) + + return (center_x, center_y, lower_x, lower_y, map_x, map_y) + + def toggle_obstacle_click(self, event): + """ + Toggle an obstacle with left button clicks + + :param event: the tkinter event + :return: nothing + """ + (center_x, center_y, lower_x, lower_y, map_x, map_y) = self.translate_click(event) + + + if (self.room_map[map_x, map_y] == 0): + self.draw_obstacle(lower_x,lower_y, size=self.factor, color=self.obstacle_color, map_x=map_x, map_y=map_y) + self.room_map[map_x, map_y] = self.obstacle_id + else: + #print("Deleting obstacle ", self.obstacle_ids[map_x, map_y]) + self.clear_obstacle(map_x, map_y) + self.room_map[map_x, map_y] = 0 + + def change_obstacle_type_click(self, event): + """ + Right click brings up a menu to let you choose what kind of obstacle this is + + :param event: + :return: + """ + print("In popup menu") + popup_menu = Menu(self.root, tearoff=0) + popup_menu.add_command(label="Set to start", command=lambda: self.draw_start_click(event)) + popup_menu.add_command(label="Set to goal", command=lambda: self.draw_goal_click(event)) + popup_menu.add_command(label="Remove", command=lambda: self.toggle_obstacle_click(event)) + + popup_menu.post(event.x, event.y) + + + def draw_goal_click(self, event): + """ + Draw a green box for a goal at button 1 clicks + + :param event: + :return: + """ + (center_x, center_y, lower_x, lower_y, map_x, map_y) = self.translate_click(event) + + # clear whatever was there + self.clear_obstacle(map_x, map_y) + self.room_map[map_x, map_y] = 0 + + # and save the click into the map + self.room_map[map_x, map_y] = self.goal_id + self.draw_obstacle(lower_x,lower_y, self.factor, color=self.goal_color, map_x=map_x, map_y=map_y) + + + def draw_start_click(self, event): + """ + Draw a red box for the start + + :param event: + :return: + """ + (center_x, center_y, lower_x, lower_y, map_x, map_y) = self.translate_click(event) + + # clear whatever was there + self.clear_obstacle(map_x, map_y) + self.room_map[map_x, map_y] = 0 + + # and save the click into the map + self.room_map[map_x, map_y] = self.start_id + self.draw_obstacle(lower_x,lower_y, self.factor, color=self.start_color, map_x=map_x, map_y=map_y) + + + def draw_obstacle(self, x, y, size, color, map_x, map_y): + # draw the rectangle + obs_id = self.room_canvas.create_rectangle(x, y, x + size, y + size, fill=color) + #print("Obstacle id is ", obs_id) + self.obstacle_ids[map_x, map_y] = obs_id + + def clear_obstacle(self, map_x, map_y): + # draw the rectangle + self.room_canvas.delete(self.obstacle_ids[map_x, map_y]) + self.obstacle_ids[map_x, map_y] = 0 + + def set_scale(self): + try: + self.scale_val = int(self.scale.get()) + except: + self.scale_val = 1 + + # set the factor used for drawing translations + self.factor = 10 * self.scale_val + + def create_room(self): + """ + Create the window with the room grid + + Uses the scale parameter set from the first gui window to decide how big the boxes are (scale must be an int) + + Draws a grid with black lines every 10 * scale pixels (e.g. every decimeter) and then draws a thicker + line every meter (e.g. every 10 lines) + + :return: + """ + length = float(self.length.get()) + height = float(self.height.get()) + + # initialize the internal map + self.room_map = np.zeros((int(length * 10), int(height * 10))) + self.obstacle_ids = np.zeros((int(length * 10), int(height * 10)), dtype='int') + print(self.room_map.shape) + + print("Length is %0.1f and height is %0.1f" % (length, height)) + + self.set_scale() + self.draw_room(length, height) + + def draw_room(self, length, height): + # each pixel is scale * 1 cm so multiply by 100 to get the width/height from the meters + canvas_width = int(length * 100 * self.scale_val) + canvas_height = int(height * 100 * self.scale_val) + + # create the blank canvas + room = Toplevel(self.root) + + # put the menu into the room + # menu code mostly from + # https://www.python-course.eu/tkinter_menus.php + menu = Menu(room) + room.config(menu=menu) + filemenu = Menu(menu) + menu.add_cascade(label="File", menu=filemenu) + filemenu.add_command(label="Save Map", command=self.save_file_menu) + filemenu.add_separator() + filemenu.add_command(label="Exit", command=self.root.quit) + + helpmenu = Menu(menu) + menu.add_cascade(label="Help", menu=helpmenu) + helpmenu.add_command(label="About...", command=self.about_menu) + + # draw the room + self.room_canvas = Canvas(room, width=canvas_width, height=canvas_height, bg="#ffffe6") + self.room_canvas.pack() + + # how to draw a checkered canvas from + # https://www.python-course.eu/tkinter_canvas.php + # vertical lines at an interval of "line_distance" pixel + line_distance = 10 * self.scale_val + for x in range(line_distance, canvas_width, line_distance): + if (x % (line_distance * 10) == 0): + self.room_canvas.create_line(x, 0, x, canvas_height, fill="red", width=2) + else: + self.room_canvas.create_line(x, 0, x, canvas_height, fill="black") + + # horizontal lines at an interval of "line_distance" pixel + for y in range(line_distance, canvas_height, line_distance): + if (y % (line_distance * 10) == 0): + self.room_canvas.create_line(0, y, canvas_width, y, fill="red", width=2) + else: + self.room_canvas.create_line(0, y, canvas_width, y, fill="black") + + # bind the button clicks to draw out the map + self.room_canvas.bind("", self.toggle_obstacle_click) + self.room_canvas.bind("", self.change_obstacle_type_click) + + # add in the obstacles (if any exist already) + (xs, ys) = np.nonzero(self.room_map) + factor = 10 * self.scale_val + for i, x in enumerate(xs): + y = ys[i] + lower_x = x * factor + lower_y = (self.room_map.shape[1] - y - 1) * factor + if (self.room_map[x, y] == 1): + self.draw_obstacle(lower_x, lower_y, factor, color="#7575a3", map_x=x, map_y=y) + elif (self.room_map[x, y] == 2): + self.draw_obstacle(lower_x, lower_y, factor, color="red", map_x=x, map_y=y) + elif (self.room_map[x, y] == 3): + self.draw_obstacle(lower_x, lower_y, factor, color="green", map_x=x, map_y=y) + + def draw_map_from_file(self): + width = self.room_map.shape[1] / 10.0 + length = self.room_map.shape[0] /10.0 + #print("length and width of loaded room are ", length,width) + #print("Scale is ", self.scale_val) + self.draw_room(length, width) + + def open_file_menu(self): + """ + Load a map from a file + :return: + """ + filename = filedialog.askopenfilename(initialdir=os.getcwd(), + title="Select map file", + filetypes=(("map files", "*.map"), ("all files", "*.*"))) + + fp = open(filename, "rb") + self.scale_val = pickle.load(fp) + self.room_map = pickle.load(fp) + #print("scale val is ", self.scale_val) + #print("room map is ", self.room_map) + fp.close() + self.draw_map_from_file() + + def save_file_menu(self): + """ + Bring up a save file dialog and then save + :return: + """ + filename = filedialog.asksaveasfile(initialdir=os.getcwd(), + title="Save map file", + defaultextension=".map") + print("saving to ", filename.name) + fp = open(filename.name, "wb") + pickle.dump(self.scale_val, fp) + pickle.dump(self.room_map, fp) + fp.close() + + + def about_menu(self): + pass + + def draw_initial_gui(self): + """ + Draws the intial GUI that lets you make a new room + or load one from a file + :return: + """ + + # menu code mostly from + # https://www.python-course.eu/tkinter_menus.php + menu = Menu(self.root) + self.root.config(menu=menu) + filemenu = Menu(menu) + menu.add_cascade(label="File", menu=filemenu) + filemenu.add_command(label="Open Map", command=self.open_file_menu) + filemenu.add_separator() + filemenu.add_command(label="Exit", command=self.root.quit) + + helpmenu = Menu(menu) + menu.add_cascade(label="Help", menu=helpmenu) + helpmenu.add_command(label="About...", command=self.about_menu) + + # draw the request to create a new room + Label(self.root, text="Enter the size of the room you are flying in (decimals to tenths)").grid(row=0, columnspan=2) + Label(self.root, text="Length (x) (meters)").grid(row=1) + Label(self.root, text="Height (y) (meters)").grid(row=2) + Label(self.root, text="1 cm = _ pixels").grid(row=3) + + # the entry boxes + self.length = Entry(self.root) + self.length.grid(row=1, column=1) + + self.height = Entry(self.root) + self.height.grid(row=2, column=1) + + self.scale = Entry(self.root) + self.scale.grid(row=3, column=1) + + # action buttons + Button(self.root, text='Quit', command=self.root.quit).grid(row=4, column=0, pady=4) + Button(self.root, text='Create room', command=self.create_room).grid(row=4, column=1, pady=4) + + def go(self): + """ + Start the main GUI loop + :return: + """ + mainloop() + + + + +if __name__ == "__main__": + gui = DroneGUI() + gui.draw_initial_gui() + gui.go() diff --git a/RPI Code/pyparrot_/pyparrot/demoMambo.py b/RPI Code/pyparrot_/pyparrot/demoMambo.py new file mode 100644 index 0000000..255def2 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/demoMambo.py @@ -0,0 +1,59 @@ +""" +Demo the direct flying for the python interface + +Author: Amy McGovern +""" + +from pyparrot.Minidrone import Mambo + +# you will need to change this to the address of YOUR mambo +mamboAddr = "d0:3a:69:b9:e6:5a" + +# make my mambo object +# remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect +mambo = Mambo(mamboAddr, use_wifi=False) + +print("trying to connect") +success = mambo.connect(num_retries=3) +print("connected: %s" % success) + +if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(2) + mambo.ask_for_state_update() + mambo.smart_sleep(2) + + print("taking off!") + mambo.safe_takeoff(5) + """ + print("Flying direct: going forward (positive pitch)") + mambo.fly_direct(roll=0, pitch=50, yaw=0, vertical_movement=0, duration=1) + + print("Showing turning (in place) using turn_degrees") + mambo.turn_degrees(90) + mambo.smart_sleep(2) + mambo.turn_degrees(-90) + mambo.smart_sleep(2) + + print("Flying direct: yaw") + mambo.fly_direct(roll=0, pitch=0, yaw=50, vertical_movement=0, duration=1) + + print("Flying direct: going backwards (negative pitch)") + mambo.fly_direct(roll=0, pitch=-50, yaw=0, vertical_movement=0, duration=0.5) + + print("Flying direct: roll") + mambo.fly_direct(roll=50, pitch=0, yaw=0, vertical_movement=0, duration=1) + + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=50, duration=1) + + print("Flying direct: going around in a circle (yes you can mix roll, pitch, yaw in one command!)") + mambo.fly_direct(roll=25, pitch=0, yaw=50, vertical_movement=0, duration=3) + """ + print("landing") + mambo.safe_land(5) + mambo.smart_sleep(5) + + print("disconnect") + mambo.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/docs/Makefile b/RPI Code/pyparrot_/pyparrot/docs/Makefile new file mode 100644 index 0000000..8c10188 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = pyparrot +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/RPI Code/pyparrot_/pyparrot/docs/OK2018Afternoon.pdf b/RPI Code/pyparrot_/pyparrot/docs/OK2018Afternoon.pdf new file mode 100644 index 0000000..a200439 Binary files /dev/null and b/RPI Code/pyparrot_/pyparrot/docs/OK2018Afternoon.pdf differ diff --git a/RPI Code/pyparrot_/pyparrot/docs/OK2018Morning.pdf b/RPI Code/pyparrot_/pyparrot/docs/OK2018Morning.pdf new file mode 100644 index 0000000..c8c8e75 Binary files /dev/null and b/RPI Code/pyparrot_/pyparrot/docs/OK2018Morning.pdf differ diff --git a/RPI Code/pyparrot_/pyparrot/docs/about.rst b/RPI Code/pyparrot_/pyparrot/docs/about.rst new file mode 100644 index 0000000..99e8a81 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/about.rst @@ -0,0 +1,28 @@ +.. title:: About pyparrot + +.. about: + +About the pyparrot project +========================== + +About pyparrot +----------------- + +Pyparrot was developed by `Dr. Amy McGovern `_ with the support of +the `University of Oklahoma `_ and +the `Kiss Institute for Practical Robotics `_. The original goal was to teach children to program +using educational programs like `botball `_. The pyparrot project has been adopted by groups +around the world and has been used in both K-12 settings and at the university level. + +Educational Programs Using pyparrot +----------------------------------- + +If you would like to be added to this list, please email dramymcgovern @ gmail.com (without the spaces). + +* `Botball `_ +* `University of Oklahoma School of Computer Science `_ +* `Talenteahaus `_ +* `Tech Garage `_ +* `St Eugene College `_ +* `KIPR/botball `_ + diff --git a/RPI Code/pyparrot_/pyparrot/docs/bebopcommands.rst b/RPI Code/pyparrot_/pyparrot/docs/bebopcommands.rst new file mode 100644 index 0000000..f90bbef --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/bebopcommands.rst @@ -0,0 +1,164 @@ +.. title:: Bebop Commands and Sensors + +.. bebopcommands: + +Bebop Commands and Sensors +============================== + +Bebop commands +-------------- + +Each of the public commands available to control the bebop is listed below with its documentation. +The code is also well documented and you can also look at the API through readthedocs. +All of the functions preceeded with an underscore are intended to be internal functions and are not listed below. + +Creating a Bebop object +^^^^^^^^^^^^^^^^^^^^^^^ + +``Bebop(drone_type="Bebop2")`` create a Bebop object with an optional drone_type argument that can be used to create +a bebop one or bebop 2 object. Default is Bebop 2. Note, there is limited support for the original bebop since +I do not own one for testing. + +Connecting and disconnecting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``connect(num_retries)`` connect to the Bebop's wifi services. This performs a handshake. +This can take several seconds to ensure the connection is working. +You can specify a maximum number of re-tries. Returns true if the connection suceeded or False otherwise. + +``disconnect()`` disconnect from the wifi connection + +Takeoff and landing +^^^^^^^^^^^^^^^^^^^ + +``takeoff()`` Sends a single takeoff command to the bebop. This is not the recommended method. + +``safe_takeoff(timeout)`` This is the recommended method for takeoff. It sends a command and then checks the +sensors (via flying state) to ensure the bebop is actually taking off. Then it waits until the bebop is +flying or hovering to return. It will timeout and return if the time exceeds timeout seconds. + +``land()`` Sends a single land command to the bebop. This is not the recommended method. + +``safe_land(timeout)`` This is the recommended method to land the bebop. Sends commands +until the bebop has actually reached the landed state. It will timeout and return if the time exceeds timeout seconds. + +Flying +^^^^^^ + +``flip(direction)`` Sends the flip command to the bebop. Valid directions to flip are: front, back, right, left. + +``fly_direct(roll, pitch, yaw, vertical_movement, duration)`` Fly the bebop directly using the +specified roll, pitch, yaw, and vertical movements. The commands are repeated for duration seconds. +Note there are currently no sensors reported back to the user to ensure that these are working but hopefully +that is addressed in a future firmware upgrade. Each value ranges from -100 to 100 and is essentially a percentage +and direction of the max_tilt (for roll/pitch) or max_vertical_speed (for vertical movement). + +``move_relative(dx, dy, dz, dradians)`` Moves the bebop a relative number of meters in x (forward/backward, +forward is positive), y (right/left, right is positive), dz (up/down, positive is down), and dradians. +If you use this command INDOORS, make sure you either have FULL GPS coverage or NO GPS coverage (e.g. cover the front of the bebop + with tin foil to keep it from getting a lock). If it has mixed coverage, it randomly flies at high speed in random +directions after the command executes. This is a known issue in Parrot's firmware and they state that a fix is coming. + +``set_max_altitude(altitude)`` Set the maximum allowable altitude in meters. +The altitude must be between 0.5 and 150 meters. + +``set_max_distance(distance)`` Set max distance between the takeoff and the drone in meters. +The distance must be between 10 and 2000 meters. + +``enable_geofence(value)`` If geofence is enabled, the drone won't fly over the given max distance. +Valid value: 1 if the drone can't fly further than max distance, 0 if no limitation on the drone should be done. + +``set_max_tilt(tilt)`` Set the maximum allowable tilt in degrees for the drone (this limits speed). +The tilt must be between 5 (very slow) and 30 (very fast) degrees. + +``set_max_tilt_rotation_speed(speed)`` Set the maximum allowable tilt rotation speed in degree/s. +The tilt rotation speed must be between 80 and 300 degree/s. + +``set_max_vertical_speed(speed)`` Set the maximum allowable vertical speed in m/s. +The vertical speed must be between 0.5 and 2.5 m/s. + +``set_max_rotation_speed(speed)`` Set the maximum allowable rotation speed in degree/s. +The rotation speed must be between 10 and 200 degree/s. + +``set_flat_trim(duration=0)`` Tell the Bebop to run with a flat trim. If duration > 0, waits for the comand to be acknowledged + +``set_hull_protection(present)`` Set the presence of hull protection (only for bebop 1). +The value must be 1 if hull protection is present or 0 if not present. This is only useful for the bebop 1. + +``set_indoor(is_outdoor)`` Set the bebop 1 (ignored on bebop 2) to indoor or outdoor mode. +The value must be 1 if bebop 1 is outdoors or 0 if it is indoors. This is only useful for the bebop 1. + +Pausing or sleeping in a thread safe manner +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``smart_sleep(seconds)`` This sleeps the number of seconds (which can be a floating point) but wakes for all +wifi notifications. You should use this instead of time.sleep to be consistent with the mambo but it is not +required (whereas time.sleep() will break a mambo using BLE). + +Video camera +^^^^^^^^^^^^ + +``start_video_stream()``: tells the bebop to start streaming the video. These are really intended to be +called within the DroneVision or DroneVisionGUI functions and not directly by the user (but you can call +them directly if you are writing your own vision routines). + +``stop_video_stream()``: tells the bebop to stop streaming the video. Same as above: intended to be called +by the DroneVision or DroneVisionGUI routines. + +``set_video_stream_mode(mode)``: set the video mode to one of three choices: "low_latency", +"high_reliability", "high_reliability_low_framerate". low_latency is the default. + +``pan_tilt_camera(tilt_degrees, pan_degrees)``: Send the command to pan/tilt the camera by the specified number of degrees in pan/tilt. +Note, this only seems to work in small increments. Use pan_tilt_velocity to get the camera to look straight downward. + +``pan_tilt_camera_velocity(self, tilt_velocity, pan_velocity, duration=0)``: Send the command to tilt the camera by +the specified number of degrees per second in pan/tilt. This function has two modes. First, if duration is 0, +the initial velocity is sent and then the function returns (meaning the camera will keep moving). +If duration is greater than 0, the command executes for that amount of time and then sends a stop command to +the camera and then returns. + +``set_picture_format(format)``: Change the picture format to raw, jpeg, snapshot or jpeg_fisheye. + +``set_white_balance(type)``: Change the type of white balance between: auto, tungsten, daylight, cloudy or cool_white. + +``set_exposition(value)``: Change the image exposition between -1.5 and 1.5. + +``set_saturation(value)``: Change the image saturation between -100 and 100. + +``set_timelapse(enable, interval)``: To start a timelapse set enable at 1 and an interval between 8 and 300 sec. +To stop the timelapse just set enable to 0. + +``set_video_stabilization(mode)``: Change the video stabilization between 4 modes: roll_pitch, pitch, roll, none. + +``set_video_recording(mode)``: Change the video recording mode between quality and time. + +``set_video_framerate(framerate)``: Change the video framerate between: 24_FPS, 25_FPS or 30_FPS. + +``set_video_resolutions(type)``: Change the video resolutions for stream and rec between rec1080_stream480, rec720_stream720. + +Sensor commands +^^^^^^^^^^^^^^^ + +``ask_for_state_update()`` This sends a request to the bebop to send back ALL states. The data returns +fairly quickly although not instantly. The bebop already has a sensor refresh rate of 10Hz but not all sensors are sent +automatically. If you are looking for a specific sensor that is not automatically sent, you can call this but I don't +recommend sending it over and over. Most of the sensors you need should be sent at either the 10Hz rate or as an event +is called that triggers that sensor. + +Bebop sensors +------------- + +All of the sensor data that is passed back to the Bebop is saved in a python dictionary. As needed, other variables +are stored outside the dictionary but you can get everything you need from the dictionary itself. All of the data +is stored in the BebopSensors class. + +The easiest way to interact with the sensors is to call: + +``bebop.set_user_sensor_callback(function, args)``. This sets a user callback function with optional +arguments that is called each time a sensor is updated. The refresh rate on wifi is 10Hz. + +The sensors are: + +* battery (defaults to 100 and stays at that level until a real reading is received from the drone) +* flying_state: This is updated as frequently as the drone sends it out and can be one of "landed", "takingoff", "hovering", "flying", "landing", "emergency", "usertakeoff", "motor_ramping", "emergency_landing". These are the values as specified in `ardrone3.xml `_. +* sensors_dict: all other sensors are saved by name in a dictionary. The names come from the `ardrone3.xml `_ and `common.xml `_. diff --git a/RPI Code/pyparrot_/pyparrot/docs/conf.py b/RPI Code/pyparrot_/pyparrot/docs/conf.py new file mode 100644 index 0000000..2aaccb7 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/conf.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# pyparrot documentation build configuration file, created by +# sphinx-quickstart on Tue May 29 13:55:14 2018. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +# the mock stuff was borrowed from hagelslag to help make things work on readthedocs +from mock import Mock as MagicMock + +class Mock(MagicMock): + @classmethod + def __getattr__(cls, name): + return Mock() + +MOCK_MODULES = ['numpy', 'scipy', 'zeroconf', 'cv2', 'untangle', 'bluepy', 'bluepy.btle', + 'ipaddress', 'queue', 'http.server', 'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', + 'PyQt5.QtWidgets'] +sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) + +sys.path.insert(0, os.path.abspath('..')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = ['.rst', '.md'] +#source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'pyparrot' +copyright = '2018, Amy McGovern' +author = 'Amy McGovern' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.5' +# The full version, including alpha/beta/rc tags. +release = '1.5.3' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'nature' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = {} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pyparrotdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'pyparrot.tex', 'pyparrot Documentation', + 'Amy McGovern', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pyparrot', 'pyparrot Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'pyparrot', 'pyparrot Documentation', + author, 'pyparrot', 'One line description of project.', + 'Miscellaneous'), +] + + + diff --git a/RPI Code/pyparrot_/pyparrot/docs/contact.rst b/RPI Code/pyparrot_/pyparrot/docs/contact.rst new file mode 100644 index 0000000..1f00017 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/contact.rst @@ -0,0 +1,20 @@ +.. title:: Contact the pyparrot developers + +.. contact: + +Contact the pyparrot developers +=============================== + +Contribute +---------- + +We welcome your contributions via bug report or pull request. + +* Issue Tracker: ``_ +* Pull requests: ``_ +* Source Code: ``_ + +Support +------- +If you are having issues, please let us know by reporting issues on GitHub using the issue +tracker ``_. diff --git a/RPI Code/pyparrot_/pyparrot/docs/faq.rst b/RPI Code/pyparrot_/pyparrot/docs/faq.rst new file mode 100644 index 0000000..56562d3 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/faq.rst @@ -0,0 +1,62 @@ +.. title:: Frequently Asked Questions + +.. faq: + +Frequently Asked Questions +==================================== + +Below is a list of common errors and how to fix them. + +Vision isn't showing anything on the minidrone +---------------------------------------------- + +The Minidrone camera puts itself into a "resting" state after not flying for several minutes. To solve this, you +either need to fly again (a simple takeoff and landing will suffice) or reboot the minidrone and reconnect. + +I'm using windows and my drone gives me lots of timeout errors +--------------------------------------------------------------- +This is a windows security setting and it can be fixed. Go into your windows firewall settings (control panel, +system and security, allow a program through Windows Firewall) and change the settings +for python.exe to be allowed through the firewall for both home/private networks and public networks. Your sensors will +suddenly be able to send data to your machine and safe_land will start working again as well as any sensors! + +My drone does takeoff and landing but nothing else +-------------------------------------------------- + +Likely you have the remote controller on and attached! For some reason, if the remote is on, +it will allow the python code to takeoff & land but no other commands will work. +Turn off the remote and your code should work fine! + +Errors connecting to the drone +------------------------------ + +There are two common errors that I see when flying. One requires the drone to reboot and one requires the +computer controlling the drone to reboot. + +Connection failed +^^^^^^^^^^^^^^^^^ +If you fail to connect to the drone, you will see an error message like this: + +:: + + connection failed: did you remember to connect your machine to the Drone's wifi network? + +The most likely cause is that you forgot to connect to the drone's wifi. If you tried to connect, +sometimes that connection fails. Try again or let the connection sit for a minute and try your program again. + +If you are on the wifi but you get connection refused errors, reboot the drone. + +Address in use +^^^^^^^^^^^^^^ + +The second common error is about the address being in use, as shown below. + +:: + + OSError: [Errno 48] Address already in use + + +There are two ways to fix this, depending on the issue. It is possible you tried to run a second program while +you still had a first program running. If this is the case, make sure you stop all of your minidrone programs and then +restart only one. If you are not running a second minidrone program, then the solution is to reboot. This sometimes +happens due to the program crashing before it releases the socket. diff --git a/RPI Code/pyparrot_/pyparrot/docs/gettingstartedslides.rst b/RPI Code/pyparrot_/pyparrot/docs/gettingstartedslides.rst new file mode 100644 index 0000000..a603748 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/gettingstartedslides.rst @@ -0,0 +1,13 @@ +.. title:: Slides from Workshops Teaching PyParrot + +.. gettingstartedslides: + +Workshop Materials and Slides +============================== +We have taught several workshops on using pyparrot. As we continue to teach and develop materials for +pyparrot, we will continue to share the curriculum materials here. Since the 2018 OK workshop slides supercede the +GCER 2018 slides, we only share the 2018 OK Workshop slides for now. + +* Morning slides from the OK workshop (focus on initial setup, initial flying) :download:`OK2018Morning.pdf ` + +* Afternoon slides from the OK workshop (learning to use sensors, extra slides on vision though we did not discuss at workshop) :download:`OK2018Afternoon.pdf `. \ No newline at end of file diff --git a/RPI Code/pyparrot_/pyparrot/docs/index.rst b/RPI Code/pyparrot_/pyparrot/docs/index.rst new file mode 100644 index 0000000..f7a42fb --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/index.rst @@ -0,0 +1,38 @@ +.. pyparrot documentation master file, created by + sphinx-quickstart on Tue May 29 13:16:36 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to pyparrot's documentation! +==================================== +pyparrot was designed by Dr. Amy McGovern to program Parrot Minidrone (primarily Mambo FPV but Swing is also +supported) and Parrot Bebop (1 or 2) drones using Python. This interface was developed to teach kids of all ages (K-20) STEM +concepts (programming, math, and more) by having them program a drone +to fly autonomously. Anyone can use it who is interested in +autonomous drone programming! + +Main documentation +========================== + +.. toctree:: + :maxdepth: 3 + + installation.rst + quickstartminidrone.rst + quickstartbebop.rst + gettingstartedslides.rst + minidronecommands.rst + bebopcommands.rst + vision.rst + faq.rst + contact.rst + about.rst + license.rst + modules.rst + +Indices and tables +================== + +* :ref:`modindex` is a good place to start if you want to read API docs +* :ref:`genindex` of ALL functions (warning, this is huge and overwhelming) +* :ref:`search` diff --git a/RPI Code/pyparrot_/pyparrot/docs/installation.rst b/RPI Code/pyparrot_/pyparrot/docs/installation.rst new file mode 100644 index 0000000..c21e62b --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/installation.rst @@ -0,0 +1,189 @@ +.. title:: Installation + +.. installation: + +Installation +=============== + +You have two choices for installing pyparrot: using the ``source`` code directly or downloading with ``pip``. +**Note** Pyparrot will only work with python 3. This choice was made because the support for multi-threaded +programs is improved in python 3. + +Requirements +------------ + +The choice of related packages is dependent on your choice of drone (Mambo, Mambo FPV, Bebop 1 or 2, Swing, Anafi) and +to the operating system that you will be using to develop. + +Hardware/Drone requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* **Parrot Mambo FPV**: If you have a Mambo FPV (e.g. you have the camera), you can use the wifi interface. The wifi interface will work on Mac, Linux, or Windows. + +* **Parrot Mambo Fly or Code**: If you have a Mambo without the camera, you will use the BLE interface. pyparrot currently only supports Linux for BLE. The BLE interface was developed on a Raspberry Pi 3 Model B but it has been tested on other Linux machines. + +* **Parrot Swing**: To use the Swing you will use the BLE interface. + +* **Parrot Bebop 2**: The Bebop interface was tested on a Bebop 2 using a laptop with wifi (any wifi enabled device should work). + +* **Parrot Bebop 1**: A Bebop 1 will also work with any wifi enabled device. + +* **Parrot Anafi**: Per the development board, the Anafi should work with very minor changes. I will work to officially suppport it once the SDK from parrot is released for it. + +Software requirements +^^^^^^^^^^^^^^^^^^^^^ + +Software requirements are listed below by type of connection to the drone. + +* All drones: Python 3 + + I use the ``_:: installer and package manager for python. Note, when you +install anaconda, install the Visual Studio option, especially if you have windows. Otherwise you will need to install +Visual Studio separately. The zeroconf package (listed below) requires developer tools because it needs to be compiled. + +* All drones: untangle package (this is used to parse the xml files in the parrot SDK) + + +:: + + pip install untangle + + + +* Vision: If you intend to process the camera files, you will need to install opencv and then either ffmpeg +or VLC. I installed ffmpeg using brew for the mac but apt-get on linux should also work. For VLC, you MUST install +the actual `VLC `_ To install zeroconf software do the following: + +:: + + pip install zeroconf + + +* BLE connection: pybluez (note this is ONLY for support without the camera!) This is ONLY supported on linux. +To install the BLE software do the following: + +:: + + sudo apt-get install bluetooth + sudo apt-get install bluez + sudo apt-get install python-bluez + + +Note it is also possible that you will need to install bluepy (if it isn't already there). These commands should do it: + +:: + + sudo apt-get install python-pip libglib2.0-dev + sudo pip install bluepy + sudo apt-get update + + + +Installing From Source +---------------------- + +First download pyparrot by cloning the repository from ``_ The instructions for this are below. + + +:: + + git clone https://github.com/amymcgovern/pyparrot + cd pyparrot + + +Make sure you install the necessary other packages (wifi or BLE, vision, etc) as specified above. + +Installing From Pip +------------------- + +To install from pip, type + + +:: + + pip install pyparrot + + +Make sure you install the necessary other packages (wifi or BLE, vision, etc) as specified above. + +Installation guide for windows users who might need more help +------------------------------------------------------------- + +Thank you to @JackdQuinn for contributing this. + +Make sure you install **Visual Studio** either using Anaconda or by downloading it from Microsoft. Note that Visual +Studio is free but it is required for compilation of the wifi module zeroconf, and specifically of the netifaces +module that zeroconf requires. It is a very large download if you chose to do it outside of anaconda so you will +want to start that download first. + +If you install python without anaconda, when you install choose Special install Python and +click add python to path (this will clear up some command line call issues). + +Again, if you chose regular python and not anaconda, you can check installation by typing py in the windows command line. + +:: + + py + +Once you are sure that python started, you will want to quit python. type: ``quit()`` to exit python + +:: + + quit() + +If you chose to use anaconda, bring up the anaconda menu and open an anaconda prompt to verify that it installed. +The rest of the instructions depend on whether you chose python or anaconda for your installation. If you chose python, +use the windows command prompt for pip. If you chose anaconda, use your anaconda prompt. + +If you type the pip command (with no options), it will produce a long list of options. This tells you that you +are at the right command prompt to do the rest of the installation. +**Note, the pip command will not work inside of python.** This is a command prompt command, not a python command. + +:: + + pip + + +Sometimes pip tells you that it wants to upgrade. For windows, the command is: + +:: + + python -m pip install -U pip + +To actually install, use the commands described above (and repeated here). + +:: + + pip install untangle + pip install pyparrot + pip install zeroconf + +**Note that visual studio is a requirement for zeroconf** + +Testing your install +^^^^^^^^^^^^^^^^^^^^ + +The first step is to connect your connect your controlling device (laptop, computer, etc) to the wifi for the drone. +Look for a wifi network named Mambo_number where number changes for each drone. + +After connection to your drone its time to run code! You can download all the example code from +these docs. Below is a short set of commands of how to run that code. + +Run code by cd'ing down to the directory (the folder your python code is in) and running the desired python file from the cmd line + +Example: + * open command line either through windows or anaconda (depending on your installation method) + * type: ``cd desktop`` + * this will Change your Directory to the desktop + * type: ``dir`` + * this will display a list of all the folders (directories) on the desktop + * type: ``cd yourFolderNameHere`` + * type: ``dir`` + * this will display all the files and folders in the directory + * type: ``py TheNameOfTheFileYouWantToRun.py`` or ``python TheNameOfTheFileYouWantToRun.py`` + * When you click enter the file will begin to run, if you are using the demo scripts you should see lots of nice feedback as it changes states. You can use the arrow keys to go through your history of commands which can save you lots of time if your file names are long. + * If you have several connects and disconnects try restarting your computer or resetting your ip (for the more technically inclined) + * If you have crashes where the drone is flipping to one side when it shouldn't check the blades and bumpers. The bumpers can shift after a crash and prevent the blades from spinning, or slow down their spin, which causes unintended flips \ No newline at end of file diff --git a/RPI Code/pyparrot_/pyparrot/docs/license.rst b/RPI Code/pyparrot_/pyparrot/docs/license.rst new file mode 100644 index 0000000..df634df --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/license.rst @@ -0,0 +1,32 @@ +.. title:: License + +.. license: + +License +=============== + +MIT License +----------- + +The project is licensed under the MIT License. + +Copyright (c) 2017 amymcgovern + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/RPI Code/pyparrot_/pyparrot/docs/minidronecommands.rst b/RPI Code/pyparrot_/pyparrot/docs/minidronecommands.rst new file mode 100644 index 0000000..27df755 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/minidronecommands.rst @@ -0,0 +1,152 @@ +.. title:: Minidrone Commands and Sensors + +.. minidronecommands: + +Minidrone Commands and Sensors +============================== + +Minidrone commands +-------------- + +Each of the public commands available to control the minidrone is listed below with its documentation. +The code is also well documented and you can also look at the API through readthedocs. +All of the functions preceeded with an underscore are intended to be internal functions and are not listed below. + +Creating a Mambo object +^^^^^^^^^^^^^^^^^^^^^^^ + +``Mambo(address="", use_wifi=True/False)`` +create a mambo object with the specific harware address (found using findMinidrone). The use_wifi argument defaults to +False (which means BLE is the default). Set to True to use wifi. You can only use wifi if you have a FPV camera +installed on your Mambo! If you are using wifi, the hardware address argument can be ignored (it defaults to an empty +string). + +Creating a Swing object +^^^^^^^^^^^^^^^^^^^^^^^ + +``Swing(address="")`` +create a Swing object with the specific harware address (found using findMinidrone). + +Connecting and disconnecting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``connect(num_retries)`` connect to the Minidrone either using BLE services and characteristics or wifi +(specified when you created the Mambo object). This can take several seconds to ensure the connection is working. +You can specify a maximum number of re-tries. Returns true if the connection suceeded or False otherwise. + +``disconnect()`` disconnect from the BLE or wifi connection + +Takeoff and landing +^^^^^^^^^^^^^^^^^^^ + +``safe_takeoff(timeout)`` This is the recommended method for takeoff. It sends a command and then checks the +sensors (via flying state) to ensure the minidrone is actually taking off. Then it waits until the minidrone is +flying or hovering to return. It will timeout and return if the time exceeds timeout seconds. + +``safe_land(timeout)`` This is the recommended method to land the minidrone. Sends commands +until the minidrone has actually reached the landed state. It will timeout and return if the time exceeds timeout seconds. + +``takeoff()`` Sends a single takeoff command to the minidrone. This is not the recommended method. + +``land()`` Sends a single land command to the minidrone. This is not the recommended method. + +``turn_on_auto_takeoff()`` This puts the minidrone in throw mode. When it is in throw mode, the eyes will blink. + +Flying +^^^^^^ + +``hover()`` and ``set_flat_trim()`` both tell the drone to assume the current configuration is a flat trim and it will +use this as the default when not receiving commands. This enables good hovering when not sending commands. + +``flip(direction)`` Sends the flip command to the minidrone. Valid directions to flip are: front, back, right, left. + +``turn_degrees(degrees)`` Turns the minidrone in place the specified number of degrees. +The range is -180 to 180. This can be accomplished in direct_fly() as well but this one uses the +internal minidrone sensors (which are not sent out right now) so it is more accurate. + +``fly_direct(roll, pitch, yaw, vertical_movement, duration)`` Fly the minidrone directly using the +specified roll, pitch, yaw, and vertical movements. The commands are repeated for duration seconds. +Note there are currently no sensors reported back to the user to ensure that these are working but hopefully +that is addressed in a future firmware upgrade. Each value ranges from -100 to 100 and is essentially a percentage +and direction of the max_tilt (for roll/pitch) or max_vertical_speed (for vertical movement). + +``set_max_tilt(degrees)`` Set the maximum tilt in degrees. Be careful as this makes your drone go slower or faster! +It is important to note that the fly_direct command uses this value in conjunction with the -100 to 100 percentages. + +``set_max_vertical_speed(speed)`` Set the maximum vertical speed in m/s. Be careful as this makes your drone go up/down faster! + +Pausing or sleeping in a thread safe manner +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``smart_sleep(seconds)`` This sleeps the number of seconds (which can be a floating point) but wakes for all +BLE or wifi notifications. **Note, if you are using BLE: This comamnd is VERY important**. **NEVER** use regular +time.sleep() as your BLE will disconnect regularly! Use smart_sleep instead! time.sleep() is ok if you are using +wifi but smart_sleep() handles that for you. + +USB accessories: Claw and Gun +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``open_claw()`` Open the claw. Note that the claw should be attached for this to work. +The id is obtained from a prior ``ask_for_state_update()`` call. Note that you cannot use the claw with the FPV camera attached. + +``close_claw()`` Close the claw. Note that the claw should be attached for this to work. +The id is obtained from a prior ``ask_for_state_update()`` call. Note that you cannot use the claw with the FPV camera attached. + +``fire_gun()`` Fires the gun. Note that the gun should be attached for this to work. +The id is obtained from a prior ``ask_for_state_update()`` call. Note that you cannot use the gun with the FPV camera attached. + +Swing specific commands +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``set_plane_gear_box(state)`` Choose the swing angle in plane mode. There are 3 tilt modes: gear_1, gear_2, gear_3. +Warning gear_3 is very fast. + +``set_flying_mode(mode)`` Choose flight mode between: quadricopter, plane_forward, plane_backward. + +Ground facing camera +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``take_picture()``` The minidrone will take a picture with the downward facing camera. It only stores up to 40 pictures +internally so this function deletes them after 35 have been taken. Make sure you are downloading them either +using the mobile interface or through the python code. + +**Note**: Parrot broke the ability to access the groundcam in their latest (3.0.25) firmware upgrade. We will reenable these +functions as soon as parrot fixes the firmware but for now, they will only work in versions 3.0.24 and below. + +``get_groundcam_pictures_names()`` Returns the names of the pictures stored internally from the groundcam. Only for the mambo. + +``get_groundcam_picture(name)`` Returns the picture with the specified name. Only for the mambo. + +Sensor related commands +^^^^^^^^^^^^^^^^^^^^^^^ + +``ask_for_state_update()`` This sends a request to the minidrone to send back ALL states +(this includes the claw and gun states). This really only needs to be called once at the start of the program +to initialize some of the state variables. If you are on wifi, many of the other variables are sent at 2Hz. If you are +on BLE, you will want to use this command to get more state information but keep in mind it will be slow. +This command will return immediately but you should wait a few seconds before using the new state information +as it has to be updated. + + +Mambo sensors +------------- + +All of the sensor data that is passed back to the program is saved. Note that Parrot sends back more +information via wifi than via BLE, due to the limited BLE bandwidth. The sensors are saved in Minidrone.sensors. +This is an instance of a MamboSensors class, which can be seen at the top of the Minidrone.py file. + +The easiest way to interact with the sensors is to call: + +``minidrone.set_user_sensor_callback(function, args)``. This sets a user callback function with optional +arguments that is called each time a sensor is updated. The refresh rate on wifi is 2Hz. + +The sensors are: + +* battery (defaults to 100 and stays at that level until a real reading is received from the drone) +* flying_state: This is updated as frequently as the drone sends it out and can be one of "landed", "takingoff", "hovering", "flying", "landing", "emergency", "rolling", "init". These are the values as specified in `minidrone.xml `_. +* gun_id: defaults to 0 (as far as I can tell, it is only ever 0 when it comes from the drone anyway) +* gun_state: "READY" or "BUSY" as sent by the drone, if a gun is attached. Defaults to None. +* claw_id: defaults to 0 +* claw_state: "OPENING", "OPENED", "CLOSING", "CLOSED" as sent by the drone, if a claw is attached. Defaults to None. +* speed_x, speed_y, speed_z, speed_ts: the speed in x (forward > 0), y (right > 0), and z (down > 0). The ts is the timestamp that the speed was valid. +* altitude, altitude_ts: wifi only, altitude in meters. Zero is where you took off. The ts is the timestamp where the altitude was valid. +* quaternion_w, quaternion_x, quaternion_y, quaternion_z, quaternion_ts: wifi only. Quaternion as estimated from takeoff (which is set to 0). Ranges from -1 to 1. ts is the timestamp where this was valid. +* ``get_estimated_z_orientation()``: returns the estimated orientation using the unit quaternions. Note that 0 is the direction the drone is facing when you boot it up +* sensors_dict: all other sensors are saved by name in a dictionary. The names come from the `minidrone.xml `_ and `common.xml `_. diff --git a/RPI Code/pyparrot_/pyparrot/docs/modules.rst b/RPI Code/pyparrot_/pyparrot/docs/modules.rst new file mode 100644 index 0000000..440859d --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/modules.rst @@ -0,0 +1,7 @@ +pyparrot +======== + +.. toctree:: + :maxdepth: 4 + + pyparrot diff --git a/RPI Code/pyparrot_/pyparrot/docs/pyparrot.commandsandsensors.rst b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.commandsandsensors.rst new file mode 100644 index 0000000..87a7fdb --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.commandsandsensors.rst @@ -0,0 +1,30 @@ +pyparrot.commandsandsensors package +=================================== + +Submodules +---------- + +pyparrot.commandsandsensors.DroneCommandParser module +----------------------------------------------------- + +.. automodule:: pyparrot.commandsandsensors.DroneCommandParser + :members: + :undoc-members: + :show-inheritance: + +pyparrot.commandsandsensors.DroneSensorParser module +---------------------------------------------------- + +.. automodule:: pyparrot.commandsandsensors.DroneSensorParser + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pyparrot.commandsandsensors + :members: + :undoc-members: + :show-inheritance: diff --git a/RPI Code/pyparrot_/pyparrot/docs/pyparrot.networking.rst b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.networking.rst new file mode 100644 index 0000000..53a0b1c --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.networking.rst @@ -0,0 +1,30 @@ +pyparrot.networking package +=========================== + +Submodules +---------- + +pyparrot.networking.bleConnection module +---------------------------------------- + +.. automodule:: pyparrot.networking.bleConnection + :members: + :undoc-members: + :show-inheritance: + +pyparrot.networking.wifiConnection module +----------------------------------------- + +.. automodule:: pyparrot.networking.wifiConnection + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pyparrot.networking + :members: + :undoc-members: + :show-inheritance: diff --git a/RPI Code/pyparrot_/pyparrot/docs/pyparrot.rst b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.rst new file mode 100644 index 0000000..9f13384 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.rst @@ -0,0 +1,64 @@ +pyparrot package +================ + +Subpackages +----------- + +.. toctree:: + + pyparrot.commandsandsensors + pyparrot.networking + pyparrot.scripts + pyparrot.utils + +Submodules +---------- + +pyparrot.Bebop module +--------------------- + +.. automodule:: pyparrot.Bebop + :members: + :undoc-members: + :show-inheritance: + +pyparrot.DroneVision module +--------------------------- + +.. automodule:: pyparrot.DroneVision + :members: + :undoc-members: + :show-inheritance: + +pyparrot.DroneVisionGUI module +------------------------------ + +.. automodule:: pyparrot.DroneVisionGUI + :members: + :undoc-members: + :show-inheritance: + +pyparrot.Minidrone module +--------------------- + +.. automodule:: pyparrot.Minidrone + :members: + :undoc-members: + :show-inheritance: + +pyparrot.VisionServer module +---------------------------- + +.. automodule:: pyparrot.VisionServer + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pyparrot + :members: + :undoc-members: + :show-inheritance: diff --git a/RPI Code/pyparrot_/pyparrot/docs/pyparrot.scripts.rst b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.scripts.rst new file mode 100644 index 0000000..66cfe4f --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.scripts.rst @@ -0,0 +1,22 @@ +pyparrot.scripts package +======================== + +Submodules +---------- + +pyparrot.scripts.findMinidrone module +--------------------------------- + +.. automodule:: pyparrot.scripts.findMinidrone + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pyparrot.scripts + :members: + :undoc-members: + :show-inheritance: diff --git a/RPI Code/pyparrot_/pyparrot/docs/pyparrot.utils.rst b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.utils.rst new file mode 100644 index 0000000..2dce16d --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/pyparrot.utils.rst @@ -0,0 +1,38 @@ +pyparrot.utils package +====================== + +Submodules +---------- + +pyparrot.utils.NonBlockingStreamReader module +--------------------------------------------- + +.. automodule:: pyparrot.utils.NonBlockingStreamReader + :members: + :undoc-members: + :show-inheritance: + +pyparrot.utils.colorPrint module +-------------------------------- + +.. automodule:: pyparrot.utils.colorPrint + :members: + :undoc-members: + :show-inheritance: + +pyparrot.utils.vlc module +------------------------- + +.. automodule:: pyparrot.utils.vlc + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pyparrot.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/RPI Code/pyparrot_/pyparrot/docs/quickstartbebop.rst b/RPI Code/pyparrot_/pyparrot/docs/quickstartbebop.rst new file mode 100644 index 0000000..98f5193 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/quickstartbebop.rst @@ -0,0 +1,199 @@ +.. title:: Quick Start with a Bebop + +.. quickstartmambo: + +Quick Start Guide with a Bebop +============================== + +Using the pyparrot library on the Bebop +--------------------------------------- + +Before running any of the sample code, you will need to connect to your drone. To control the Bebop, you need to +connect your controlling device (laptop, computer, etc) to the wifi for the drone. Look for the wifi network +named Bebop_number where number varies for each drone. + +Quick start: Demo Code +----------------------- +I have provided a set of `example `_ scripts for both the +Mambo and the Bebop. + +Demo of the trick commands on the bebop +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The code shown below is the +`demoBebopTricks.py `_. +demoBebopTricks.py will take off, demonstrate all 4 types of flips, and then land. It is a good program to +verify that your connection to your bebop is working well. The bebop can flip just like the Mambo! This does +the exact same thing as the Mambo tricks demo: take off, flip in all 4 directions, land. +**Be sure to run it in a room large enough to perform the flips!** + +.. code-block:: python + + """ + Demos the tricks on the bebop. Make sure you have enough room to perform them! + + Author: Amy McGovern + """ + + from pyparrot.Bebop import Bebop + + bebop = Bebop() + + print("connecting") + success = bebop.connect(10) + print(success) + + print("sleeping") + bebop.smart_sleep(5) + + bebop.ask_for_state_update() + + bebop.safe_takeoff(10) + + print("flip left") + print("flying state is %s" % bebop.sensors.flying_state) + success = bebop.flip(direction="left") + print("mambo flip result %s" % success) + bebop.smart_sleep(5) + + print("flip right") + print("flying state is %s" % bebop.sensors.flying_state) + success = bebop.flip(direction="right") + print("mambo flip result %s" % success) + bebop.smart_sleep(5) + + print("flip front") + print("flying state is %s" % bebop.sensors.flying_state) + success = bebop.flip(direction="front") + print("mambo flip result %s" % success) + bebop.smart_sleep(5) + + print("flip back") + print("flying state is %s" % bebop.sensors.flying_state) + success = bebop.flip(direction="back") + print("mambo flip result %s" % success) + bebop.smart_sleep(5) + + bebop.smart_sleep(5) + bebop.safe_land(10) + + print("DONE - disconnecting") + bebop.disconnect() + +Outdoor or large area demo of the direct flight commands on the bebop +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The second example program shows how to directly fly the bebop by controlling the yaw, pitch, roll, and +vertical movement parameters. **Make sure you try this one in a large enough room!** +This code is provided in +`demoBebopDirectFlight.py `_ +and is also shown below. + + +.. code-block:: python + + """ + Flies the bebop in a fairly wide arc. You want to be sure you have room for this. (it is commented + out but even what is here is still going to require a large space) + + Author: Amy McGovern + """ + from pyparrot.Bebop import Bebop + + bebop = Bebop() + + print("connecting") + success = bebop.connect(10) + print(success) + + print("sleeping") + bebop.smart_sleep(5) + + bebop.ask_for_state_update() + + bebop.safe_takeoff(10) + + print("Flying direct: going forward (positive pitch)") + bebop.fly_direct(roll=0, pitch=50, yaw=0, vertical_movement=0, duration=1) + + print("Flying direct: yaw") + bebop.fly_direct(roll=0, pitch=0, yaw=50, vertical_movement=0, duration=1) + + print("Flying direct: going backwards (negative pitch)") + bebop.fly_direct(roll=0, pitch=-50, yaw=0, vertical_movement=0, duration=0.5) + + print("Flying direct: roll") + bebop.fly_direct(roll=50, pitch=0, yaw=0, vertical_movement=0, duration=1) + + print("Flying direct: going up") + bebop.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=50, duration=1) + + print("Turning relative") + bebop.move_relative(0, 0, 0, math.radians(90)) + + # this works but requires a larger test space than I currently have. Uncomment with care and test only in large spaces! + #print("Flying direct: going around in a circle (yes you can mix roll, pitch, yaw in one command!)") + #bebop.fly_direct(roll=25, pitch=0, yaw=50, vertical_movement=0, duration=5) + + bebop.smart_sleep(5) + bebop.safe_land(10) + + print("DONE - disconnecting") + bebop.disconnect() + + +Indoor demo of the direct flight commands on the bebop +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you couldn't run the outdoor or large space demo to test your bebop, this one is designed for a smaller space. +It simply takes off, turns, and lands. **Make sure you are still flying in a safe place!** This code is provided in +`demoBebopIndoors.py `_ +and is also shown below. + + +.. code-block:: python + + """ + Demo the Bebop indoors (sets small speeds and then flies just a small amount) + Note, the bebop will hurt your furniture if it hits it. Even though this is a very small + amount of flying, be sure you are doing this in an open area and are prepared to catch! + + Author: Amy McGovern + """ + + from pyparrot.Bebop import Bebop + + bebop = Bebop() + + print("connecting") + success = bebop.connect(10) + print(success) + + if (success): + print("turning on the video") + bebop.start_video_stream() + + print("sleeping") + bebop.smart_sleep(2) + + bebop.ask_for_state_update() + + bebop.safe_takeoff(10) + + # set safe indoor parameters + bebop.set_max_tilt(5) + bebop.set_max_vertical_speed(1) + + # trying out the new hull protector parameters - set to 1 for a hull protection and 0 without protection + bebop.set_hull_protection(1) + + print("Flying direct: Slow move for indoors") + bebop.fly_direct(roll=0, pitch=20, yaw=0, vertical_movement=0, duration=2) + + bebop.smart_sleep(5) + + bebop.safe_land(10) + + print("DONE - disconnecting") + bebop.stop_video_stream() + bebop.smart_sleep(5) + bebop.disconnect() \ No newline at end of file diff --git a/RPI Code/pyparrot_/pyparrot/docs/quickstartminidrone.rst b/RPI Code/pyparrot_/pyparrot/docs/quickstartminidrone.rst new file mode 100644 index 0000000..574b388 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/quickstartminidrone.rst @@ -0,0 +1,604 @@ +.. title:: Quick Start with a Minidrone + +.. quickstartmambo: + +Quick Start Guide with a Minidrone +============================== + +Using the pyparrot library on the Minidrone +--------------------------------------- + +Before running any of the sample code, you will need to connect to your drone. If you have a Mambo FPV, I highly +recommend using the wifi connection since it sends much more information using wifi than BLE. If you have a Mambo Code +or a Mambo Fly or Swing(neither of which has a camera), then you need to use the BLE connection. + +wifi connection +^^^^^^^^^^^^^^^ + +If you are using the wifi (e.g. Mambo FPV), you need to connect your controlling device (laptop, computer, etc) +to the wifi for the drone. Look for a wifi network named Mambo_number where number changes for each drone. + +BLE connection +^^^^^^^^^^^^^^ + +If you do not have a camera or want to use BLE for other reasons(e.g. swarm), you will first need to find the +BLE address of your Minidrone(s). BLE permissions on linux require that this command run in sudo mode. +To run this, from the bin directory for your python installation, type: + +:: + + sudo findMinidrone + + +This will identify all BLE devices within hearing of the Pi. The Minidrone's specific address will be printed at the end. +Save the address and use it in your connection code (discussed below). If findMinidrone does not +report "FOUND A MAMBO!" or "FOUND A SWING!", then be sure your minidrone is turned on when you run the findMambo code and that your Pi +(or other linux box) has its BLE interface turned on. + +The output should look something like this. I removed my own BLE addresses from my network for security but I am +showing the address of the mambo that I use for all the demo scripts. + +.. code-block:: console + + ~/miniconda3/bin $ sudo ./find_mambo + Discovered device
+ Discovered device
+ Discovered device
+ Discovered device e0:14:d0:63:3d:d0 + Received new data from
+ Discovered device
+ Discovered device
+ Received new data from
+ Discovered device
+ FOUND A MAMBO! + Device e0:14:d0:63:3d:d0 (random), RSSI=-60 dB + Complete Local Name = Mambo_ + + + +Quick start: Demo Code +----------------------- + +I have provided a set of `example `_ scripts for both the +Mambo and the Bebop. Note that you will need to edit the minidrone scripts to either use your own BLE address or to +ensure that use_wifi=True is set, so that it connects using wifi. +**Note that you do not need to run any of the other code in sudo mode!** That was only for discovery. + +Demo of the trick commands on the mambo +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The code shown below is the +`demoMamboTricks.py `_. +demoMamboTricks.py will take off, demonstrate all 4 types of flips, and then land. It is a good program to +verify that your connection to your mambo is working well. Be sure to run it in a room large enough +to perform the flips! The highlighted lines need to change for YOUR mambo and connection choices. + + +.. code-block:: python + + """ + Demo the trick flying for the python interface + + Author: Amy McGovern + """ + + from pyparrot.Minidrone import Mambo + + # you will need to change this to the address of YOUR mambo + mamboAddr = "e0:14:d0:63:3d:d0" + + # make my mambo object + # remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect + mambo = Mambo(mamboAddr, use_wifi=True) + + print("trying to connect") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(2) + mambo.ask_for_state_update() + mambo.smart_sleep(2) + + print("taking off!") + mambo.safe_takeoff(5) + + if (mambo.sensors.flying_state != "emergency"): + print("flying state is %s" % mambo.sensors.flying_state) + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=20, duration=1) + + print("flip left") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="left") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("flip right") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="right") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("flip front") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="front") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("flip back") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="back") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("landing") + print("flying state is %s" % mambo.sensors.flying_state) + mambo.safe_land(5) + mambo.smart_sleep(5) + + print("disconnect") + mambo.disconnect() + + + + +Demo of the direct flight commands on the mambo +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The second example program shows how to directly fly the mambo by controlling the yaw, pitch, roll, and +vertical movement parameters. **Make sure you try this one in a large enough room!** +This code is provided in +`demoMamboDirectFlight.py `_ +and is also shown below. Again, the highlighted lines must be changed to the parameters for your mambo and connection. + +.. code-block:: python + + """ + Demo the direct flying for the python interface + + Author: Amy McGovern + """ + + from pyparrot.Minidrone import Mambo + + # you will need to change this to the address of YOUR mambo + mamboAddr = "e0:14:d0:63:3d:d0" + + # make my mambo object + # remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect + mambo = Mambo(mamboAddr, use_wifi=True) + + print("trying to connect") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(2) + mambo.ask_for_state_update() + mambo.smart_sleep(2) + + print("taking off!") + mambo.safe_takeoff(5) + + print("Flying direct: going forward (positive pitch)") + mambo.fly_direct(roll=0, pitch=50, yaw=0, vertical_movement=0, duration=1) + + print("Showing turning (in place) using turn_degrees") + mambo.turn_degrees(90) + mambo.smart_sleep(2) + mambo.turn_degrees(-90) + mambo.smart_sleep(2) + + print("Flying direct: yaw") + mambo.fly_direct(roll=0, pitch=0, yaw=50, vertical_movement=0, duration=1) + + print("Flying direct: going backwards (negative pitch)") + mambo.fly_direct(roll=0, pitch=-50, yaw=0, vertical_movement=0, duration=0.5) + + print("Flying direct: roll") + mambo.fly_direct(roll=50, pitch=0, yaw=0, vertical_movement=0, duration=1) + + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=50, duration=1) + + print("Flying direct: going around in a circle (yes you can mix roll, pitch, yaw in one command!)") + mambo.fly_direct(roll=25, pitch=0, yaw=50, vertical_movement=0, duration=3) + + print("landing") + mambo.safe_land(5) + mambo.smart_sleep(5) + + print("disconnect") + mambo.disconnect() + + + +Demo of the USB claw accessory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your mambo has the USB accessories (claw and gun), you can control them but you *MUST* be in BLE mode. +The mambo can only handle one USB accessory at a time and the camera counts as a USB accessory so you must use +the BLE connection only. `demoMamboClaw.py `_ +show how to use the claw accessory. The highlighted line must be changed to the BLE address for your mambo and the use_wifi +parameter must stay at False. In this demo program, the mambo takes off, opens and closes the claw, and lands again. + +.. code-block:: python + + """ + Demo the claw for the python interface + + Author: Amy McGovern + """ + + from pyparrot.Minidrone import Mambo + + # you will need to change this to the address of YOUR mambo + mamboAddr = "e0:14:d0:63:3d:d0" + + # make my mambo object + # remember you can't use the claw with the camera installed so this must be BLE connected to work + mambo = Mambo(mamboAddr, use_wifi=False) + + print("trying to connect") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + # get the state information + print("sleeping") + mambo.smart_sleep(2) + mambo.ask_for_state_update() + mambo.smart_sleep(2) + + print("taking off!") + mambo.safe_takeoff(5) + + print("open and close the claw") + mambo.open_claw() + # you have to sleep to let the claw open (it needs time to do it) + mambo.smart_sleep(5) + + mambo.close_claw() + # you have to sleep to let the claw close (it needs time to do it) + mambo.smart_sleep(5) + + print("landing") + mambo.safe_land(5) + mambo.smart_sleep(5) + + print("disconnect") + mambo.disconnect() + +Demo of the USB gun accessory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`demoMamboGun.py `_ +show how to use the gun accessory. The highlighted line must be changed to the BLE address for your mambo and the use_wifi +parameter must stay at False. In this demo program, the mambo takes off, fires the gun, and lands again. + +.. code-block:: python + + """ + Demo the gun for the python interface + + Author: Amy McGovern + """ + + from pyparrot.Minidrone import Mambo + + # you will need to change this to the address of YOUR mambo + mamboAddr = "e0:14:d0:63:3d:d0" + + # make my mambo object + # remember you can't use the gun with the camera installed so this must be BLE connected to work + mambo = Mambo(mamboAddr, use_wifi=False) + + print("trying to connect") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + # get the state information + print ("sleeping") + mambo.smart_sleep(2) + mambo.ask_for_state_update() + mambo.smart_sleep(2) + + print("shoot the gun") + mambo.fire_gun() + + # sleep to ensure it does the firing + mambo.smart_sleep(15) + + print("disconnect") + mambo.disconnect() + + + +Demo of the ground-facing camera +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`demoMamboGroundcam.py `_ +show how to use the mambo's ground-facing camera. This feature **ONLY** works in wifi mode. It can be slow +to download the frames so do not count on this running at several frames per second. The example code shown +below takes off, takes a picture, and then grabs a random picture from the ground facing camera set. + +.. code-block:: python + + """ + Demo of the groundcam + Mambo takes off, takes a picture and shows a RANDOM frame, not the last one + Author: Valentin Benke, https://github.com/Vabe7 + Author: Amy McGovern + """ + + from pyparrot.Minidrone import Mambo + import cv2 + + mambo = Mambo(None, use_wifi=True) #address is None since it only works with WiFi anyway + print("trying to connect to mambo now") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(1) + mambo.ask_for_state_update() + mambo.smart_sleep(1) + mambo.safe_takeoff(5) + + # take the photo + pic_success = mambo.take_picture() + + # need to wait a bit for the photo to show up + mambo.smart_sleep(0.5) + + picture_names = mambo.groundcam.get_groundcam_pictures_names() #get list of availible files + print(picture_names) + + frame = mambo.groundcam.get_groundcam_picture(picture_names[0],True) #get frame which is the first in the array + + if frame is not None: + if frame is not False: + cv2.imshow("Groundcam", frame) + cv2.waitKey(100) + + mambo.safe_land(5) + mambo.disconnect() + +Demo of the flying mode on the swing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +`demoSwingDirectFlight.py `_ +You can see how to use the set_flying_mode command. I advise you to have enough space to use this script. + +.. code-block:: python + + """ + Demo the direct flying for the python interface + + Author: Victor804 + """ + + from pyparrot.Minidrone import Swing + + # you will need to change this to the address of YOUR swing + swingAddr = "e0:14:04:a7:3d:cb" + + # make my swing object + swing = Swing(swingAddr) + + print("trying to connect") + success = swing.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + swing.smart_sleep(2) + swing.ask_for_state_update() + swing.smart_sleep(2) + + print("taking off!") + swing.safe_takeoff(5) + + print("plane forward") + swing.set_flying_mode("plane_forward") + + swing.smart_sleep(1) + + print("quadricopter") + swing.set_flying_mode("quadricopter") + + print("landing") + swing.safe_land(5) + swing.smart_sleep(5) + + print("disconnect") + swing.disconnect() + + +Demo joystick for Swing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +`demoSwingJoystick.py `_ +Example code to control the swig with a joystick. Easy to modify for your needs. + +.. code-block:: python + + + import pygame + import sys + from pyparrot.Minidrone import Swing + + def joystick_init(): + """ + Initializes the controller, allows the choice of the controller. + If no controller is detected returns an error. + + :param: + :return joystick: + """ + pygame.init() + pygame.joystick.init() + + joystick_count = pygame.joystick.get_count() + + if joystick_count > 0: + for i in range(joystick_count): + joystick = pygame.joystick.Joystick(i) + joystick.init() + + name = joystick.get_name() + print([i], name) + + joystick.quit() + + else: + sys.exit("Error: No joystick detected") + + selected_joystick = eval(input("Enter your joystick number:")) + + if selected_joystick not in range(joystick_count): + sys.exit("Error: Your choice is not valid") + + joystick = pygame.joystick.Joystick(selected_joystick) + joystick.init() + + return joystick + + + def mapping_button(joystick, dict_commands): + """ + Associating a controller key with a command in dict_commands. + + :param joystick, dict_commands: + :return mapping: + """ + mapping = {} + + for command in dict_commands: + print("Press the key", command) + done = False + while not done: + for event in pygame.event.get(): + if event.type == pygame.JOYBUTTONDOWN: + if event.button not in (value for value in mapping.values()): + mapping[command] = event.button + done = True + + return mapping + + + def mapping_axis(joystick, axes=["pitch", "roll", "yaw", "vertical"]): + """ + Associating the analog thumbsticks of the controller with a command in dict commands + + :param joystick, dict_commands: + :return mapping: + """ + mapping = {} + + for i in axes: + print("Push the", i, "axis") + done = False + while not done: + for event in pygame.event.get(): + if event.type == pygame.JOYAXISMOTION: + if event.axis not in (value for value in mapping.values()): + mapping[i] = event.axis + done = True + + return mapping + + + def _parse_button(dict_commands, button): + """ + Send the commands to the drone. + If multiple commands are assigned to a key each command will be sent one by one to each press. + + :param dict_commands, button: + :return: + """ + commands = dict_commands[button][0] + args = dict_commands[button][-1] + + command = commands[0] + arg = args[0] + + if len(commands) == 1: + if len(args) == 1: + command(arg) + + else: + command(arg) + dict_commands[button][-1] = args[1:]+[arg] + + else: + if len(commands) == 1: + command(arg) + dict_commands[button][0] = commands[1:]+[command] + + else: + command(arg) + dict_commands[button][0] = commands[1:]+[command] + dict_commands[button][-1] = args[1:]+[arg] + + + def main_loop(joystick, dict_commands, mapping_button, mapping_axis): + """ + First connects to the drone and makes a flat trim. + Then in a loop read the events of the controller to send commands to the drone. + + :param joystick, dict_commands, mapping_button, mapping_axis: + :return: + """ + swing.connect(10) + swing.flat_trim() + + while True: + pygame.event.get() + + pitch = joystick.get_axis(mapping_axis["pitch"])*-100 + roll = joystick.get_axis(mapping_axis["roll"])*100 + yaw = joystick.get_axis(mapping_axis["yaw"])*100 + vertical = joystick.get_axis(mapping_axis["vertical"])*-100 + + swing.fly_direct(roll, pitch, yaw, vertical, 0.1) + + for button, value in mapping_button.items(): + if joystick.get_button(value): + _parse_button(dict_commands, button) + + + if __name__ == "__main__": + swing = Swing("e0:14:04:a7:3d:cb") + + #Example of dict_commands + dict_commands = { + "takeoff_landing":[ #Name of the button + [swing.safe_takeoff, swing.safe_land],#Commands execute one by one + [5]#Argument for executing the function + ], + "fly_mode":[ + [swing.set_flying_mode], + ["quadricopter", "plane_forward"] + ], + "plane_gear_box_up":[ + [swing.set_plane_gear_box], + [((swing.sensors.plane_gear_box[:-1]+str(int(swing.sensors.plane_gear_box[-1])+1)) if swing.sensors.plane_gear_box[-1] != "3" else "gear_3")]#"gear_1" => "gear_2" => "gear_3" + ], + "plane_gear_box_down":[ + [swing.set_plane_gear_box], + [((swing.sensors.plane_gear_box[:-1]+str(int(swing.sensors.plane_gear_box[-1])-1)) if swing.sensors.plane_gear_box[-1] != "1" else "gear_1")]#"gear_3" => "gear_2" => "gear_1" + ] + } + + joystick = joystick_init() + + mapping_button = mapping_button(joystick, dict_commands) + mapping_axis = mapping_axis(joystick) + + main_loop(joystick, dict_commands, mapping_button, mapping_axis) diff --git a/RPI Code/pyparrot_/pyparrot/docs/vision.rst b/RPI Code/pyparrot_/pyparrot/docs/vision.rst new file mode 100644 index 0000000..fce3c8c --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/docs/vision.rst @@ -0,0 +1,469 @@ +.. title:: Using Vision on the Mambos and Bebop + +.. vision: + +Using Vision on the Mambos and Bebop +==================================== + +The vision system uses a common interface for the Mambo and the Bebop. There are two approaches to the vision. +The first relies on ffmpeg and the second on libVLC. Both approaches assume opencv is also installed. +Note, the reason we do not simply rely on opencv directly is that there is a known existing bug in +opencv with RTSP streams that makes them unreliable. The behavior we reliably saw by using opencv directly was +that the stream would open, obtain between 10 and 15 frames and then stop collecting any new frames. Since +this is not tenable for flying, we bypass opencv's direct open of the stream with two other approaches described below. + +Using ffmpeg for vision +----------------------- + +`ffmpeg `_ is an open-source cross-platform library that can be used to process a +wide variety of video and audio streams. We use ffmpeg to bypass the issue with opencv by opening the video stream +in ffmpeg. The advantage of this approach is that the ffmpeg encoder can read the raw video streams (RTSP for +Mambo and RTP for Bebop) and convert it directly to a format that opencv can easily read. We chose to convert to png +format. The disadvantage is that ffmpeg has to save the converted images to disk, which introduces a slight delay +to the image processing. If you use a high-end laptop with a solid state drive, this delay can be as low as +0.5 seconds. Lower-processing speed laptops can introduce longer delays. However, if you want access to the images +after your flight or you do not need real-time access, this is the better choice. + +The vision code itself can be found in +`DroneVision.py `_. +Running this code requires both the ffmpeg software and the opencv package. This approach does NOT show a +live stream of the vision to the user but you can visualize the images using +the `VisionServer.py `_. + +One thing to note: since ffmpeg requires the images to be written to a folder, it will save images to a +directory named images inside the pyparrot package. **You will want to clean this folder out after each flight!** + + +Mambo ffmpeg vision demo +^^^^^^^^^^^^^^^^^^^^^^^^ + +The demo code for the mambo, `demoMamboVision.py `_ +shown below, has the mambo take off, move upwards for a short time, flip, and then land. +While all of this flight code is running, the vision processing is running in a separate thread. + +The first highlighted line: + +.. code-block:: python + + testFlying = False + + +can be changed to True to have the mambo fly or set to False to do the vision without the mambo moving. +**One big note: if the mambo is NOT flying, it will turn the camera into a sleep mode after several minutes +and you either need to reboot the mambo or connect and takeoff to restart the camera.** + +The highlighted line with the code: + + +.. code-block:: python + + mamboVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + + +is where the user sets the function to be called with every new vision frame. In this demo, the images are simply +read in and saved back to a new file name. + + +.. code-block:: python + :emphasize-lines: 14, 54 + + """ + Demo of the ffmpeg based mambo vision code (basically flies around and saves out photos as it flies) + + Author: Amy McGovern + """ + from pyparrot.Mambo import Mambo + from pyparrot.DroneVision import DroneVision + import threading + import cv2 + import time + + + # set this to true if you want to fly for the demo + testFlying = False + + class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + print("in save pictures on image %d " % self.index) + + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + cv2.imwrite(filename, img) + self.index +=1 + #print(self.index) + + + + # you will need to change this to the address of YOUR mambo + mamboAddr = "e0:14:d0:63:3d:d0" + + # make my mambo object + # remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect + mambo = Mambo(mamboAddr, use_wifi=True) + print("trying to connect to mambo now") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(1) + mambo.ask_for_state_update() + mambo.smart_sleep(1) + + print("Preparing to open vision") + mamboVision = DroneVision(mambo, is_bebop=False, buffer_size=30) + userVision = UserVision(mamboVision) + mamboVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + success = mamboVision.open_video() + print("Success in opening vision is %s" % success) + + if (success): + print("Vision successfully started!") + #removed the user call to this function (it now happens in open_video()) + #mamboVision.start_video_buffering() + + if (testFlying): + print("taking off!") + mambo.safe_takeoff(5) + + if (mambo.sensors.flying_state != "emergency"): + print("flying state is %s" % mambo.sensors.flying_state) + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=20, duration=1) + + print("flip left") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="left") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("landing") + print("flying state is %s" % mambo.sensors.flying_state) + mambo.safe_land(5) + else: + print("Sleeeping for 15 seconds - move the mambo around") + mambo.smart_sleep(15) + + # done doing vision demo + print("Ending the sleep and vision") + mamboVision.close_video() + + mambo.smart_sleep(5) + + print("disconnecting") + mambo.disconnect() + + +Bebop ffmpeg vision demo +^^^^^^^^^^^^^^^^^^^^^^^^ +The demo code for the bebop for the ffmpeg vision works nearly identically to the mambo demo except it does +not fly the bebop around. Instead, it starts the camera and then sleeps for 30 seconds for the user to +move around or move the drone around. This is intended as a safe demo for indoors. It also moves +the camera around so that it is obvious the vision is recording different photos. The code is +available at `demoBebopVision.py `_ +and is shown below. The highlighted line is again where the user sets the callback function of how to +process the vision frames. + +Updated in Version 1.5.13: you can tell DroneVision to either remove all the old vision files (now the default) +or not by sending the parameter cleanup_old_images=True or False. + +.. code-block:: python + :emphasize-lines: 40 + + """ + Demo of the Bebop ffmpeg based vision code (basically flies around and saves out photos as it flies) + + Author: Amy McGovern + """ + from pyparrot.Bebop import Bebop + from pyparrot.DroneVision import DroneVision + import threading + import cv2 + import time + + isAlive = False + + class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + #print("saving picture") + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + #cv2.imwrite(filename, img) + self.index +=1 + + + # make my bebop object + bebop = Bebop() + + # connect to the bebop + success = bebop.connect(5) + + if (success): + # start up the video + bebopVision = DroneVision(bebop, is_bebop=True) + + userVision = UserVision(bebopVision) + bebopVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + success = bebopVision.open_video() + + if (success): + print("Vision successfully started!") + #removed the user call to this function (it now happens in open_video()) + #bebopVision.start_video_buffering() + + # skipping actually flying for safety purposes indoors - if you want + # different pictures, move the bebop around by hand + print("Fly me around by hand!") + bebop.smart_sleep(5) + + print("Moving the camera using velocity") + bebop.pan_tilt_camera_velocity(pan_velocity=0, tilt_velocity=-2, duration=4) + bebop.smart_sleep(25) + print("Finishing demo and stopping vision") + bebopVision.close_video() + + # disconnect nicely so we don't need a reboot + bebop.disconnect() + else: + print("Error connecting to bebop. Retry") + + + +Using libVLC for vision +----------------------- + +Our second approach to vision relies on the libVLC library, which in turn relies on the VLC program. +`VLC `_ is a cross-platform media player and libVLC is a python +interface to the VLC libraries. This can be done entirely in memory (not writing out to disk as ffmpeg required), +which means that the delay is minimized. If you have a need for the full image stream after your flight, you likely +should choose the ffmpeg approach. If you simply want to use the vision, this approach may work better for you +since you don't have the disk delay and you don't introduce the issues with the images subdirectory. +The other advantage of this approach is that you get a real-time video stream of what the drone is seeing. However, +controlling the drone after vision has started requires setting a new parameter called user_code_to_run, as shown +below and in the highlighted line in the demo code. + +.. code-block:: python + + mamboVision = DroneVisionGUI(mambo, is_bebop=False, buffer_size=200, + user_code_to_run=demo_mambo_user_vision_function, user_args=(mambo, )) + + +**To make this approach work, you MUST install the VLC client version 3.0.1 or greater.** +Installing only libvlc is not needed (the source is included with pyparrot) and it will not work. +Installing the client installs extra software that the libvlc python library requires. + +There are two example programs, one for the bebop and one for the mambo. Both show the window opened by this +approach and the way that a user can run their own code by assigning code to the Run button. + +libVLC demo code for the Mambo +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This code can be downloaded from +`demoMamboVisionGUI.py `_ +and is repeated below. + +.. code-block:: python + :emphasize-lines: 93, 94 + + """ + Demo of the Bebop vision using DroneVisionGUI that relies on libVLC. It is a different + multi-threaded approach than DroneVision + + Author: Amy McGovern + """ + from pyparrot.Mambo import Mambo + from pyparrot.DroneVisionGUI import DroneVisionGUI + import cv2 + + + # set this to true if you want to fly for the demo + testFlying = True + + class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + # print("in save pictures on image %d " % self.index) + + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + # uncomment this if you want to write out images every time you get a new one + #cv2.imwrite(filename, img) + self.index +=1 + #print(self.index) + + + def demo_mambo_user_vision_function(mamboVision, args): + """ + Demo the user code to run with the run button for a mambo + + :param args: + :return: + """ + mambo = args[0] + + if (testFlying): + print("taking off!") + mambo.safe_takeoff(5) + + if (mambo.sensors.flying_state != "emergency"): + print("flying state is %s" % mambo.sensors.flying_state) + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=15, duration=2) + + print("flip left") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="left") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("landing") + print("flying state is %s" % mambo.sensors.flying_state) + mambo.safe_land(5) + else: + print("Sleeeping for 15 seconds - move the mambo around") + mambo.smart_sleep(15) + + # done doing vision demo + print("Ending the sleep and vision") + mamboVision.close_video() + + mambo.smart_sleep(5) + + print("disconnecting") + mambo.disconnect() + + + if __name__ == "__main__": + # you will need to change this to the address of YOUR mambo + mamboAddr = "e0:14:d0:63:3d:d0" + + # make my mambo object + # remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect + mambo = Mambo(mamboAddr, use_wifi=True) + print("trying to connect to mambo now") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(1) + mambo.ask_for_state_update() + mambo.smart_sleep(1) + + print("Preparing to open vision") + mamboVision = DroneVisionGUI(mambo, is_bebop=False, buffer_size=200, + user_code_to_run=demo_mambo_user_vision_function, user_args=(mambo, )) + userVision = UserVision(mamboVision) + mamboVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + mamboVision.open_video() + + + +libVLC demo code for the Bebop +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This code can be downloaded from +`demoBebopVision.py `_ +and is repeated below. + +.. code-block:: python + :emphasize-lines: 69,70 + + """ + Demo of the Bebop vision using DroneVisionGUI (relies on libVLC). It is a different + multi-threaded approach than DroneVision + + Author: Amy McGovern + """ + from pyparrot.Bebop import Bebop + from pyparrot.DroneVisionGUI import DroneVisionGUI + import threading + import cv2 + import time + + isAlive = False + + class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + #print("saving picture") + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + #cv2.imwrite(filename, img) + self.index +=1 + + + def demo_user_code_after_vision_opened(bebopVision, args): + bebop = args[0] + + print("Vision successfully started!") + #removed the user call to this function (it now happens in open_video()) + #bebopVision.start_video_buffering() + + # takeoff + bebop.safe_takeoff(5) + + # skipping actually flying for safety purposes indoors - if you want + # different pictures, move the bebop around by hand + print("Fly me around by hand!") + bebop.smart_sleep(5) + + if (bebopVision.vision_running): + print("Moving the camera using velocity") + bebop.pan_tilt_camera_velocity(pan_velocity=0, tilt_velocity=-2, duration=4) + bebop.smart_sleep(5) + + # land + bebop.safe_land(5) + + print("Finishing demo and stopping vision") + bebopVision.close_video() + + # disconnect nicely so we don't need a reboot + print("disconnecting") + bebop.disconnect() + + if __name__ == "__main__": + # make my bebop object + bebop = Bebop() + + # connect to the bebop + success = bebop.connect(5) + + if (success): + # start up the video + bebopVision = DroneVisionGUI(bebop, is_bebop=True, user_code_to_run=demo_user_code_after_vision_opened, + user_args=(bebop, )) + + userVision = UserVision(bebopVision) + bebopVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + bebopVision.open_video() + + else: + print("Error connecting to bebop. Retry") + + diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoBebopDirectFlight.py b/RPI Code/pyparrot_/pyparrot/examples/demoBebopDirectFlight.py new file mode 100644 index 0000000..0b44af3 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoBebopDirectFlight.py @@ -0,0 +1,49 @@ +""" +Flies the bebop in a fairly wide arc. You want to be sure you have room for this. (it is commented +out but even what is here is still going to require a large space) + +Author: Amy McGovern +""" +from pyparrot.Bebop import Bebop +import math + +bebop = Bebop() + +print("connecting") +success = bebop.connect(10) +print(success) + +print("sleeping") +bebop.smart_sleep(5) + +bebop.ask_for_state_update() + +bebop.safe_takeoff(5) + +print("Flying direct: going forward (positive pitch)") +bebop.fly_direct(roll=0, pitch=50, yaw=0, vertical_movement=0, duration=1) + +print("Flying direct: yaw") +bebop.fly_direct(roll=0, pitch=0, yaw=50, vertical_movement=0, duration=1) + +print("Flying direct: going backwards (negative pitch)") +bebop.fly_direct(roll=0, pitch=-50, yaw=0, vertical_movement=0, duration=0.5) + +print("Flying direct: roll") +bebop.fly_direct(roll=50, pitch=0, yaw=0, vertical_movement=0, duration=1) + +print("Flying direct: going up") +bebop.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=50, duration=1) + +#print("Turning relative") +#bebop.move_relative(0, 0, 0, math.radians(90)) + +# this works but requires a larger test space than I currently have. Uncomment with care and test only in large spaces! +#print("Flying direct: going around in a circle (yes you can mix roll, pitch, yaw in one command!)") +#bebop.fly_direct(roll=25, pitch=0, yaw=50, vertical_movement=0, duration=5) + +bebop.smart_sleep(1) +bebop.safe_land(5) + +print("DONE - disconnecting") +bebop.disconnect() \ No newline at end of file diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoBebopIndoors.py b/RPI Code/pyparrot_/pyparrot/examples/demoBebopIndoors.py new file mode 100644 index 0000000..4ceeaa4 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoBebopIndoors.py @@ -0,0 +1,46 @@ +""" +Demo the Bebop indoors (sets small speeds and then flies just a small amount) +Note, the bebop will hurt your furniture if it hits it. Even though this is a very small +amount of flying, be sure you are doing this in an open area and are prepared to catch! + +Author: Amy McGovern +""" + +from pyparrot.Bebop import Bebop + +bebop = Bebop(drone_type="Bebop2") + +print("connecting") +success = bebop.connect(10) +print(success) + +if (success): + print("turning on the video") + bebop.start_video_stream() + + print("sleeping") + bebop.smart_sleep(2) + + bebop.ask_for_state_update() + + bebop.safe_takeoff(10) + + # set safe indoor parameters + bebop.set_max_tilt(5) + bebop.set_max_vertical_speed(1) + + # trying out the new hull protector parameters - set to 1 for a hull protection and 0 without protection + #bebop.set_hull_protection(1) + + print("Flying direct: Slow move for indoors") + bebop.fly_direct(roll=0, pitch=20, yaw=0, vertical_movement=0, duration=2) + + bebop.smart_sleep(5) + + bebop.safe_land(10) + + print("DONE - disconnecting") + bebop.stop_video_stream() + bebop.smart_sleep(5) + print(bebop.sensors.battery) + bebop.disconnect() \ No newline at end of file diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoBebopTricks.py b/RPI Code/pyparrot_/pyparrot/examples/demoBebopTricks.py new file mode 100644 index 0000000..030af7f --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoBebopTricks.py @@ -0,0 +1,50 @@ +""" +Demos the tricks on the bebop. Make sure you have enough room to perform them! + +Author: Amy McGovern +""" + +from pyparrot.Bebop import Bebop + +bebop = Bebop() + +print("connecting") +success = bebop.connect(10) +print(success) + +print("sleeping") +bebop.smart_sleep(5) + +bebop.ask_for_state_update() + +bebop.safe_takeoff(10) + +print("flip left") +print("flying state is %s" % bebop.sensors.flying_state) +success = bebop.flip(direction="left") +print("mambo flip result %s" % success) +bebop.smart_sleep(5) + +print("flip right") +print("flying state is %s" % bebop.sensors.flying_state) +success = bebop.flip(direction="right") +print("mambo flip result %s" % success) +bebop.smart_sleep(5) + +print("flip front") +print("flying state is %s" % bebop.sensors.flying_state) +success = bebop.flip(direction="front") +print("mambo flip result %s" % success) +bebop.smart_sleep(5) + +print("flip back") +print("flying state is %s" % bebop.sensors.flying_state) +success = bebop.flip(direction="back") +print("mambo flip result %s" % success) +bebop.smart_sleep(5) + +bebop.smart_sleep(5) +bebop.safe_land(10) + +print("DONE - disconnecting") +bebop.disconnect() \ No newline at end of file diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoBebopVision.py b/RPI Code/pyparrot_/pyparrot/examples/demoBebopVision.py new file mode 100644 index 0000000..2ffc8e6 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoBebopVision.py @@ -0,0 +1,63 @@ +""" +Demo of the Bebop ffmpeg based vision code (basically flies around and saves out photos as it flies) + +Author: Amy McGovern +""" +from pyparrot.Bebop import Bebop +from pyparrot.DroneVision import DroneVision +import threading +import cv2 +import time + +isAlive = False + +class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + #print("saving picture") + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + #cv2.imwrite(filename, img) + self.index +=1 + + +# make my bebop object +bebop = Bebop() + +# connect to the bebop +success = bebop.connect(5) + +if (success): + # start up the video + bebopVision = DroneVision(bebop, is_bebop=True) + + userVision = UserVision(bebopVision) + bebopVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + success = bebopVision.open_video() + + if (success): + print("Vision successfully started!") + #removed the user call to this function (it now happens in open_video()) + #bebopVision.start_video_buffering() + + # skipping actually flying for safety purposes indoors - if you want + # different pictures, move the bebop around by hand + print("Fly me around by hand!") + bebop.smart_sleep(5) + + print("Moving the camera using velocity") + bebop.pan_tilt_camera_velocity(pan_velocity=0, tilt_velocity=-2, duration=4) + bebop.smart_sleep(25) + print("Finishing demo and stopping vision") + bebopVision.close_video() + + # disconnect nicely so we don't need a reboot + bebop.disconnect() +else: + print("Error connecting to bebop. Retry") + diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoBebopVisionGUI.py b/RPI Code/pyparrot_/pyparrot/examples/demoBebopVisionGUI.py new file mode 100644 index 0000000..ebcd4de --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoBebopVisionGUI.py @@ -0,0 +1,78 @@ +""" +Demo of the Bebop vision using DroneVisionGUI (relies on libVLC). It is a different +multi-threaded approach than DroneVision + +Author: Amy McGovern +""" +from pyparrot.Bebop import Bebop +from pyparrot.DroneVisionGUI import DroneVisionGUI +import threading +import cv2 +import time + +isAlive = False + +class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + #print("saving picture") + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + #cv2.imwrite(filename, img) + self.index +=1 + + +def demo_user_code_after_vision_opened(bebopVision, args): + bebop = args[0] + + print("Vision successfully started!") + #removed the user call to this function (it now happens in open_video()) + #bebopVision.start_video_buffering() + + # takeoff + # bebop.safe_takeoff(5) + + # skipping actually flying for safety purposes indoors - if you want + # different pictures, move the bebop around by hand + print("Fly me around by hand!") + bebop.smart_sleep(15) + + if (bebopVision.vision_running): + print("Moving the camera using velocity") + bebop.pan_tilt_camera_velocity(pan_velocity=0, tilt_velocity=-2, duration=4) + bebop.smart_sleep(5) + + # land + bebop.safe_land(5) + + print("Finishing demo and stopping vision") + bebopVision.close_video() + + # disconnect nicely so we don't need a reboot + print("disconnecting") + bebop.disconnect() + +if __name__ == "__main__": + # make my bebop object + bebop = Bebop() + + # connect to the bebop + success = bebop.connect(5) + + if (success): + # start up the video + bebopVision = DroneVisionGUI(bebop, is_bebop=True, user_code_to_run=demo_user_code_after_vision_opened, + user_args=(bebop, )) + + userVision = UserVision(bebopVision) + bebopVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + bebopVision.open_video() + + else: + print("Error connecting to bebop. Retry") + diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboClaw.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboClaw.py new file mode 100644 index 0000000..08eeda0 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboClaw.py @@ -0,0 +1,43 @@ +""" +Demo the claw for the python interface + +Author: Amy McGovern +""" + +from pyparrot.Minidrone import Mambo + +# you will need to change this to the address of YOUR mambo +mamboAddr = "e0:14:d0:63:3d:d0" + +# make my mambo object +# remember you can't use the claw with the camera installed so this must be BLE connected to work +mambo = Mambo(mamboAddr, use_wifi=False) + +print("trying to connect") +success = mambo.connect(num_retries=3) +print("connected: %s" % success) + +# get the state information +print("sleeping") +mambo.smart_sleep(2) +mambo.ask_for_state_update() +mambo.smart_sleep(2) + +print("taking off!") +mambo.safe_takeoff(5) + +print("open and close the claw") +mambo.open_claw() +# you have to sleep to let the claw open (it needs time to do it) +mambo.smart_sleep(5) + +mambo.close_claw() +# you have to sleep to let the claw close (it needs time to do it) +mambo.smart_sleep(5) + +print("landing") +mambo.safe_land(5) +mambo.smart_sleep(5) + +print("disconnect") +mambo.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboDirectFlight.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboDirectFlight.py new file mode 100644 index 0000000..b62e190 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboDirectFlight.py @@ -0,0 +1,60 @@ +""" +Demo the direct flying for the python interface + +Author: Amy McGovern +""" + +from pyparrot.Minidrone import Mambo + +# you will need to change this to the address of YOUR mambo +mamboAddr = "d0:3a:69:b9:e6:5a" + +# make my mambo object +# remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect +mambo = Mambo(mamboAddr, use_wifi=True) + +print("trying to connect") +success = mambo.connect(num_retries=3) +print("connected: %s" % success) + +if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(2) + mambo.ask_for_state_update() + mambo.smart_sleep(2) + + print("taking off!") + mambo.safe_takeoff(5) + + """ + print("Flying direct: going forward (positive pitch)") + mambo.fly_direct(roll=0, pitch=50, yaw=0, vertical_movement=0, duration=1) + """ + print("Showing turning (in place) using turn_degrees") + mambo.turn_degrees(90) + mambo.smart_sleep(2) + mambo.turn_degrees(-90) + mambo.smart_sleep(2) + """ + print("Flying direct: yaw") + mambo.fly_direct(roll=0, pitch=0, yaw=50, vertical_movement=0, duration=1) + + print("Flying direct: going backwards (negative pitch)") + mambo.fly_direct(roll=0, pitch=-50, yaw=0, vertical_movement=0, duration=0.5) + + print("Flying direct: roll") + mambo.fly_direct(roll=50, pitch=0, yaw=0, vertical_movement=0, duration=1) + + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=50, duration=1) + + print("Flying direct: going around in a circle (yes you can mix roll, pitch, yaw in one command!)") + mambo.fly_direct(roll=25, pitch=0, yaw=50, vertical_movement=0, duration=3) + """ + print("landing") + mambo.safe_land(5) + mambo.smart_sleep(5) + + print("disconnect") + mambo.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboGroundcam.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboGroundcam.py new file mode 100644 index 0000000..5f44d2c --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboGroundcam.py @@ -0,0 +1,41 @@ +""" +Demo of the groundcam +Mambo takes off, takes a picture and shows a RANDOM frame, not the last one +Author: Valentin Benke, https://github.com/Vabe7 +Author: Amy McGovern +""" + +from pyparrot.Minidrone import Mambo +import cv2 + +mambo = Mambo(None, use_wifi=True) #address is None since it only works with WiFi anyway +print("trying to connect to mambo now") +success = mambo.connect(num_retries=3) +print("connected: %s" % success) + +if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(1) + mambo.ask_for_state_update() + mambo.smart_sleep(1) + mambo.safe_takeoff(5) + + # take the photo + pic_success = mambo.take_picture() + + # need to wait a bit for the photo to show up + mambo.smart_sleep(0.5) + + picture_names = mambo.groundcam.get_groundcam_pictures_names() #get list of availible files + print(picture_names) + + frame = mambo.groundcam.get_groundcam_picture(picture_names[0],True) #get frame which is the first in the array + + if frame is not None: + if frame is not False: + cv2.imshow("Groundcam", frame) + cv2.waitKey(100) + + mambo.safe_land(5) + mambo.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboGun.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboGun.py new file mode 100644 index 0000000..aec7f31 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboGun.py @@ -0,0 +1,33 @@ +""" +Demo the gun for the python interface + +Author: Amy McGovern +""" + +from pyparrot.Minidrone import Mambo + +# you will need to change this to the address of YOUR mambo +mamboAddr = "e0:14:d0:63:3d:d0" + +# make my mambo object +# remember you can't use the gun with the camera installed so this must be BLE connected to work +mambo = Mambo(mamboAddr, use_wifi=False) + +print("trying to connect") +success = mambo.connect(num_retries=3) +print("connected: %s" % success) + +# get the state information +print ("sleeping") +mambo.smart_sleep(2) +mambo.ask_for_state_update() +mambo.smart_sleep(2) + +print("shoot the gun") +mambo.fire_gun() + +# sleep to ensure it does the firing +mambo.smart_sleep(15) + +print("disconnect") +mambo.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboTricks.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboTricks.py new file mode 100644 index 0000000..f616ddc --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboTricks.py @@ -0,0 +1,66 @@ +""" +Demo the trick flying for the python interface + +Author: Amy McGovern +""" + +from pyparrot.Minidrone import Mambo + +# If you are using BLE: you will need to change this to the address of YOUR mambo +# if you are using Wifi, this can be ignored +mamboAddr = "e0:14:d0:63:3d:d0" + +# make my mambo object +# remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect +mambo = Mambo(mamboAddr, use_wifi=True) + +print("trying to connect") +success = mambo.connect(num_retries=3) +print("connected: %s" % success) + +if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(2) + mambo.ask_for_state_update() + mambo.smart_sleep(2) + + print("taking off!") + mambo.safe_takeoff(5) + + if (mambo.sensors.flying_state != "emergency"): + print("flying state is %s" % mambo.sensors.flying_state) + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=20, duration=1) + + print("flip left") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="left") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("flip right") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="right") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("flip front") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="front") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("flip back") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="back") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("landing") + print("flying state is %s" % mambo.sensors.flying_state) + mambo.safe_land(5) + mambo.smart_sleep(5) + + print("disconnect") + mambo.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboVision.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboVision.py new file mode 100644 index 0000000..8c09c95 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboVision.py @@ -0,0 +1,92 @@ +""" +Demo of the ffmpeg based mambo vision code (basically flies around and saves out photos as it flies) + +Author: Amy McGovern +""" +from pyparrot.Minidrone import Mambo +from pyparrot.DroneVision import DroneVision +import threading +import cv2 +import time + + +# set this to true if you want to fly for the demo +testFlying = False + +class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + print("in save pictures on image %d " % self.index) + + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + cv2.imwrite(filename, img) + self.index +=1 + #print(self.index) + + + +# you will need to change this to the address of YOUR mambo +mamboAddr = "e0:14:d0:63:3d:d0" + +# make my mambo object +# remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect +mambo = Mambo(mamboAddr, use_wifi=True) +print("trying to connect to mambo now") +success = mambo.connect(num_retries=3) +print("connected: %s" % success) + +if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(1) + mambo.ask_for_state_update() + mambo.smart_sleep(1) + + print("Preparing to open vision") + mamboVision = DroneVision(mambo, is_bebop=False, buffer_size=30) + userVision = UserVision(mamboVision) + mamboVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + success = mamboVision.open_video() + print("Success in opening vision is %s" % success) + + if (success): + print("Vision successfully started!") + #removed the user call to this function (it now happens in open_video()) + #mamboVision.start_video_buffering() + + if (testFlying): + print("taking off!") + mambo.safe_takeoff(5) + + if (mambo.sensors.flying_state != "emergency"): + print("flying state is %s" % mambo.sensors.flying_state) + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=20, duration=1) + + print("flip left") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="left") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("landing") + print("flying state is %s" % mambo.sensors.flying_state) + mambo.safe_land(5) + else: + print("Sleeeping for 15 seconds - move the mambo around") + mambo.smart_sleep(15) + + # done doing vision demo + print("Ending the sleep and vision") + mamboVision.close_video() + + mambo.smart_sleep(5) + + print("disconnecting") + mambo.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboVisionGUI.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboVisionGUI.py new file mode 100644 index 0000000..37b8064 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboVisionGUI.py @@ -0,0 +1,97 @@ +""" +Demo of the Bebop vision using DroneVisionGUI that relies on libVLC. It is a different +multi-threaded approach than DroneVision + +Author: Amy McGovern +""" +from pyparrot.Minidrone import Mambo +from pyparrot.DroneVisionGUI import DroneVisionGUI +import cv2 + + +# set this to true if you want to fly for the demo +testFlying = True + +class UserVision: + def __init__(self, vision): + self.index = 0 + self.vision = vision + + def save_pictures(self, args): + # print("in save pictures on image %d " % self.index) + + img = self.vision.get_latest_valid_picture() + + if (img is not None): + filename = "test_image_%06d.png" % self.index + # uncomment this if you want to write out images every time you get a new one + #cv2.imwrite(filename, img) + self.index +=1 + #print(self.index) + + +def demo_mambo_user_vision_function(mamboVision, args): + """ + Demo the user code to run with the run button for a mambo + + :param args: + :return: + """ + mambo = args[0] + + if (testFlying): + print("taking off!") + mambo.safe_takeoff(5) + + if (mambo.sensors.flying_state != "emergency"): + print("flying state is %s" % mambo.sensors.flying_state) + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=15, duration=2) + + print("flip left") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="left") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("landing") + print("flying state is %s" % mambo.sensors.flying_state) + mambo.safe_land(5) + else: + print("Sleeeping for 15 seconds - move the mambo around") + mambo.smart_sleep(15) + + # done doing vision demo + print("Ending the sleep and vision") + mamboVision.close_video() + + mambo.smart_sleep(5) + + print("disconnecting") + mambo.disconnect() + + +if __name__ == "__main__": + # you will need to change this to the address of YOUR mambo + mamboAddr = "e0:14:d0:63:3d:d0" + + # make my mambo object + # remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect + mambo = Mambo(mamboAddr, use_wifi=True) + print("trying to connect to mambo now") + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(1) + mambo.ask_for_state_update() + mambo.smart_sleep(1) + + print("Preparing to open vision") + mamboVision = DroneVisionGUI(mambo, is_bebop=False, buffer_size=200, + user_code_to_run=demo_mambo_user_vision_function, user_args=(mambo, )) + userVision = UserVision(mamboVision) + mamboVision.set_user_callback_function(userVision.save_pictures, user_callback_args=None) + mamboVision.open_video() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoMamboVisionGUITwoWindows.py b/RPI Code/pyparrot_/pyparrot/examples/demoMamboVisionGUITwoWindows.py new file mode 100644 index 0000000..48975b6 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoMamboVisionGUITwoWindows.py @@ -0,0 +1,107 @@ +""" +Demo of the Mambo vision using DroneVisionGUI that relies on libVLC and shows how to make a +second window using opencv to draw on the processed window. It is a different +multi-threaded approach than DroneVision + +Author: Amy McGovern +""" +from pyparrot.Minidrone import Mambo +from pyparrot.DroneVisionGUI import DroneVisionGUI +import cv2 +import time + + +# set this to true if you want to fly for the demo +testFlying = True +font = cv2.FONT_HERSHEY_SIMPLEX + +def draw_second_pictures(args): + """ + Grab the latest stream from the drone and draw it in a second opencv window with some text to show that it + is being processed + + :param args: + :return: + """ + + # get the vision + mamboVision = args[0] + + # get the latest images + img = mamboVision.get_latest_valid_picture() + + # if the images is invalid, return + if(img is None): + return + + # put the roll and pitch at the top of the screen + cv2.putText(img, 'demo text', (50, 50), font, 1, (255, 0, 255), 2, cv2.LINE_AA) + cv2.imshow("MarkerStream", img) + +def demo_mambo_user_vision_function(mamboVision, args): + """ + Demo the user code to run with the run button for a mambo + + :param args: + :return: + """ + mambo = args[0] + + if (testFlying): + print("taking off!") + mambo.safe_takeoff(5) + + if (mambo.sensors.flying_state != "emergency"): + print("flying state is %s" % mambo.sensors.flying_state) + print("Flying direct: going up") + mambo.fly_direct(roll=0, pitch=0, yaw=0, vertical_movement=15, duration=2) + + print("flip left") + print("flying state is %s" % mambo.sensors.flying_state) + success = mambo.flip(direction="left") + print("mambo flip result %s" % success) + mambo.smart_sleep(5) + + print("landing") + print("flying state is %s" % mambo.sensors.flying_state) + mambo.safe_land(5) + else: + print("Sleeeping for 15 seconds - move the mambo around") + mambo.smart_sleep(15) + + # done doing vision demo + print("Ending the sleep and vision") + mamboVision.close_video() + + mambo.smart_sleep(5) + + print("disconnecting") + mambo.disconnect() + + +if __name__ == "__main__": + # make my mambo object + # remember to set True/False for the wifi depending on if you are using the wifi or the BLE to connect + # the address can be empty if you are using wifi + mambo = Mambo(address="", use_wifi=True) + print("trying to connect to mambo now") + + success = mambo.connect(num_retries=3) + print("connected: %s" % success) + + if (success): + # get the state information + print("sleeping") + mambo.smart_sleep(1) + mambo.ask_for_state_update() + mambo.smart_sleep(1) + + # setup the extra window to draw the markers in + cv2.namedWindow("ExampleWindow") + + print("Preparing to open vision") + mamboVision = DroneVisionGUI(mambo, is_bebop=False, buffer_size=200, + user_code_to_run=demo_mambo_user_vision_function, user_args=(mambo, )) + + mamboVision.set_user_callback_function(draw_second_pictures, user_callback_args=(mamboVision, )) + mamboVision.open_video() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoSwingDirectFlight.py b/RPI Code/pyparrot_/pyparrot/examples/demoSwingDirectFlight.py new file mode 100644 index 0000000..53451e5 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoSwingDirectFlight.py @@ -0,0 +1,42 @@ +""" +Demo the direct flying for the python interface + +Author: Victor804 +""" + +from pyparrot.Minidrone import Swing + +# you will need to change this to the address of YOUR swing +swingAddr = "e0:14:04:a7:3d:cb" + +# make my swing object +swing = Swing(swingAddr) + +print("trying to connect") +success = swing.connect(num_retries=3) +print("connected: %s" % success) + +if (success): + # get the state information + print("sleeping") + swing.smart_sleep(2) + swing.ask_for_state_update() + swing.smart_sleep(2) + + print("taking off!") + swing.safe_takeoff(5) + + print("plane forward") + swing.set_flying_mode("plane_forward") + + swing.smart_sleep(1) + + print("quadricopter") + swing.set_flying_mode("quadricopter") + + print("landing") + swing.safe_land(5) + swing.smart_sleep(5) + + print("disconnect") + swing.disconnect() diff --git a/RPI Code/pyparrot_/pyparrot/examples/demoSwingJoystick.py b/RPI Code/pyparrot_/pyparrot/examples/demoSwingJoystick.py new file mode 100644 index 0000000..c935260 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/examples/demoSwingJoystick.py @@ -0,0 +1,173 @@ +import pygame +import sys +from pyparrot.Minidrone import Swing + +def joystick_init(): + """ + Initializes the controller, allows the choice of the controller. + If no controller is detected returns an error. + + :param: + :return joystick: + """ + pygame.init() + pygame.joystick.init() + + joystick_count = pygame.joystick.get_count() + + if joystick_count > 0: + for i in range(joystick_count): + joystick = pygame.joystick.Joystick(i) + joystick.init() + + name = joystick.get_name() + print([i], name) + + joystick.quit() + + else: + sys.exit("Error: No joystick detected") + + selected_joystick = eval(input("Enter your joystick number:")) + + if selected_joystick not in range(joystick_count): + sys.exit("Error: Your choice is not valid") + + joystick = pygame.joystick.Joystick(selected_joystick) + joystick.init() + + return joystick + + +def mapping_button(joystick, dict_commands): + """ + Associating a controller key with a command in dict_commands. + + :param joystick, dict_commands: + :return mapping: + """ + mapping = {} + + for command in dict_commands: + print("Press the key", command) + done = False + while not done: + for event in pygame.event.get(): + if event.type == pygame.JOYBUTTONDOWN: + if event.button not in (value for value in mapping.values()): + mapping[command] = event.button + done = True + + return mapping + + +def mapping_axis(joystick, axes=["pitch", "roll", "yaw", "vertical"]): + """ + Associating the analog thumbsticks of the controller with a command in dict commands + + :param joystick, dict_commands: + :return mapping: + """ + mapping = {} + + for i in axes: + print("Push the", i, "axis") + done = False + while not done: + for event in pygame.event.get(): + if event.type == pygame.JOYAXISMOTION: + if event.axis not in (value for value in mapping.values()): + mapping[i] = event.axis + done = True + + return mapping + + +def _parse_button(dict_commands, button): + """ + Send the commands to the drone. + If multiple commands are assigned to a key each command will be sent one by one to each press. + + :param dict_commands, button: + :return: + """ + commands = dict_commands[button][0] + args = dict_commands[button][-1] + + command = commands[0] + arg = args[0] + + if len(commands) == 1: + if len(args) == 1: + command(arg) + + else: + command(arg) + dict_commands[button][-1] = args[1:]+[arg] + + else: + if len(commands) == 1: + command(arg) + dict_commands[button][0] = commands[1:]+[command] + + else: + command(arg) + dict_commands[button][0] = commands[1:]+[command] + dict_commands[button][-1] = args[1:]+[arg] + + +def main_loop(joystick, dict_commands, mapping_button, mapping_axis): + """ + First connects to the drone and makes a flat trim. + Then in a loop read the events of the controller to send commands to the drone. + + :param joystick, dict_commands, mapping_button, mapping_axis: + :return: + """ + swing.connect(10) + swing.flat_trim() + + while True: + pygame.event.get() + + pitch = joystick.get_axis(mapping_axis["pitch"])*-100 + roll = joystick.get_axis(mapping_axis["roll"])*100 + yaw = joystick.get_axis(mapping_axis["yaw"])*100 + vertical = joystick.get_axis(mapping_axis["vertical"])*-100 + + swing.fly_direct(roll, pitch, yaw, vertical, 0.1) + + for button, value in mapping_button.items(): + if joystick.get_button(value): + _parse_button(dict_commands, button) + + +if __name__ == "__main__": + swing = Swing("e0:14:04:a7:3d:cb") + + #Example of dict_commands + dict_commands = { + "takeoff_landing":[ #Name of the button + [swing.safe_takeoff, swing.safe_land],#Commands execute one by one + [5]#Argument for executing the function + ], + "fly_mode":[ + [swing.set_flying_mode], + ["quadricopter", "plane_forward"] + ], + "plane_gear_box_up":[ + [swing.set_plane_gear_box], + [((swing.sensors.plane_gear_box[:-1]+str(int(swing.sensors.plane_gear_box[-1])+1)) if swing.sensors.plane_gear_box[-1] != "3" else "gear_3")]#"gear_1" => "gear_2" => "gear_3" + ], + "plane_gear_box_down":[ + [swing.set_plane_gear_box], + [((swing.sensors.plane_gear_box[:-1]+str(int(swing.sensors.plane_gear_box[-1])-1)) if swing.sensors.plane_gear_box[-1] != "1" else "gear_1")]#"gear_3" => "gear_2" => "gear_1" + ] + } + + joystick = joystick_init() + + mapping_button = mapping_button(joystick, dict_commands) + mapping_axis = mapping_axis(joystick) + + main_loop(joystick, dict_commands, mapping_button, mapping_axis) diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/Bebop.py b/RPI Code/pyparrot_/pyparrot/pyparrot/Bebop.py new file mode 100644 index 0000000..6cd4e0a --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/Bebop.py @@ -0,0 +1,883 @@ +""" +Bebop class holds all of the methods needed to pilot the drone from python and to ask for sensor +data back from the drone + +Author: Amy McGovern, dramymcgovern@gmail.com +""" +import time +from pyparrot.networking.wifiConnection import WifiConnection +from pyparrot.utils.colorPrint import color_print +from pyparrot.commandsandsensors.DroneCommandParser import DroneCommandParser +from pyparrot.commandsandsensors.DroneSensorParser import DroneSensorParser +from datetime import datetime + +class BebopSensors: + def __init__(self): + self.sensors_dict = dict() + self.RelativeMoveEnded = False + self.CameraMoveEnded_tilt = False + self.CameraMoveEnded_pan = False + self.flying_state = "unknown" + self.flat_trim_changed = False + self.max_altitude_changed = False + self.max_distance_changed = False + self.no_fly_over_max_distance = False + self.max_tilt_changed = False + self.max_pitch_roll_rotation_speed_changed = False + self.max_vertical_speed = False + self.max_rotation_speed = False + self.hull_protection_changed = False + self.outdoor_mode_changed = False + self.picture_format_changed = False + self.auto_white_balance_changed = False + self.exposition_changed = False + self.saturation_changed = False + self.timelapse_changed = False + self.video_stabilization_changed = False + self.video_recording_changed = False + self.video_framerate_changed = False + self.video_resolutions_changed = False + + # default to full battery + self.battery = 100 + + # this is optionally set elsewhere + self.user_callback_function = None + + def set_user_callback_function(self, function, args): + """ + Sets the user callback function (called everytime the sensors are updated) + + :param function: name of the user callback function + :param args: arguments (tuple) to the function + :return: + """ + self.user_callback_function = function + self.user_callback_function_args = args + + def update(self, sensor_name, sensor_value, sensor_enum): + if (sensor_name is None): + print("Error empty sensor") + return + + + if (sensor_name, "enum") in sensor_enum: + # grab the string value + if (sensor_value is None or sensor_value > len(sensor_enum[(sensor_name, "enum")])): + value = "UNKNOWN_ENUM_VALUE" + else: + enum_value = sensor_enum[(sensor_name, "enum")][sensor_value] + value = enum_value + + self.sensors_dict[sensor_name] = value + + else: + # regular sensor + self.sensors_dict[sensor_name] = sensor_value + + # some sensors are saved outside the dictionary for internal use (they are also in the dictionary) + if (sensor_name == "FlyingStateChanged_state"): + self.flying_state = self.sensors_dict["FlyingStateChanged_state"] + + if (sensor_name == "PilotingState_FlatTrimChanged"): + self.flat_trim_changed = True + + if (sensor_name == "moveByEnd_dX"): + self.RelativeMoveEnded = True + + if (sensor_name == "OrientationV2_tilt"): + self.CameraMoveEnded_tilt = True + + if (sensor_name == "OrientationV2_pan"): + self.CameraMoveEnded_pan = True + + if (sensor_name == "MaxAltitudeChanged_current"): + self.max_altitude_changed = True + + if (sensor_name == "MaxDistanceChanged_current"): + self.max_distance_changed = True + + if (sensor_name == "NoFlyOverMaxDistanceChanged_shouldNotFlyOver"): + self.no_fly_over_max_distance_changed = True + + if (sensor_name == "MaxTiltChanged_current"): + self.max_tilt_changed = True + + if (sensor_name == "MaxPitchRollRotationSpeedChanged_current"): + self.max_pitch_roll_rotation_speed_changed = True + + if (sensor_name == "MaxVerticalSpeedChanged_current"): + self.max_vertical_speed_changed = True + + if (sensor_name == "MaxRotationSpeedChanged_current"): + self.max_rotation_speed_changed = True + + if (sensor_name == "HullProtectionChanged_present"): + self.hull_protection_changed = True + + if (sensor_name == "OutdoorChanged_present"): + self.outdoor_mode_changed = True + + if (sensor_name == "BatteryStateChanged_battery_percent"): + self.battery = sensor_value + + if (sensor_name == "PictureFormatChanged_type"): + self.picture_format_changed = True + + if (sensor_name == "AutoWhiteBalanceChanged_type"): + self.auto_white_balance_changed = True + + if (sensor_name == "ExpositionChanged_value"): + self.exposition_changed = True + + if (sensor_name == "SaturationChanged_value"): + self.saturation_changed = True + + if (sensor_name == "TimelapseChanged_enabled"): + self.timelapse_changed = True + + if (sensor_name == "VideoStabilizationModeChanged_mode"): + self.video_stabilization_changed = True + + if (sensor_name == "VideoRecordingModeChanged_mode"): + self.video_recording_changed = True + + if (sensor_name == "VideoFramerateChanged_framerate"): + self.video_framerate_changed = True + + if (sensor_name == "VideoResolutionsChanged_type"): + self.video_resolutions_changed = True + + # call the user callback if it isn't None + if (self.user_callback_function is not None): + self.user_callback_function(self.user_callback_function_args) + + + def __str__(self): + str = "Bebop sensors: %s" % self.sensors_dict + return str + +class Bebop(): + def __init__(self, drone_type="Bebop2", ip_address=None): + """ + Create a new Bebop object. Assumes you have connected to the Bebop's wifi + + """ + self.drone_type = drone_type + + self.drone_connection = WifiConnection(self, drone_type=drone_type, ip_address=ip_address) + + # intialize the command parser + self.command_parser = DroneCommandParser() + + # initialize the sensors and the parser + self.sensors = BebopSensors() + self.sensor_parser = DroneSensorParser(drone_type=drone_type) + + def set_user_sensor_callback(self, function, args): + """ + Set the (optional) user callback function for sensors. Every time a sensor + is updated, it calls this function. + + :param function: name of the function + :param args: tuple of arguments to the function + :return: nothing + """ + self.sensors.set_user_callback_function(function, args) + + def update_sensors(self, data_type, buffer_id, sequence_number, raw_data, ack): + """ + Update the sensors (called via the wifi or ble connection) + + :param data: raw data packet that needs to be parsed + :param ack: True if this packet needs to be ack'd and False otherwise + """ + #print("data type is %d buffer id is %d sequence number is %d " % (data_type, buffer_id, sequence_number)) + sensor_list = self.sensor_parser.extract_sensor_values(raw_data) + #print(sensor_list) + if (sensor_list is not None): + for sensor in sensor_list: + (sensor_name, sensor_value, sensor_enum, header_tuple) = sensor + if (sensor_name is not None): + self.sensors.update(sensor_name, sensor_value, sensor_enum) + else: + color_print("data type %d buffer id %d sequence number %d" % (data_type, buffer_id, sequence_number), "WARN") + color_print("This sensor is missing (likely because we don't need it)", "WARN") + + if (ack): + self.drone_connection.ack_packet(buffer_id, sequence_number) + + def connect(self, num_retries): + """ + Connects to the drone and re-tries in case of failure the specified number of times. Seamlessly + connects to either wifi or BLE depending on how you initialized it + + :param: num_retries is the number of times to retry + + :return: True if it succeeds and False otherwise + """ + + # special case for when the user tries to do BLE when it isn't available + if (self.drone_connection is None): + return False + + connected = self.drone_connection.connect(num_retries) + return connected + + + def disconnect(self): + """ + Disconnect the BLE connection. Always call this at the end of your programs to + cleanly disconnect. + + :return: void + """ + self.drone_connection.disconnect() + + def ask_for_state_update(self): + """ + Ask for a full state update (likely this should never be used but it can be called if you want to see + everything the bebop is storing) + + :return: nothing but it will eventually fill the sensors with all of the state variables as they arrive + """ + command_tuple = self.command_parser.get_command_tuple("common", "Common", "AllStates") + return self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + def flat_trim(self, duration=0): + """ + Sends the flat_trim command to the bebop. Gets the codes for it from the xml files. + :param duration: if duration is greater than 0, waits for the trim command to be finished or duration to be reached + """ + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Piloting", "FlatTrim") + self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + if (duration > 0): + # wait for the specified duration + start_time = datetime.now() + new_time = datetime.now() + diff = (new_time - start_time).seconds + ((new_time - start_time).microseconds / 1000000.0) + + while (not self.sensors.flat_trim_changed and diff < duration): + self.smart_sleep(0.1) + + new_time = datetime.now() + diff = (new_time - start_time).seconds + ((new_time - start_time).microseconds / 1000000.0) + + + + + def takeoff(self): + """ + Sends the takeoff command to the bebop. Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Piloting", "TakeOff") + self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + def safe_takeoff(self, timeout): + """ + Sends commands to takeoff until the Bebop reports it is taking off + + :param timeout: quit trying to takeoff if it takes more than timeout seconds + """ + + start_time = time.time() + # take off until it really listens + while (self.sensors.flying_state != "takingoff" and (time.time() - start_time < timeout)): + if (self.sensors.flying_state == "emergency"): + return + success = self.takeoff() + self.smart_sleep(1) + + # now wait until it finishes takeoff before returning + while ((self.sensors.flying_state not in ("flying", "hovering") and + (time.time() - start_time < timeout))): + if (self.sensors.flying_state == "emergency"): + return + self.smart_sleep(1) + + + def land(self): + """ + Sends the land command to the bebop. Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Piloting", "Landing") + return self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + def emergency_land(self): + """ + Sends the land command to the bebop on the high priority/emergency channel. + Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Piloting", "Landing") + return self.drone_connection.send_noparam_high_priority_command_packet(command_tuple) + + def is_landed(self): + """ + Returns true if it is landed or emergency and False otherwise + :return: + """ + if (self.sensors.flying_state in ("landed", "emergency")): + return True + else: + return False + + def safe_land(self, timeout): + """ + Ensure the Bebop lands by sending the command until it shows landed on sensors + """ + start_time = time.time() + + while (self.sensors.flying_state not in ("landing", "landed") and (time.time() - start_time < timeout)): + if (self.sensors.flying_state == "emergency"): + return + color_print("trying to land", "INFO") + success = self.land() + self.smart_sleep(1) + + while (self.sensors.flying_state != "landed" and (time.time() - start_time < timeout)): + if (self.sensors.flying_state == "emergency"): + return + self.smart_sleep(1) + + def smart_sleep(self, timeout): + """ + Don't call time.sleep directly as it will mess up BLE and miss WIFI packets! Use this + which handles packets received while sleeping + + :param timeout: number of seconds to sleep + """ + self.drone_connection.smart_sleep(timeout) + + def _ensure_fly_command_in_range(self, value): + """ + Ensure the fly direct commands are in range + + :param value: the value sent by the user + :return: a value in the range -100 to 100 + """ + if (value < -100): + return -100 + elif (value > 100): + return 100 + else: + return value + + def fly_direct(self, roll, pitch, yaw, vertical_movement, duration): + """ + Direct fly commands using PCMD. Each argument ranges from -100 to 100. Numbers outside that are clipped + to that range. + + Note that the xml refers to gaz, which is apparently french for vertical movements: + http://forum.developer.parrot.com/t/terminology-of-gaz/3146 + + :param roll: + :param pitch: + :param yaw: + :param vertical_movement: + :return: + """ + + my_roll = self._ensure_fly_command_in_range(roll) + my_pitch = self._ensure_fly_command_in_range(pitch) + my_yaw = self._ensure_fly_command_in_range(yaw) + my_vertical = self._ensure_fly_command_in_range(vertical_movement) + + # print("roll is %d pitch is %d yaw is %d vertical is %d" % (my_roll, my_pitch, my_yaw, my_vertical)) + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Piloting", "PCMD") + + self.drone_connection.send_pcmd_command(command_tuple, my_roll, my_pitch, my_yaw, my_vertical, duration) + + + def flip(self, direction): + """ + Sends the flip command to the bebop. Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + Valid directions to flip are: front, back, right, left + + :return: True if the command was sent and False otherwise + """ + fixed_direction = direction.lower() + if (fixed_direction not in ("front", "back", "right", "left")): + print("Error: %s is not a valid direction. Must be one of %s" % direction, "front, back, right, or left") + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", + "Animations", "Flip", fixed_direction) + # print command_tuple + # print enum_tuple + + return self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + def move_relative(self, dx, dy, dz, dradians): + """ + Move relative to our current position and pause until the command is done. Note that + EVERY time we tested flying relative up (e.g. negative z) it did additional lateral moves + that were unnecessary. I'll be posting this to the development board but, until then, + I recommend only using dx, dy, and dradians which all seem to work well. + + :param dx: change in front axis (meters) + :param dy: change in right/left (positive is right) (meters) + :param dz: change in height (positive is DOWN) (meters) + :param dradians: change in heading in radians + + :return: nothing + """ + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Piloting", "moveBy") + param_tuple = [dx, dy, dz, dradians] # Enable + param_type_tuple = ['float', 'float', 'float', 'float'] + #reset the bit that tells when the move ends + self.sensors.RelativeMoveEnded = False + + # send the command + self.drone_connection.send_param_command_packet(command_tuple, param_tuple, param_type_tuple) + + # sleep until it ends + while (not self.sensors.RelativeMoveEnded): + self.smart_sleep(0.01) + + + def start_video_stream(self): + """ + Sends the start stream command to the bebop. The bebop will start streaming + RTP packets on the port defined in wifiConnection.py (55004 by default). + The packets can be picked up by opening an approriate SDP file in a media + player such as VLC, MPlayer, FFMPEG or OpenCV. + + :return: nothing + """ + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "MediaStreaming", "VideoEnable") + param_tuple = [1] # Enable + param_type_tuple = ['u8'] + self.drone_connection.send_param_command_packet(command_tuple,param_tuple,param_type_tuple) + + + def stop_video_stream(self): + """ + Sends the stop stream command to the bebop. The bebop will stop streaming + RTP packets. + + :return: nothing + """ + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "MediaStreaming", "VideoEnable") + param_tuple = [0] # Disable + param_type_tuple = ['u8'] + self.drone_connection.send_param_command_packet(command_tuple,param_tuple,param_type_tuple) + + + def set_video_stream_mode(self,mode='low_latency'): + """ + Set the video mode for the RTP stream. + :param: mode: one of 'low_latency', 'high_reliability' or 'high_reliability_low_framerate' + + :return: True if the command was sent and False otherwise + """ + + # handle case issues + fixed_mode = mode.lower() + + if (fixed_mode not in ("low_latency", "high_reliability", "high_reliability_low_framerate")): + print("Error: %s is not a valid stream mode. Must be one of %s" % (mode, "low_latency, high_reliability or high_reliability_low_framerate")) + print("Ignoring command and returning") + return False + + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", + "MediaStreaming", "VideoStreamMode", mode) + + return self.drone_connection.send_enum_command_packet_ack(command_tuple,enum_tuple) + + def pan_tilt_camera(self, tilt_degrees, pan_degrees): + """ + Send the command to pan/tilt the camera by the specified number of degrees in pan/tilt + + Note, this only seems to work in small increments. Use pan_tilt_velocity to get the camera to look + straight downward + + :param tilt_degrees: tilt degrees + :param pan_degrees: pan degrees + :return: + """ + if(self.drone_type == "Bebop2"): + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Camera", "OrientationV2") + + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[tilt_degrees, pan_degrees], + param_type_tuple=['float', 'float'], ack=False) + else: + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Camera", "Orientation") + + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[tilt_degrees, pan_degrees], + param_type_tuple=['i8', 'i8'], ack=False) + + def pan_tilt_camera_velocity(self, tilt_velocity, pan_velocity, duration=0): + """ + Send the command to tilt the camera by the specified number of degrees per second in pan/tilt. + This function has two modes. First, if duration is 0, the initial velocity is sent and + then the function returns (meaning the camera will keep moving). If duration is greater than 0, + the command executes for that amount of time and then sends a stop command to the camera + and then returns. + + :param tilt_degrees: tile change in degrees per second + :param pan_degrees: pan change in degrees per second + :param duration: seconds to run the command for + :return: + """ + command_tuple = self.command_parser.get_command_tuple("ardrone3", "Camera", "Velocity") + + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[tilt_velocity, pan_velocity], + param_type_tuple=['float', 'float'], ack=False) + + + if (duration > 0): + # wait for the specified duration + start_time = time.time() + while (time.time() - start_time < duration): + self.drone_connection.smart_sleep(0.1) + + # send the stop command + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[0, 0], + param_type_tuple=['float', 'float'], ack=False) + + def set_max_altitude(self, altitude): + """ + Set max altitude in meters. + + :param altitude: altitude in meters + :return: + """ + if (altitude < 0.5 or altitude > 150): + print("Error: %s is not valid altitude. The altitude must be between 0.5 and 150 meters" % altitude) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "PilotingSettings", "MaxAltitude") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[altitude], param_type_tuple=['float']) + + while (not self.sensors.max_altitude_changed): + self.smart_sleep(0.1) + + def set_max_distance(self, distance): + """ + Set max distance between the takeoff and the drone in meters. + + :param distance: distance in meters + :return: + """ + if (distance < 10 or distance > 2000): + print("Error: %s is not valid altitude. The distance must be between 10 and 2000 meters" % distance) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "PilotingSettings", "MaxDistance") + + self.sensors.max_distance_changed = False + + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[distance], param_type_tuple=['float']) + + while (not self.sensors.max_distance_changed): + self.smart_sleep(0.1) + + def enable_geofence(self, value): + """ + If geofence is enabled, the drone won't fly over the given max distance. + 1 if the drone can't fly further than max distance, 0 if no limitation on the drone should be done. + + :param value: + :return: + """ + if (value not in (0, 1)): + print("Error: %s is not valid value. Valid value: 1 to enable geofence/ 0 to disable geofence" % value) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "PilotingSettings", "NoFlyOverMaxDistance") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[value], param_type_tuple=['u8']) + + while (not self.sensors.no_fly_over_max_distance_changed): + self.smart_sleep(0.1) + + def set_max_tilt(self, tilt): + """ + Set max pitch/roll in degrees + + :param tilt: max tilt for both pitch and roll in degrees + :return: + """ + if (tilt < 5 or tilt > 30): + print("Error: %s is not valid tilt. The tilt must be between 5 and 30 degrees" % tilt) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "PilotingSettings", "MaxTilt") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[tilt], param_type_tuple=['float']) + + while (not self.sensors.max_tilt_changed): + self.smart_sleep(0.1) + + def set_max_tilt_rotation_speed(self, speed): + """ + Set max pitch/roll rotation speed in degree/s + + :param speed: max rotation speed for both pitch and roll in degree/s + :return: + """ + if (speed < 80 or speed > 300): + print("Error: %s is not valid speed. The speed must be between 80 and 300 degree/s" % speed) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "SpeedSettings", "MaxPitchRollRotationSpeed") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[speed], param_type_tuple=['float']) + + while (not self.sensors.max_pitch_roll_rotation_speed_changed): + self.smart_sleep(0.1) + + def set_max_vertical_speed(self, speed): + """ + Set max vertical speed in m/s + + :param speed: max vertical speed in m/s + :return: + """ + if (speed < 0.5 or speed > 2.5): + print("Error: %s is not valid speed. The speed must be between 0.5 and 2.5 m/s" % speed) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "SpeedSettings", "MaxVerticalSpeed") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[speed], param_type_tuple=['float']) + + while (not self.sensors.max_vertical_speed_changed): + self.smart_sleep(0.1) + + def set_max_rotation_speed(self, speed): + """ + Set max yaw rotation speed in degree/s + + :param speed: max rotation speed for yaw in degree/s + :return: + """ + if (speed < 10 or speed > 200): + print("Error: %s is not valid speed. The speed must be between 10 and 200 degree/s" % speed) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "SpeedSettings", "MaxRotationSpeed") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[speed], param_type_tuple=['float']) + + while (not self.sensors.max_rotation_speed_changed): + self.smart_sleep(0.1) + + def set_hull_protection(self, present): + """ + Set the presence of hull protection - this is only needed for bebop 1 + 1 if present, 0 if not present + + :param present: + :return: + """ + if (present not in (0, 1)): + print("Error: %s is not valid value. The value must be 0 or 1" % present) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "SpeedSettings", "HullProtection") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[present], param_type_tuple=['u8']) + + while (not self.sensors.hull_protection_changed): + self.smart_sleep(0.1) + + def set_indoor(self, is_outdoor): + """ + Set bebop 1 to indoor mode (not used in bebop 2!!) + 1 if outdoor, 0 if indoor + + :param present: + :return: + """ + if (is_outdoor not in (0, 1)): + print("Error: %s is not valid value. The value must be 0 or 1" % is_outdoor) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "SpeedSettings", "Outdoor") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[is_outdoor], param_type_tuple=['u8']) + + #while (not self.sensors.outdoor_mode_changed): + # self.smart_sleep(0.1) + + def set_picture_format(self, format): + """ + Set picture format + + :param format: + :return: + """ + if (format not in ('raw', 'jpeg', 'snapshot', 'jpeg_fisheye')): + print("Error: %s is not valid value. The value must be : raw, jpeg, snapshot, jpeg_fisheye" % format) + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", "PictureSettings", "PictureFormatSelection", format) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + while (not self.sensors.picture_format_changed): + self.smart_sleep(0.1) + + def set_white_balance(self, type): + """ + Set white balance + + :param type: + :return: + """ + if (type not in ('auto', 'tungsten', 'daylight', 'cloudy', 'cool_white')): + print("Error: %s is not valid value. The value must be : auto, tungsten, daylight, cloudy, cool_white" % type) + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", "PictureSettings", "AutoWhiteBalanceSelection", type) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + while (not self.sensors.auto_white_balance_changed): + self.smart_sleep(0.1) + + def set_exposition(self, value): + """ + Set image exposure + + :param value: + :return: + """ + if (value < -1.5 or value > 1.5): + print("Error: %s is not valid image exposure. The value must be between -1.5 and 1.5." % value) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "PictureSettings", "ExpositionSelection") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[value], param_type_tuple=['float']) + + while (not self.sensors.exposition_changed): + self.smart_sleep(0.1) + + def set_saturation(self, value): + """ + Set image saturation + + :param value: + :return: + """ + if (value < -100 or value > 100): + print("Error: %s is not valid image saturation. The value must be between -100 and 100." % value) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "PictureSettings", "SaturationSelection") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[value], param_type_tuple=['float']) + + while (not self.sensors.saturation_changed): + self.smart_sleep(0.1) + + def set_timelapse(self, enable, interval=8): + """ + Set timelapse mode + + :param enable: + :param interval: + :return: + """ + if (enable not in (0, 1) or interval < 8 or interval > 300): + print("Error: %s or %s is not valid value." % (enable, interval)) + print("Ignoring command and returning") + return + + command_tuple = self.command_parser.get_command_tuple("ardrone3", "PictureSettings", "TimelapseSelection") + self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[enable, interval], param_type_tuple=['u8', 'float']) + + while (not self.sensors.timelapse_changed): + self.smart_sleep(0.1) + + def set_video_stabilization(self, mode): + """ + Set video stabilization mode + + :param mode: + :return: + """ + if (mode not in ('roll_pitch', 'pitch', 'roll', 'none')): + print("Error: %s is not valid value. The value must be : roll_pitch, pitch, roll, none" % mode) + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", "PictureSettings", "VideoStabilizationMode", mode) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + while (not self.sensors.video_stabilization_changed): + self.smart_sleep(0.1) + + def set_video_recording(self, mode): + """ + Set video recording mode + + :param mode: + :return: + """ + if (mode not in ('quality', 'time')): + print("Error: %s is not valid value. The value must be : quality, time" % mode) + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", "PictureSettings", "VideoRecordingMode", mode) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + while (not self.sensors.video_recording_changed): + self.smart_sleep(0.1) + + def set_video_framerate(self, framerate): + """ + Set video framerate + + :param framerate: + :return: + """ + if (framerate not in ('24_FPS', '25_FPS', '30_FPS')): + print("Error: %s is not valid value. The value must be : 24_FPS, 25_FPS, 30_FPS" % framerate) + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", "PictureSettings", "VideoFramerate", framerate) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + while (not self.sensors.video_framerate_changed): + self.smart_sleep(0.1) + + def set_video_resolutions(self, type): + """ + Set video resolutions + + :param type: + :return: + """ + if (type not in ('rec1080_stream480', 'rec720_stream720')): + print("Error: %s is not valid value. The value must be : rec1080_stream480, rec720_stream720" % type) + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("ardrone3", "PictureSettings", "VideoResolutions", type) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + while (not self.sensors.video_resolutions_changed): + self.smart_sleep(0.1) diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/DroneVision.py b/RPI Code/pyparrot_/pyparrot/pyparrot/DroneVision.py new file mode 100644 index 0000000..4d422d0 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/DroneVision.py @@ -0,0 +1,295 @@ +""" +DroneVision is separated from the main Mambo/Bebop class to enable the use of the drone without the FPV camera. +If you want to do vision processing, you will need to create a DroneVision object to capture the +video stream. + +Note that this module relies on the opencv module and the ffmpeg program + +Ffmpeg write the images out to the images directory and then they are read in from the user thread. The DroneVisionGUI +does not save copies of the images and instead shows you the images on the screen (they are saved to memory only). +While you can see the images in real-time from this program using VisionServer, if you need copies of the images, +you will want to use the ffmpeg approach. If you want a smaller delay on your image data for real-time control, you likely want +to use libvlc and DroneVisionGUI. + +Author: Amy McGovern, dramymcgovern@gmail.com +""" +import cv2 +import threading +import time +import subprocess +import os +from os.path import join +import inspect +from pyparrot.utils.NonBlockingStreamReader import NonBlockingStreamReader +import shutil +import signal + +class DroneVision: + def __init__(self, drone_object, is_bebop, buffer_size=200, cleanup_old_images=True): + """ + Setup your vision object and initialize your buffers. You won't start seeing pictures + until you call open_video. + + :param drone_object reference to the drone (mambo or bebop) object + :param is_bebop: True if it is a bebop and false if it is a mambo + :param buffer_size: number of frames to buffer in memory. Defaults to 10. + :param cleanup_old_images: clean up the old images in the directory (defaults to True, set to false to keep old data around) + """ + self.fps = 30 + self.buffer_size = buffer_size + self.drone_object = drone_object + self.is_bebop = is_bebop + self.cleanup_old_images = cleanup_old_images + + # initialize a buffer (will contain the last buffer_size vision objects) + self.buffer = [None] * buffer_size + self.buffer_index = 0 + + # setup the thread for monitoring the vision (but don't start it until we connect in open_video) + self.vision_thread = threading.Thread(target=self._buffer_vision, + args=(buffer_size, )) + self.user_vision_thread = None + self.vision_running = True + + # the vision thread starts opencv on these files. That will happen inside the other thread + # so here we just sent the image index to 1 ( to start) + self.image_index = 1 + + + def set_user_callback_function(self, user_callback_function=None, user_callback_args=None): + """ + Set the (optional) user callback function for handling the new vision frames. This is + run in a separate thread that starts when you start the vision buffering + + :param user_callback_function: function + :param user_callback_args: arguments to the function + :return: + """ + self.user_vision_thread = threading.Thread(target=self._user_callback, + args=(user_callback_function, user_callback_args)) + + + def open_video(self): + """ + Open the video stream using ffmpeg for capturing and processing. The address for the stream + is the same for all Mambos and is documented here: + + http://forum.developer.parrot.com/t/streaming-address-of-mambo-fpv-for-videoprojection/6442/6 + + Remember that this will only work if you have connected to the wifi for your mambo! + + Note that the old method tried to open the stream directly into opencv but there are known issues + with rtsp streams in opencv. We bypassed opencv to use ffmpeg directly and then opencv is used to + process the output of ffmpeg + + :return True if the vision opened correctly and False otherwise + """ + + # start the stream on the bebop + if (self.is_bebop): + self.drone_object.start_video_stream() + + # we have bypassed the old opencv VideoCapture method because it was unreliable for rtsp + # get the path for the config files + fullPath = inspect.getfile(DroneVision) + shortPathIndex = fullPath.rfind("/") + if (shortPathIndex == -1): + # handle Windows paths + shortPathIndex = fullPath.rfind("\\") + print(shortPathIndex) + shortPath = fullPath[0:shortPathIndex] + self.imagePath = join(shortPath, "images") + self.utilPath = join(shortPath, "utils") + print(self.imagePath) + print(self.utilPath) + + if (self.cleanup_old_images): + print("removing all the old images") + shutil.rmtree(self.imagePath) + os.mkdir(self.imagePath) + + # the first step is to open the rtsp stream through ffmpeg first + # this step creates a directory full of images, one per frame + print("Opening ffmpeg") + if (self.is_bebop): + cmdStr = "ffmpeg -protocol_whitelist \"file,rtp,udp\" -i %s/bebop.sdp -r 30 image_" % self.utilPath + "%03d.png" + print(cmdStr) + self.ffmpeg_process = \ + subprocess.Popen(cmdStr, shell=True, cwd=self.imagePath, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + else: + self.ffmpeg_process = \ + subprocess.Popen("ffmpeg -i rtsp://192.168.99.1/media/stream2 -r 30 image_%03d.png", + shell=True, cwd=self.imagePath, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + + # immediately start the vision buffering (before we even know if it succeeded since waiting puts us behind) + self._start_video_buffering() + + # open non-blocking readers to look for errors or success + print("Opening non-blocking readers") + stderr_reader = NonBlockingStreamReader(self.ffmpeg_process.stderr) + stdout_reader = NonBlockingStreamReader(self.ffmpeg_process.stdout) + + + # look for success in the stdout + # If it starts correctly, it will have the following output in the stdout + # Stream mapping: + # Stream #0:0 -> #0:0 (h264 (native) -> png (native)) + + # if it fails, it has the following in stderr + # Output file #0 does not contain any stream + + success = False + while (not success): + + line = stderr_reader.readline() + if (line is not None): + line_str = line.decode("utf-8") + print(line_str) + if line_str.find("Stream #0:0 -> #0:0 (h264 (native) -> png (native))") > -1: + success = True + break + if line_str.find("Output file #0 does not contain any stream") > -1: + print("Having trouble connecting to the camera 1. A reboot of the mambo may help.") + break + + line = stdout_reader.readline() + if (line is not None): + line_str = line.decode("utf-8") + print(line_str) + if line_str.find("Output file #0 does not contain any stream") > -1: + print("Having trouble connecting to the camera 2. A reboot of the mambo may help.") + break + + if line_str.find("Stream #0:0 -> #0:0 (h264 (native) -> png (native))") > -1: + success = True + + # cleanup our non-blocking readers no matter what happened + stdout_reader.finish_reader() + stderr_reader.finish_reader() + + # return whether or not it worked + return success + + def _start_video_buffering(self): + """ + If the video capture was successfully opened, then start the thread to buffer the stream + + :return: Nothing + """ + print("starting vision thread") + self.vision_thread.start() + + if (self.user_vision_thread is not None): + self.user_vision_thread.start() + + def _user_callback(self, user_vision_function, user_args): + """ + Internal method to call the user vision functions + + :param user_vision_function: user callback function to handle vision + :param user_args: optional arguments to the user callback function + :return: + """ + + while (self.vision_running): + if (self.new_frame): + user_vision_function(user_args) + + #reset the bit for a new frame + self.new_frame = False + + # put the thread back to sleep for fps + # sleeping shorter to ensure we stay caught up on frames + time.sleep(1.0 / (3.0 * self.fps)) + + + def _buffer_vision(self, buffer_size): + """ + Internal method to save valid video captures from the camera fps times a second + + :param buffer_size: number of images to buffer (set in init) + :return: + """ + + # start with no new data + self.new_frame = False + + # when the method is first called, sometimes there is already data to catch up on + # so find the latest image in the directory and set the index to that + found_latest = False + while (not found_latest): + path = "%s/image_%03d.png" % (self.imagePath, self.image_index) + if (os.path.exists(path)) and (not os.path.isfile(path)): + # just increment through it (don't save any of these first images) + self.image_index = self.image_index + 1 + else: + found_latest = True + + # run forever, trying to grab the latest image + while (self.vision_running): + # grab the latest image from the ffmpeg stream + try: + # make the name for the next image + path = "%s/image_%03d.png" % (self.imagePath, self.image_index) + if (not os.path.exists(path)) and (not os.path.isfile(path)): + #print("File %s doesn't exist" % (path)) + #print(os.listdir(self.imagePath)) + continue + + img = cv2.imread(path,1) + + # sometimes cv2 returns a None object so skip putting those in the array + if (img is not None): + self.image_index = self.image_index + 1 + + # got a new image, save it to the buffer directly + self.buffer_index += 1 + self.buffer_index %= buffer_size + #print video_frame + self.buffer[self.buffer_index] = img + self.new_frame = True + + except cv2.error: + #Assuming its an empty image, so decrement the index and try again. + # print("Trying to read an empty png. Let's wait and try again.") + self.image_index = self.image_index - 1 + continue + + # put the thread back to sleep for faster than fps to ensure we stay on top of the frames + # coming in from ffmpeg + time.sleep(1.0 / (2.0 * self.fps)) + + + def get_latest_valid_picture(self): + """ + Return the latest valid image (from the buffer) + + :return: last valid image received from the Mambo + """ + return self.buffer[self.buffer_index] + + def close_video(self): + """ + Stop the vision processing and all its helper threads + """ + + # the helper threads look for this variable to be true + self.vision_running = False + + # kill the ffmpeg subprocess + print("Killing the ffmpeg subprocess") + self.ffmpeg_process.kill() + self.ffmpeg_process.terminate() + time.sleep(3) + + if (self.ffmpeg_process.poll() is not None): + print("Sending a second kill call to the ffmpeg process") + self.ffmpeg_process.kill() + self.ffmpeg_process.terminate() + time.sleep(3) + + + # send the command to kill the vision stream (bebop only) + if (self.is_bebop): + self.drone_object.stop_video_stream() + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/DroneVisionGUI.py b/RPI Code/pyparrot_/pyparrot/pyparrot/DroneVisionGUI.py new file mode 100644 index 0000000..266a3a4 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/DroneVisionGUI.py @@ -0,0 +1,405 @@ +""" +DroneVisionGUI is a new class that parallels DroneVision but with several important changes. + +1) This module uses VLC instead of FFMPEG +2) This module opens a GUI window to show you the video in real-time (you could +watch it in real-time previously through the VisionServer) +3) Because GUI windows are different on different OS's (and in particular OS X behaves differently +than linux and windows) and because they want to run in the main program thread, the way your program runs +is different. You first open the GUI and then you have the GUI spawn a thread to run your program. +4) This module can use a virtual disk in memory to save the images, thus shortening the time delay for the +camera for your programs. + +Author: Amy McGovern, dramymcgovern@gmail.com +Some of the LIBVLC code comes from +Author: Valentin Benke, valentin.benke@aon.at +""" +import cv2 +import time +from functools import partial +from os.path import join +import inspect +import tempfile +import sys +import pyparrot.utils.vlc as vlc +from PyQt5.QtCore import Qt, QTimer, QThread +from PyQt5.QtGui import QPalette, QColor +from PyQt5.QtWidgets import QMainWindow, QWidget, QFrame, QSlider, QHBoxLayout, QPushButton, \ + QVBoxLayout, QAction, QFileDialog, QApplication + + +class Player(QMainWindow): + """ + Modification of the simple Media Player using VLC and Qt + to show the mambo stream + + The window part of this example was modified from the QT example cited below. + VLC requires windows to create and show the video and this was a cross-platform solution. + VLC will automatically create the windows in linux but not on the mac. + Amy McGovern, dramymcgovern@gmail.com + + Qt example for VLC Python bindings + https://github.com/devos50/vlc-pyqt5-example + Copyright (C) 2009-2010 the VideoLAN team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + """ + def __init__(self, vlc_player, drone_gui): + """ + Create a UI window for the VLC player + :param vlc_player: the VLC player (created outside the function) + """ + QMainWindow.__init__(self) + self.setWindowTitle("VLC Drone Video Player") + + # save the media player + self.mediaplayer = vlc_player + + # need a reference to the main drone vision class + self.drone_vision = drone_gui + + # create the GUI + self.createUI() + + def createUI(self): + """ + Set up the window for the VLC viewer + """ + self.widget = QWidget(self) + self.setCentralWidget(self.widget) + + # In this widget, the video will be drawn + if sys.platform == "darwin": # for MacOS + from PyQt5.QtWidgets import QMacCocoaViewContainer + self.videoframe = QMacCocoaViewContainer(0) + else: + self.videoframe = QFrame() + self.palette = self.videoframe.palette() + self.palette.setColor (QPalette.Window, + QColor(0,0,0)) + self.videoframe.setPalette(self.palette) + self.videoframe.setAutoFillBackground(True) + + self.hbuttonbox = QHBoxLayout() + self.playbutton = QPushButton("Run my program") + self.hbuttonbox.addWidget(self.playbutton) + self.playbutton.clicked.connect(partial(self.drone_vision.run_user_code, self.playbutton)) + + self.landbutton = QPushButton("Land NOW") + self.hbuttonbox.addWidget(self.landbutton) + self.landbutton.clicked.connect(self.drone_vision.land) + + self.stopbutton = QPushButton("Quit") + self.hbuttonbox.addWidget(self.stopbutton) + self.stopbutton.clicked.connect(self.drone_vision.close_exit) + + self.vboxlayout = QVBoxLayout() + self.vboxlayout.addWidget(self.videoframe) + self.vboxlayout.addLayout(self.hbuttonbox) + + self.widget.setLayout(self.vboxlayout) + + # the media player has to be 'connected' to the QFrame + # (otherwise a video would be displayed in it's own window) + # this is platform specific! + # you have to give the id of the QFrame (or similar object) to + # vlc, different platforms have different functions for this + if sys.platform.startswith('linux'): # for Linux using the X Server + self.mediaplayer.set_xwindow(self.videoframe.winId()) + elif sys.platform == "win32": # for Windows + self.mediaplayer.set_hwnd(self.videoframe.winId()) + elif sys.platform == "darwin": # for MacOS + self.mediaplayer.set_nsobject(int(self.videoframe.winId())) + + +class UserVisionProcessingThread(QThread): + + def __init__(self, user_vision_function, user_args, drone_vision): + """ + :param user_vision_function: user callback function to handle vision + :param user_args: optional arguments to the user callback function + """ + QThread.__init__(self) + self.user_vision_function = user_vision_function + self.user_args = user_args + self.drone_vision = drone_vision + + def __del__(self): + self.wait() + + def run(self): + print("user callback being called") + while (self.drone_vision.vision_running): + self.user_vision_function(self.user_args) + + # put the thread back to sleep for fps + # sleeping shorter to ensure we stay caught up on frames + time.sleep(1.0 / (3.0 * self.drone_vision.fps)) + + # exit when the vision thread ends + print("exiting user vision thread") + self.exit() + +class UserCodeToRun(QThread): + def __init__(self, user_function, user_args, drone_vision): + """ + :param user_function: user code to run (presumably flies the drone) + :param user_args: optional arguments to the user function + """ + QThread.__init__(self) + self.user_vision_function = user_function + self.user_args = user_args + self.drone_vision = drone_vision + + def __del__(self): + self.wait() + + def run(self): + self.user_vision_function(self.drone_vision, self.user_args) + + +class DroneVisionGUI: + def __init__(self, drone_object, is_bebop, user_code_to_run, user_args, buffer_size=200, network_caching=200, fps=20): + """ + Setup your vision object and initialize your buffers. You won't start seeing pictures + until you call open_video. + + :param drone_object reference to the drone (mambo or bebop) object + :param is_bebop: True if it is a bebop and false if it is a mambo + :param user_code_to_run: user code to run with the run button (remember + this is needed due to the GUI taking the thread) + :param user_args: arguments to the user code + :param buffer_size: number of frames to buffer in memory. Defaults to 10. + :param network_caching: buffering time in milli-seconds, 200 should be enough, 150 works on some devices + :param fps: frame rate for the vision + """ + self.fps = fps + self.vision_interval = int(1000 * 1.0 / self.fps) + self.buffer_size = buffer_size + self.drone_object = drone_object + self.is_bebop = is_bebop + + # initialize a buffer (will contain the last buffer_size vision objects) + self.buffer = [None] * buffer_size + self.buffer_size = buffer_size + self.buffer_index = 0 + + # vision threading is done from a QTimer instead of a separate thread + self.new_frame = False + self.vision_running = True + + # the vision thread starts opencv on these files. That will happen inside the other thread + # so here we just sent the image index to 1 ( to start) + self.image_index = 1 + + # save the caching parameters and choice of libvlc + self.network_caching = network_caching + + # save the user function and args for calling from the run button + self.user_code_to_run = user_code_to_run + self.user_args = user_args + self.user_thread = UserCodeToRun(user_code_to_run, user_args, self) + + # in case we never setup a user callback function + self.user_vision_thread = None + + # has the land button been clicked - saved in case the user needs it in their code + self.land_button_clicked = False + + def run_user_code(self, button): + """ + Start the thread to run the user code + :return: + """ + button.setEnabled(False) + self.user_thread.start() + + + def set_user_callback_function(self, user_callback_function=None, user_callback_args=None): + """ + Set the (optional) user callback function for handling the new vision frames. This is + run in a separate thread that starts when you start the vision buffering + + :param user_callback_function: function + :param user_callback_args: arguments to the function + :return: + """ + self.user_vision_thread = UserVisionProcessingThread(user_callback_function, user_callback_args, self) + + + def open_video(self): + """ + Open the video stream using vlc. Note that this version is blocking meaning + this function will NEVER return. If you want to run your own code and not just + watch the video, be sure you set your user code in the constructor! + + Remember that this will only work if you have connected to the wifi for your mambo! + + :return never returns due to QT running in the main loop by requirement + """ + + # start the stream on the bebop + if (self.is_bebop): + self.drone_object.start_video_stream() + + # we have bypassed the old opencv VideoCapture method because it was unreliable for rtsp + + # get the path for the config files + fullPath = inspect.getfile(DroneVisionGUI) + shortPathIndex = fullPath.rfind("/") + if (shortPathIndex == -1): + # handle Windows paths + shortPathIndex = fullPath.rfind("\\") + print(shortPathIndex) + shortPath = fullPath[0:shortPathIndex] + self.imagePath = join(shortPath, "images") + self.utilPath = join(shortPath, "utils") + print(self.imagePath) + print(self.utilPath) + + if self.is_bebop: + # generate the streaming-address for the Bebop + self.utilPath = join(shortPath, "utils") + self.stream_adress = "%s/bebop.sdp" % self.utilPath + else: + # generate the streaming-address for the Mambo + self.stream_adress = "rtsp://192.168.99.1/media/stream2" + + # initialise the vlc-player with the network-caching + self.player = vlc.MediaPlayer(self.stream_adress, ":network-caching=" + str(self.network_caching)) + + # start the buffering + success = self._start_video_buffering() + + + def _start_video_buffering(self): + """ + If the video capture was successfully opened, then start the thread to buffer the stream + + :return: if using libvlc this will return whether or not the player started + """ + # open/draw the GUI + app = QApplication(sys.argv) + self.vlc_gui = Player(vlc_player=self.player, drone_gui=self) + self.vlc_gui.show() + self.vlc_gui.resize(640, 480) + + # ensure that closing the window closes vision + app.aboutToQuit.connect(self.land_close_exit) + + if (self.user_vision_thread is not None): + print("Starting user vision thread") + self.user_vision_thread.start() + + # setup the timer for snapshots + self.timer = QTimer(self.vlc_gui) + self.timer.setInterval(self.vision_interval) + self.timer.timeout.connect(self._buffer_vision) + self.timer.start() + + # show the stream + success = self.player.play() + print("success from play call is %s " % success) + + # start the GUI loop + app.exec() + + + def _buffer_vision(self): + """ + Internal method to save valid video captures from the camera fps times a second + + :return: + """ + + # start with no new data + self.new_frame = False + + # run forever, trying to grab the latest image + if (self.vision_running): + # generate a temporary file, gets deleted after usage automatically + #self.file = tempfile.NamedTemporaryFile(dir=self.imagePath) + self.file = join(self.imagePath, "visionStream.jpg") + #self.file = tempfile.SpooledTemporaryFile(max_size=32768) + # save the current picture from the stream + self.player.video_take_snapshot(0, self.file, 0, 0) + # read the picture into opencv + img = cv2.imread(self.file) + + # sometimes cv2 returns a None object so skip putting those in the array + if (img is not None): + # got a new image, save it to the buffer directly + self.buffer_index += 1 + self.buffer_index %= self.buffer_size + #print video_frame + self.buffer[self.buffer_index] = img + self.new_frame = True + + def get_latest_valid_picture(self): + """ + Return the latest valid image (from the buffer) + + :return: last valid image received from the Mambo + """ + return self.buffer[self.buffer_index] + + def close_exit(self): + """ + Land, close the video, and exit the GUI + :return: + """ + self.close_video() + sys.exit() + + def land_close_exit(self): + """ + Called if you Quit the GUI: lands the drone, stops vision, and exits the GUI + :return: + """ + self.land() + self.close_exit() + + def land(self): + """ + Send the land command over the emergency channel when the user pushes the button + + :return: + """ + # tell the user that the land button was clicked + self.land_button_clicked = True + + # land the drone + if (self.is_bebop): + if (not self.drone_object.is_landed()): + self.drone_object.emergency_land() + else: + if (not self.drone_object.is_landed()): + self.drone_object.safe_land(5) + + + def close_video(self): + """ + Stop the vision processing and all its helper threads + """ + + # the helper threads look for this variable to be true + self.vision_running = False + + self.player.stop() + + # send the command to kill the vision stream (bebop only) + if (self.is_bebop): + self.drone_object.stop_video_stream() + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/Minidrone.py b/RPI Code/pyparrot_/pyparrot/pyparrot/Minidrone.py new file mode 100644 index 0000000..eb8b5ec --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/Minidrone.py @@ -0,0 +1,804 @@ +""" +Mambo class holds all of the methods needed to pilot the drone from python and to ask for sensor +data back from the drone + +Author: Amy McGovern, dramymcgovern@gmail.com +Author: Alexander Zach, https://github.com/alex-zach, groundcam support +Author: Valentin Benke, https://github.com/Vabe7, groundcam support +""" +import time +from pyparrot.networking.wifiConnection import WifiConnection +try: + from pyparrot.networking.bleConnection import BLEConnection + BLEAvailable = True +except: + BLEAvailable = False +from pyparrot.utils.colorPrint import color_print +from pyparrot.commandsandsensors.DroneCommandParser import DroneCommandParser +from pyparrot.commandsandsensors.DroneSensorParser import DroneSensorParser +import math +from os.path import join +import inspect + +#Groundcam Imports +from ftplib import FTP +import tempfile +try: + import cv2 + OpenCVAvailable = True + print("OpenCVAvailable is %s" % OpenCVAvailable) +except: + OpenCVAvailable = False + print("OpenCVAvailable is %s" % OpenCVAvailable) + +class MinidroneSensors: + """ + Store the minidrone's last known sensor values + """ + + def __init__(self): + + # default to full battery + self.battery = 100 + + # drone on the ground + self.flying_state = "landed" + + # dictionary for extra sensors + self.sensors_dict = dict() + + self.gun_id = 0 + self.gun_state = None + + self.claw_id = 0 + self.claw_state = None + + self.flying_mode = "quadricopter" + self.plane_gear_box = "gear_1" + + # new SDK sends speed, altitude, and quaternions + self.speed_x = 0 + self.speed_y = 0 + self.speed_z = 0 + self.speed_ts = 0 + + # these are only available on wifi + self.altitude = -1 + self.altitude_ts = 0 + + self.quaternion_w = 0 + self.quaternion_x = 0 + self.quaternion_y = 0 + self.quaternion_z = 0 + self.quaternion_ts = -1 + + # this is optionally set elsewhere + self.user_callback_function = None + + def set_user_callback_function(self, function, args): + """ + Sets the user callback function (called everytime the sensors are updated) + + :param function: name of the user callback function + :param args: arguments (tuple) to the function + :return: + """ + self.user_callback_function = function + self.user_callback_function_args = args + + def update(self, name, value, sensor_enum): + """ + Update the sensor + + :param name: name of the sensor to update + :param value: new value for the sensor + :param sensor_enum: enum list for the sensors that use enums so that we can translate from numbers to strings + :return: + """ + #print("updating sensor %s" % name) + #print(value) + if (name is None): + print("Error empty sensor") + return + + + if (name, "enum") in sensor_enum: + # grab the string value + if (value > len(sensor_enum[(name, "enum")])): + value = "UNKNOWN_ENUM_VALUE" + else: + enum_value = sensor_enum[(name, "enum")][value] + value = enum_value + + + # add it to the sensors + if (name == "BatteryStateChanged_battery_percent"): + self.battery = value + elif (name == "FlyingStateChanged_state"): + self.flying_state = value + elif (name == "ClawState_id"): + self.claw_id = value + elif (name == "ClawState_state"): + self.claw_state = value + elif (name == "GunState_id"): + self.gun_id = value + elif (name == "GunState_state"): + self.gun_state = value + elif (name == "DroneSpeed_speed_x"): + self.speed_x = value + elif (name == "DroneSpeed_speed_y"): + self.speed_y = value + elif (name == "DroneSpeed_speed_z"): + self.speed_z = value + elif (name == "DroneSpeed_ts"): + self.speed_ts = value + elif (name == "DroneAltitude_altitude"): + self.altitude = value + elif (name == "DroneAltitude_ts"): + self.altitude_ts = value + elif (name == "DroneQuaternion_q_w"): + self.quaternion_w = value + elif (name == "DroneQuaternion_q_x"): + self.quaternion_x = value + elif (name == "DroneQuaternion_q_y"): + self.quaternion_y = value + elif (name == "DroneQuaternion_q_z"): + self.quaternion_z = value + elif (name == "DroneQuaternion_ts"): + self.quaternion_ts = value + elif (name == "FlyingModeChanged_mode"): + self.flying_mode = value + elif (name == "PlaneGearBoxChanged_state"): + self.plane_gear_box = value + else: + #print "new sensor - add me to the struct but saving in the dict for now" + self.sensors_dict[name] = value + + # call the user callback if it isn't None + if (self.user_callback_function is not None): + self.user_callback_function(self.user_callback_function_args) + + def get_estimated_z_orientation(self): + """ + Uses the quaternions to return an estimated orientation + + Learn more about unit quaternions here: + + https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation + + NOTE: This is not a real compass heading. 0 degrees is where you are facing when + the mambo turns on! + + :return: + """ + + (X, Y, Z) = self.quaternion_to_euler_angle(self.quaternion_w, self.quaternion_x, + self.quaternion_y, self.quaternion_z) + return Z + + def quaternion_to_euler_angle(self, w, x, y, z): + """ + This code is directly from: + + https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles + + :param x: + :param y: + :param z: + :return: + """ + ysqr = y * y + + t0 = +2.0 * (w * x + y * z) + t1 = +1.0 - 2.0 * (x * x + ysqr) + X = math.degrees(math.atan2(t0, t1)) + + t2 = +2.0 * (w * y - z * x) + t2 = +1.0 if t2 > +1.0 else t2 + t2 = -1.0 if t2 < -1.0 else t2 + Y = math.degrees(math.asin(t2)) + + t3 = +2.0 * (w * z + x * y) + t4 = +1.0 - 2.0 * (ysqr + z * z) + Z = math.degrees(math.atan2(t3, t4)) + + return X, Y, Z + + def __str__(self): + """ + Make a nicely printed struct for debugging + + :return: string for print calls + """ + my_str = "mambo state: battery %d, " % self.battery + my_str += "flying state is %s, " % self.flying_state + my_str += "speed (x, y, z) and ts is (%f, %f, %f) at %f " % (self.speed_x, self.speed_y, self.speed_z, self.speed_ts) + if (self.altitude_ts > -1): + my_str += "altitude (m) %f and ts is %f " % (self.altitude, self.altitude_ts) + + if (self.quaternion_ts > -1): + my_str += "quaternion (w, x, y, z) and ts is (%f, %f, %f, %f) at %f " % ( + self.quaternion_w, self.quaternion_x, self.quaternion_y, self.quaternion_z, self.quaternion_ts) + my_str += "gun id: %d, state %s, " % (self.gun_id, self.gun_state) + my_str += "claw id: %d, state %s, " % (self.claw_id, self.claw_state) + my_str += "extra sensors: %s," % self.sensors_dict + return my_str + + + +class MamboGroundcam: + def __init__(self): + """ + Initialises the FTP-Session for the picture-download. + Only works with WiFi. + """ + self.MEDIA_PATH = '/internal_000/mambo/media' # Filepath on the Mambo + # groundcam remains broken on 3.0.26 and now it times out + #try: + # self.ftp = FTP('192.168.99.3') # IP-Address of the drone itself + # login = self.ftp.login() + # print("FTP login success is %s" % login) + #except: + print("ERROR: ftp login is disabled by parrot firmware 3.0.25 and 26. Groundcam will not work.") + self.ftp = None + + # get the path for the config files + fullPath = inspect.getfile(Mambo) + shortPathIndex = fullPath.rfind("/") + if (shortPathIndex == -1): + # handle Windows paths + shortPathIndex = fullPath.rfind("\\") + print(shortPathIndex) + shortPath = fullPath[0:shortPathIndex] + self.imagePath = join(shortPath, "images") + self.storageFile = join(self.imagePath, "groundcam.jpg") + print(self.storageFile) + #self.storageFile = tempfile.NamedTemporaryFile() + + def _close(self): + if (self.ftp is not None): + self.ftp.close() + + def get_groundcam_pictures_names(self): + """ + Retruns a list with the names of the pictures stored on the Mambo. + :return The list as an array, if there isn't any file, the array is empty. + """ + if (self.ftp is None): + return list() + else: + self.ftp.cwd(self.MEDIA_PATH) + list = self.ftp.nlst() + list = sorted(list) + return list + + def get_groundcam_picture(self, filename, cv2_flag): + """ + Downloads the specified picture from the Mambo and stores it into a tempfile. + + :param filename: the name of the file which should be downloaded ON THE MAMBO. + :param cv2_flag: if true this function will return a cv2 image object, if false the name of the temporary file will be returned + :return False if there was an error during download, if cv2 is True a cv2 frame or it just returns the file name of the temporary file + """ + # handle the broken firmware upgrade + if (self.ftp is None): + return False + + # otherwise return the photos + self.ftp.cwd(self.MEDIA_PATH) + try: + self.ftp.retrbinary('RETR ' + filename, open(self.storageFile, "wb").write) #download + if cv2_flag and OpenCVAvailable: + img = cv2.imread(self.storageFile) + return img + else: + return filename + except Exception as e: + print(e) + return False + + def _delete_file(self, filename): + ''' + Deletes a file on the drone + :param filename: Filename of the file you wnat to delete + ''' + if (self.ftp is not None): + self.ftp.delete(filename) + + +class Minidrone: + def __init__(self, address="", use_wifi=False): + """ + If you need BLE: Initialize with its BLE address - if you don't know the address, call findMambo + and that will discover it for you. + You can also connect to the wifi on the FPV camera. Do not use this if the camera is not connected. Also, + ensure you have connected your machine to the wifi on the camera before attempting this or it will not work. + :param address: unique address for this mambo (can be ignored if you are using wifi) + :param use_wifi: set to True to connect with wifi instead of BLE + """ + self.address = address + self.use_wifi = use_wifi + self.groundcam = None + if (use_wifi): + self.drone_connection = WifiConnection(self, drone_type="Mambo") + # initialize groundcam + self.groundcam = MamboGroundcam() + else: + if (BLEAvailable): + self.drone_connection = BLEConnection(address, self) + else: + self.drone_connection = None + color_print("ERROR: you are trying to use a BLE connection on a system that doesn't have BLE installed.", "ERROR") + return + + # intialize the command parser + self.command_parser = DroneCommandParser() + + # initialize the sensors and the parser + self.sensors = MinidroneSensors() + self.sensor_parser = DroneSensorParser(drone_type="Minidrone") + + + def set_user_sensor_callback(self, function, args): + """ + Set the (optional) user callback function for sensors. Every time a sensor + is updated, it calls this function. + + :param function: name of the function + :param args: tuple of arguments to the function + :return: nothing + """ + self.sensors.set_user_callback_function(function, args) + + def update_sensors(self, data_type, buffer_id, sequence_number, raw_data, ack): + """ + Update the sensors (called via the wifi or ble connection) + + :param data: raw data packet that needs to be parsed + :param ack: True if this packet needs to be ack'd and False otherwise + """ + + sensor_list = self.sensor_parser.extract_sensor_values(raw_data) + if (sensor_list is not None): + for sensor in sensor_list: + (sensor_name, sensor_value, sensor_enum, header_tuple) = sensor + if (sensor_name is not None): + self.sensors.update(sensor_name, sensor_value, sensor_enum) + # print(self.sensors) + else: + color_print( + "data type %d buffer id %d sequence number %d" % (data_type, buffer_id, sequence_number), + "WARN") + color_print("This sensor is missing (likely because we don't need it)", "WARN") + + if (ack): + self.drone_connection.ack_packet(buffer_id, sequence_number) + + def connect(self, num_retries): + """ + Connects to the drone and re-tries in case of failure the specified number of times. Seamlessly + connects to either wifi or BLE depending on how you initialized it + + :param: num_retries is the number of times to retry + + :return: True if it succeeds and False otherwise + """ + + # special case for when the user tries to do BLE when it isn't available + if (self.drone_connection is None): + return False + + connected = self.drone_connection.connect(num_retries) + return connected + + + def takeoff(self): + """ + Sends the takeoff command to the mambo. Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("minidrone", "Piloting", "TakeOff") + return self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + + def safe_takeoff(self, timeout): + """ + Sends commands to takeoff until the mambo reports it is taking off + + :param timeout: quit trying to takeoff if it takes more than timeout seconds + """ + + start_time = time.time() + # take off until it really listens + while (self.sensors.flying_state != "takingoff" and (time.time() - start_time < timeout)): + if (self.sensors.flying_state == "emergency"): + return + success = self.takeoff() + self.smart_sleep(1) + + # now wait until it finishes takeoff before returning + while ((self.sensors.flying_state not in ("flying", "hovering") and + (time.time() - start_time < timeout))): + if (self.sensors.flying_state == "emergency"): + return + self.smart_sleep(1) + + def land(self): + """ + Sends the land command to the mambo. Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("minidrone", "Piloting", "Landing") + return self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + def is_landed(self): + """ + Returns true if it is landed or emergency and False otherwise + :return: + """ + if (self.sensors.flying_state in ("landed", "emergency")): + return True + else: + return False + + def safe_land(self, timeout): + """ + Ensure the mambo lands by sending the command until it shows landed on sensors + """ + start_time = time.time() + + while (self.sensors.flying_state not in ("landing", "landed") and (time.time() - start_time < timeout)): + if (self.sensors.flying_state == "emergency"): + return + color_print("trying to land", "INFO") + success = self.land() + self.smart_sleep(1) + + while (self.sensors.flying_state != "landed" and (time.time() - start_time < timeout)): + if (self.sensors.flying_state == "emergency"): + return + self.smart_sleep(1) + + + def smart_sleep(self, timeout): + """ + Don't call time.sleep directly as it will mess up BLE and miss WIFI packets! Use this + which handles packets received while sleeping + + :param timeout: number of seconds to sleep + """ + self.drone_connection.smart_sleep(timeout) + + def hover(self): + """ + Sends the command execute a flat trim to the mambo. This is basically a hover command. + Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("minidrone", "Piloting", "FlatTrim") + # print command_tuple + return self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + def flip(self, direction): + """ + Sends the flip command to the mambo. Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + Valid directions to flip are: front, back, right, left + + :return: True if the command was sent and False otherwise + """ + fixed_direction = direction.lower() + if (fixed_direction not in ("front", "back", "right", "left")): + print("Error: %s is not a valid direction. Must be one of %s" % direction, "front, back, right, or left") + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("minidrone", + "Animations", "Flip", fixed_direction) + # print command_tuple + # print enum_tuple + + return self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + def turn_degrees(self, degrees): + """ + Turn the mambo the specified number of degrees (-180, 180). Degrees must be an integere + so it is cast to an integer here. If you send it a float, it will be rounded according to + the rules of int() + + This is called cap in the xml but it means degrees per + http://forum.developer.parrot.com/t/what-does-cap-stand-for/6213/2 + + :param degrees: degrees to turn (-180 to 180) + :return: True if the command was sent and False otherwise + """ + degrees = int(degrees) + if (degrees > 180): + degrees = 180 + print("Degrees too large: setting to 180") + elif (degrees < -180): + degrees = -180 + print("Degrees too large and negative: setting to -180") + + command_tuple = self.command_parser.get_command_tuple("minidrone", "Animations", "Cap") + return self.drone_connection.send_turn_command(command_tuple, degrees) + + def turn_on_auto_takeoff(self): + """ + Turn on the auto take off (throw mode) + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("minidrone", "Piloting", "AutoTakeOffMode") + + return self.drone_connection.send_param_command_packet(command_tuple, param_tuple=[1], param_type_tuple=["u8"]) + + + def take_picture(self): + """ + Ask the drone to take a picture also checks how many frames are on there, if there are ore than 35 it deletes one + If connected via Wifi it + If it is connected via WiFi it also deletes all frames on the Mambo once there are more than 35, + since after there are 40 the next ones are ignored + :return: True if the command was sent and False otherwise + """ + if self.use_wifi: + list = self.groundcam.get_groundcam_pictures_names() + if len(list) > 35: #if more than 35 pictures on the Mambo delete all + print("deleting") + for file in list: + self.groundcam._delete_file(file) + + command_tuple = self.command_parser.get_command_tuple("minidrone", "MediaRecord", "PictureV2") + return self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + def ask_for_state_update(self): + """ + Ask for a full state update (likely this should never be used but it can be called if you want to see + everything the mambo is storing) + + :return: nothing but it will eventually fill the MamboSensors with all of the state variables as they arrive + """ + command_tuple = self.command_parser.get_command_tuple("common", "Common", "AllStates") + return self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + def _ensure_fly_command_in_range(self, value): + """ + Ensure the fly direct commands are in range and also ensures the values are integers (just rounds them) + + :param value: the value sent by the user + :return: a value in the range -100 to 100 + """ + if (value < -100): + return -100 + elif (value > 100): + return 100 + else: + return int(value) + + def fly_direct(self, roll, pitch, yaw, vertical_movement, duration=None): + """ + Direct fly commands using PCMD. Each argument ranges from -100 to 100. Numbers outside that are clipped + to that range. + + Note that the xml refers to gaz, which is apparently french for vertical movements: + http://forum.developer.parrot.com/t/terminology-of-gaz/3146 + + duration is optional: if you want it to fly for a specified period of time, set this to the number of + seconds (fractions are fine) or use None to send the command once. Note, if you do this, you will need + an outside loop that sends lots of commands or your drone will not fly very far. The command is not repeated + inside the drone. it executes once and goes back to hovering without new commands coming in. But the option + of zero duration allows for smoother flying if you want to do the control loop yourself. + + + :param roll: roll speed in -100 to 100 + :param pitch: pitch speed in -100 to 100 + :param yaw: yaw speed in -100 to 100 + :param vertical_movement: vertical speed in -100 to 100 + :param duration: optional: seconds for a specified duration or None to send it once (see note above) + :return: + """ + + my_roll = self._ensure_fly_command_in_range(roll) + my_pitch = self._ensure_fly_command_in_range(pitch) + my_yaw = self._ensure_fly_command_in_range(yaw) + my_vertical = self._ensure_fly_command_in_range(vertical_movement) + + #print("roll is %d pitch is %d yaw is %d vertical is %d" % (my_roll, my_pitch, my_yaw, my_vertical)) + command_tuple = self.command_parser.get_command_tuple("minidrone", "Piloting", "PCMD") + + if (duration is None): + self.drone_connection.send_single_pcmd_command(command_tuple, my_roll, my_pitch, my_yaw, my_vertical) + else: + self.drone_connection.send_pcmd_command(command_tuple, my_roll, my_pitch, my_yaw, my_vertical, duration) + + + + def open_claw(self): + """ + Open the claw - note not supposed under wifi since the camera takes the place of the claw + + :return: True if the command was sent and False otherwise (can include errors or asking to do this using wifi) + """ + # not supposed under wifi since the camera takes the place of the claw + if (self.use_wifi): + return False + + # print "open claw" + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("minidrone", "UsbAccessory", "ClawControl", "OPEN") + # print command_tuple + # print enum_tuple + + return self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple, self.sensors.claw_id) + + def close_claw(self): + """ + Close the claw - note not supposed under wifi since the camera takes the place of the claw + + :return: True if the command was sent and False otherwise (can include errors or asking to do this using wifi) + """ + + # not supposed under wifi since the camera takes the place of the claw + if (self.use_wifi): + return False + + # print "close claw" + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("minidrone", "UsbAccessory", "ClawControl", "CLOSE") + # print command_tuple + # print enum_tuple + + return self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple, self.sensors.claw_id) + + + def set_max_vertical_speed(self, value): + """ + Sets the maximum vertical speed in m/s. Unknown what the true maximum is but + we do ensure you only set positive values. + + :param value: maximum speed + :return: True if the command was sent and False otherwise + """ + + if (value < 0): + print("Can't set a negative max vertical speed. Setting to 1 m/s instead.") + value = 1 + + command_tuple = self.command_parser.get_command_tuple("minidrone", "SpeedSettings", "MaxVerticalSpeed") + param_tuple = [value] + param_type_tuple = ['float'] + return self.drone_connection.send_param_command_packet(command_tuple,param_tuple,param_type_tuple) + + def set_max_tilt(self, value): + """ + Sets the maximum tilt in degrees. Ensures you only set positive values. + + :param value: maximum tilt in degrees + :return: True if the command was sent and False otherwise + """ + + if (value < 0): + print("Can't set a negative max horizontal speed. Setting to 1 m/s instead.") + value = 1 + + command_tuple = self.command_parser.get_command_tuple("minidrone", "PilotingSettings", "MaxTilt") + param_tuple = [value] + param_type_tuple = ['float'] + return self.drone_connection.send_param_command_packet(command_tuple,param_tuple,param_type_tuple) + + def emergency(self): + """ + Sends the emergency command to the mambo. Gets the codes for it from the xml files. Ensures the + packet was received or sends it again up to a maximum number of times. + + :return: True if the command was sent and False otherwise + """ + command_tuple = self.command_parser.get_command_tuple("minidrone", "Piloting", "Emergency") + self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + + def safe_emergency(self, timeout): + """ + Sends emergency stop command until the Mambo reports it is not flying anymore + + :param timeout: quit trying to emergency stop if it takes more than timeout seconds + """ + + start_time = time.time() + # send emergency until it really listens + while ((self.sensors.flying_state in ("flying", "hovering")) and (time.time() - start_time < timeout)): + success = self.emergency() + self.smart_sleep(1) + + # now wait until it touches ground before returning + while ((self.sensors.flying_state != "landed") and (time.time() - start_time < timeout)): + self.smart_sleep(1) + + def flat_trim(self): + """ + Sends the flat_trim command to the mambo. Gets the codes for it from the xml files. + """ + command_tuple = self.command_parser.get_command_tuple("minidrone", "Piloting", "FlatTrim") + self.drone_connection.send_noparam_command_packet_ack(command_tuple) + + +class Mambo(Minidrone): + + def disconnect(self): + """ + Disconnect the BLE connection. Always call this at the end of your programs to + cleanly disconnect. + + :return: void + """ + self.drone_connection.disconnect() + if self.groundcam is not None: + self.groundcam._close() + + + def fire_gun(self): + """ + Fire the gun (assumes it is attached) - note not supposed under wifi since the camera takes the place of the gun + + :return: True if the command was sent and False otherwise (can include errors or asking to do this using wifi) + """ + + # not supposed under wifi since the camera takes the place of the gun + if (self.use_wifi): + return False + + # print "firing gun" + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("minidrone", "UsbAccessory", "GunControl", "FIRE") + # print command_tuple + # print enum_tuple + + return self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple, self.sensors.gun_id) + +class Swing(Minidrone): + + def disconnect(self): + """ + Disconnect the BLE connection. Always call this at the end of your programs to + cleanly disconnect. + + :return: void + """ + self.drone_connection.disconnect() + + + def set_flying_mode(self, mode): + """ + Set drone flying mode + + :param state: + :return: + """ + if (mode not in ('quadricopter', 'plane_forward', 'plane_backward')): + print("Error: %s is not a valid value. The value must be: quadricopter, plane_forward, plane_backward" % mode) + print("Ignoring command and returning") + return + + self.set_plane_gear_box(self.sensors.plane_gear_box) + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("minidrone", "Piloting", "FlyingMode", mode) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) + + + def set_plane_gear_box(self, state): + """ + Set plane gear box + + :param state: + :return: + """ + if (state not in ('gear_1', 'gear_2', 'gear_3')): + print("Error: %s is not a valid value. The value must be: gear_1, gear_2, gear_3" % state) + print("Ignoring command and returning") + return + + (command_tuple, enum_tuple) = self.command_parser.get_command_tuple_with_enum("minidrone", "Piloting", "PlaneGearBox", state) + self.drone_connection.send_enum_command_packet_ack(command_tuple, enum_tuple) diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/VisionServer.py b/RPI Code/pyparrot_/pyparrot/pyparrot/VisionServer.py new file mode 100644 index 0000000..9b4ea14 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/VisionServer.py @@ -0,0 +1,143 @@ +""" + This is a simple web server to let the user see the vision that is being processed by ffmpeg. It + essentially replaces the role of VLC. Note that there are several user parameters that should be + set to run this program. + + This does not replace the vision process! This is a separate process just to run a web server that + lets you see what the mambo is seeing. + + orig author: Igor Maculan - n3wtron@gmail.com + A Simple mjpg stream http server + + Updated for python3 including png streaming and + graceful error handling - Taner Davis +""" +import cv2 +import os.path +import time +from http.server import BaseHTTPRequestHandler, HTTPServer + +""" + Location on your computer where you want to start reading files from to start + streaming. You'll need keep the %03d in the file name to ensure that the stream + can keep finding new photos made from the stream. This allows the program to fill + in image numbers like 004 or 149. + + Note: this will not loop at 999, the program will look for 1000 after 999. +""" +#IMAGE_PATH = "/Users/Leo/Desktop/pyparrot-1.2.1/Test file/images/image_%03d.png" +IMAGE_PATH = "/Users/amy/repos/pyparrot/images/image_%03d.png" + +""" + The URL or website name you would like to stream to. Unless you have a strong reason + to change this, keep this as "127.0.0.1" +""" +HOST_NAME = "127.0.0.1" + +""" + The port youd like to stream to locally. Unless you have a strong reason to change + this, keep this as 9090. +""" +PORT_NUMBER = 9090 + +""" + Set this according to how we want to stream: + Stream in color -> >0 (1, 2, 873, etc.) + Stream in black and white -> 0 + Stream in color with transparency -> <0 (-1, -6, -747, etc.) +""" +STREAMING_IMAGE_TYPE = 1 + + +class CamHandler(BaseHTTPRequestHandler): + def do_GET(self): + """ + When we go to the URL to see the stream, we need to decide if we need to build + the screen or if we need to start loading images to view. + + "GET / HTTP/1.1" - The request when we want to build the webpage + Note: self.path here is the first, lone "/" + + "GET /cam.mjpg HTTP/1.1" - The request to start viewing the images for the stream + Note: self.path here is "/cam.mjpg" + """ + # If we haven't built the page yet, let's do that. Happens we first load the page. + if self.path.endswith('.html') or self.path == "/": + # Send HTTP Success (200) to let the browser know it's okay to continue and build the page. + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + # Create html for image source using the host name and port provided in the program code + self.wfile.write(''.encode()) + self.wfile.write(('' % (HOST_NAME, PORT_NUMBER)).encode()) + self.wfile.write(''.encode()) + # Page built. Let's bail. + return + if self.path.endswith('.mjpg'): + # Send HTTP Success (200) to let browser know to take page headers and start showing images + self.send_response(200) + self.send_header('Content-type', 'multipart/x-mixed-replace; boundary=--pngboundary') + self.end_headers() + # The name of the first picture will be 1, but the leading zeros are handled in the IMAGE_PATH variable + index = 1 + while True: + try: + # Create path name with the index + path = IMAGE_PATH % (index) + # If our file doesn't exist, don't try to read it just yet. + if (not os.path.exists(path)) and (not os.path.isfile(path)): + print("File %s doesn't exist. Trying again." % (path)) + # Go back to the try above + continue + # File exists. Let's try to read the image + img = cv2.imread(path, STREAMING_IMAGE_TYPE) + #print("Reading image with index of %d" % (index)) + # Increase index by one to read the next image file next time + index = index + 1 + # Encode the read png image as a buffer of bytes and write the image to our page + r, buf = cv2.imencode(".png", img) + self.wfile.write("--pngboundary\r\n".encode()) + self.send_header('Content-type', 'image/png') + self.send_header('Content-length', str(len(buf))) + self.end_headers() + self.wfile.write(bytearray(buf)) + self.wfile.write('\r\n'.encode()) + except cv2.error: + """ + This can happen when the png is created from the stream, but it hasn't been completely + filled in. This will throw an error through opencv about trying to read an empty image. + Catch it here and return to the try statement above to keep the program from exiting. + """ + print("Trying to read an empty png. Let's wait and try again.") + continue + except KeyboardInterrupt: + """ + This happens when we input a kill command of some sort like command+c. Let the user know + we have successfully receive the exit command, and stop reading images to the web page. + """ + print("Leaving the stream") + break + return + + +def main(): + """ + Builds an http page to see images in a stream as they are created in the IMAGE_PATH specified above. + """ + try: + # Build a server that will allow us to access it/send requests through a browser + server = HTTPServer(('', PORT_NUMBER), CamHandler) + print("Server started at \"http://%s:%d\"" % (HOST_NAME, PORT_NUMBER)) + # Keep the server up until we close it below through a Keyboad Interruption + server.serve_forever() + # Command+c/Command+z will stop the server once the webpage above is also stopped. + except KeyboardInterrupt: + server.socket.close() + print("Terminated Vision program successfully") + + +""" + Convenience naming methodology to let use call the entire script through command line. +""" +if __name__ == '__main__': + main() diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/__init__.py b/RPI Code/pyparrot_/pyparrot/pyparrot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/DroneCommandParser.py b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/DroneCommandParser.py new file mode 100644 index 0000000..b704d24 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/DroneCommandParser.py @@ -0,0 +1,109 @@ +import untangle +import os +from os.path import join + +class DroneCommandParser: + def __init__(self): + # store the commandsandsensors as they are called so you don't have to parse each time + self.command_tuple_cache = dict() + + # parse the command files from XML (so we don't have to store ids and can use names + # for readability and portability!) + + # grab module path per http://www.karoltomala.com/blog/?p=622 + path = os.path.abspath(__file__) + dir_path = os.path.dirname(path) + + self.common_commands = untangle.parse(join(dir_path, 'common.xml')) + self.minidrone_commands = untangle.parse(join(dir_path, 'minidrone.xml')) + self.ardrone3_commands = untangle.parse(join(dir_path, 'ardrone3.xml')) + + + def get_command_tuple(self, project, myclass, cmd): + """ + Parses the command XML for the specified class name and command name + + :param myclass: class name (renamed to myclass to avoid reserved name) in the xml file + :param cmd: command to execute (from XML file) + :return: + """ + # only search if it isn't already in the cache + if (myclass, cmd) in self.command_tuple_cache: + return self.command_tuple_cache[(myclass, cmd)] + + # pick the right command file to draw from + if (project == "ardrone3"): + my_file = self.ardrone3_commands + elif (project == "minidrone"): + my_file = self.minidrone_commands + else: + my_file = self.common_commands + + # run the search first in minidrone xml and then hit common if that failed + project_id = int(my_file.project['id']) + + for child in my_file.project.myclass: + if child['name'] == myclass: + class_id = int(child['id']) + #print child['name'] + + for subchild in child.cmd: + #print subchild + if subchild['name'] == cmd: + #print subchild['name'] + cmd_id = int(subchild['id']) + + # cache the result + self.command_tuple_cache[(myclass, cmd)] = (project_id, class_id, cmd_id) + return (project_id, class_id, cmd_id) + + + + def get_command_tuple_with_enum(self, project, myclass, cmd, enum_name): + """ + Parses the command XML for the specified class name and command name and checks for enum_name + + :param myclass: class name (renamed to myclass to avoid reserved name) in the xml file + :param cmd: command to execute (from XML file) + :return: + """ + # only search if it isn't already in the cache + if (myclass, cmd, enum_name) in self.command_tuple_cache: + #print("using the cache") + #print(self.command_tuple_cache[(myclass, cmd, enum_name)]) + return self.command_tuple_cache[(myclass, cmd, enum_name)] + + # pick the right command file to draw from + if (project == "ardrone3"): + my_file = self.ardrone3_commands + elif (project == "minidrone"): + my_file = self.minidrone_commands + else: + my_file = self.common_commands + + # run the search first in minidrone xml and then hit common if that failed + project_id = int(my_file.project['id']) + + for child in my_file.project.myclass: + if child['name'] == myclass: + class_id = int(child['id']) + #print child['name'] + + for subchild in child.cmd: + #print subchild + if subchild['name'] == cmd: + #print subchild['name'] + cmd_id = int(subchild['id']) + + for arg_child in subchild.arg: + if arg_child['type'] == "enum": + for e_idx, echild in enumerate(arg_child.enum): + if echild['name'] == enum_name: + enum_id = e_idx + + # cache the result + self.command_tuple_cache[(myclass, cmd, enum_name)] = ((project_id, class_id, cmd_id), enum_id) + + #print ((project_id, class_id, cmd_id), enum_id) + return ((project_id, class_id, cmd_id), enum_id) + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/DroneSensorParser.py b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/DroneSensorParser.py new file mode 100644 index 0000000..3579c60 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/DroneSensorParser.py @@ -0,0 +1,204 @@ +""" +Sensor parser class: handles the XML parsing and gets the values but the actual data is stored with the drone itself +since it knows what to do with it. +""" +import struct +import untangle +from pyparrot.utils.colorPrint import color_print +import os +from os.path import join + +def get_data_format_and_size(data, data_type): + """ + Internal function to convert data_type to the corresponding struct.pack format string + as per https://docs.python.org/2/library/struct.html#format-characters + + Function contributed by awm102 on GitHub. Amy moved this to DroneSensorParser to be + more general, edited a bit to fit within the drone sensor parser as well. + + :param data: the data that will be packed. Not actually used here unless the data_type is string, then + it is used to calculate the data size. + :param data_type: a string representing the data type + :return: a tuple of a string representing the struct.pack format for the data type and an int representing + the number of bytes + """ + + if data_type == "u8" or data_type == "enum": + format_char = " + + + All ARDrone3-only commands + + All commands related to piloting the drone + + + + + + + + + + Boolean flag: 1 if the roll and pitch values should be taken in consideration. 0 otherwise + + + Roll angle as signed percentage. + On copters: + Roll angle expressed as signed percentage of the max pitch/roll setting, in range [-100, 100] + -100 corresponds to a roll angle of max pitch/roll to the left (drone will fly left) + 100 corresponds to a roll angle of max pitch/roll to the right (drone will fly right) + This value may be clamped if necessary, in order to respect the maximum supported physical tilt of the copter. + + On fixed wings: + Roll angle expressed as signed percentage of the physical max roll of the wing, in range [-100, 100] + Negative value makes the plane fly to the left + Positive value makes the plane fly to the right + + + Pitch angle as signed percentage. + On copters: + Expressed as signed percentage of the max pitch/roll setting, in range [-100, 100] + -100 corresponds to a pitch angle of max pitch/roll towards sky (drone will fly backward) + 100 corresponds to a pitch angle of max pitch/roll towards ground (drone will fly forward) + This value may be clamped if necessary, in order to respect the maximum supported physical tilt of the copter. + + On fixed wings: + Expressed as signed percentage of the physical max pitch of the wing, in range [-100, 100] + Negative value makes the plane fly in direction of the sky + Positive value makes the plane fly in direction of the ground + + + Yaw rotation speed as signed percentage. + On copters: + Expressed as signed percentage of the max yaw rotation speed setting, in range [-100, 100]. + -100 corresponds to a counter-clockwise rotation of max yaw rotation speed + 100 corresponds to a clockwise rotation of max yaw rotation speed + This value may be clamped if necessary, in order to respect the maximum supported physical tilt of the copter. + + On fixed wings: + Giving more than a fixed value (75% for the moment) triggers a circle. + Positive value will trigger a clockwise circling + Negative value will trigger a counter-clockwise circling + + + Throttle as signed percentage. + On copters: + Expressed as signed percentage of the max vertical speed setting, in range [-100, 100] + -100 corresponds to a max vertical speed towards ground + 100 corresponds to a max vertical speed towards sky + This value may be clamped if necessary, in order to respect the maximum supported physical tilt of the copter. + During the landing phase, putting some positive gaz will cancel the land. + + On fixed wings: + Expressed as signed percentage of the physical max throttle, in range [-100, 100] + Negative value makes the plane fly slower + Positive value makes the plane fly faster + + + Command timestamp in milliseconds (low 24 bits) + command sequence number (high 8 bits) [0;255]. + + + + + + + + + + + + 1 to start the navigate home, 0 to stop it + + + + + + State of automatic take off mode (1 for autotake off enabled) + + + + + + Wanted displacement along the front axis [m] + + + Wanted displacement along the right axis [m] + + + Wanted displacement along the down axis [m] + + + Wanted rotation of heading [rad] + + + + + + State of user take off mode + - 1 to enter in user take off. + - 0 to exit from user take off. + + + + + + The circling direction + + Circling ClockWise + + + Circling Counter ClockWise + + + Use drone default Circling direction set by CirclingDirection cmd + + + + + + + Latitude of the location (in degrees) to reach + + + Longitude of the location (in degrees) to reach + + + Altitude above sea level (in m) to reach + + + Orientation mode of the move to + + The drone won't change its orientation + + + The drone will make a rotation to look in direction of the given location + + + The drone will orientate itself to the given heading before moving to the location + + + The drone will orientate itself to the given heading while moving to the location + + + + Heading (relative to the North in degrees). + This value is only used if the orientation mode is HEADING_START or HEADING_DURING + + + + + + + + + Latitude of the location (in degrees) to look at + + + Longitude of the location (in degrees) to look at + + + Altitude above sea level (in m) to look at + + + + + + + + Animation commands + + + + Direction for the flip + + Flip direction front + + + Flip direction back + + + Flip direction right + + + Flip direction left + + + + + + Ask the drone to move camera + + + + Tilt camera consign for the drone (in degree) + The value is saturated by the drone. + Saturation value is sent by thre drone through CameraSettingsChanged command. + + + Pan camera consign for the drone (in degree) + The value is saturated by the drone. + Saturation value is sent by thre drone through CameraSettingsChanged command. + + + + + + Tilt camera consign for the drone (in degree) + The value is saturated by the drone. + Saturation value is sent by thre drone through CameraSettingsChanged command. + + + Pan camera consign for the drone (in degree) + The value is saturated by the drone. + Saturation value is sent by thre drone through CameraSettingsChanged command. + + + + + + Tilt camera velocity consign [deg/s] + Negative tilt velocity move camera to bottom + Positive tilt velocity move camera to top + + + Pan camera velocity consign [deg/s] + Negative pan velocity move camera to left + Positive pan velocity move camera to right + + + + + Media recording management + + + + Mass storage id to take picture + + + + + + Command to record video + + Stop the video recording + + + Start the video recording + + + + Mass storage id to record + + + + + + + + + Command to record video + + Stop the video recording + + + Start the video recording + + + + + + State of media recording + + + + 1 if picture has been taken, 0 otherwise + + + Mass storage id where the picture was recorded + + + + + + State of video + + Video was stopped + + + Video was started + + + Video was failed + + + Video was auto stopped + + + + Mass storage id where the video was recorded + + + + + + State of device picture recording + + The picture recording is ready + + + The picture recording is busy + + + The picture recording is not available + + + + Error to explain the state + + No Error + + + Unknown generic error + + + Picture camera is out of order + + + Memory full ; cannot save one additional picture + + + Battery is too low to start/keep recording. + + + + + + + State of device video recording + + Video is stopped + + + Video is started + + + The video recording is not available + + + + Error to explain the state + + No Error + + + Unknown generic error + + + Video camera is out of order + + + Memory full ; cannot save one additional video + + + Battery is too low to start/keep recording. + + + + + + + Streaming resolution + + 360p resolution. + + + 480p resolution. + + + 720p resolution. + + + 1080p resolution. + + + + Recording resolution + + 360p resolution. + + + 480p resolution. + + + 720p resolution. + + + 1080p resolution. + + + + + + Events of media recording + + + + Last event of picture recording + + Picture taken and saved + + + Picture failed + + + + Error to explain the event + + No Error + + + Unknown generic error ; only when state is failed + + + Picture recording is busy ; only when state is failed + + + Picture recording not available ; only when state is failed + + + Memory full ; only when state is failed + + + Battery is too low to record. + + + + + + + Event of video recording + + Video start + + + Video stop and saved + + + Video failed + + + + Error to explain the event + + No Error + + + Unknown generic error ; only when state is failed + + + Video recording is busy ; only when state is failed + + + Video recording not available ; only when state is failed + + + Memory full + + + Battery is too low to record. + + + Video was auto stopped + + + + + + State from drone + + + + + + + Drone flying state + + Landed state + + + Taking off state + + + Hovering / Circling (for fixed wings) state + + + Flying state + + + Landing state + + + Emergency state + + + User take off state. Waiting for user action to take off. + + + Motor ramping state. + + + Emergency landing state. + Drone autopilot has detected defective sensor(s). + Only Yaw argument in PCMD is taken into account. + All others flying commands are ignored. + + + + + + + Drone alert state + + No alert + + + User emergency alert + + + Cut out alert + + + Critical battery alert + + + Low battery alert + + + The angle of the drone is too high + + + + + + + State of navigate home + + Navigate home is available + + + Navigate home is in progress + + + Navigate home is not available + + + Navigate home has been received, but its process is pending + + + + Reason of the state + + User requested a navigate home (available->inProgress) + + + Connection between controller and product lost (available->inProgress) + + + Low battery occurred (available->inProgress) + + + Navigate home is finished (inProgress->available) + + + Navigate home has been stopped (inProgress->available) + + + Navigate home disabled by product (inProgress->unavailable or available->unavailable) + + + Navigate home enabled by product (unavailable->available) + + + + + + + Latitude position in decimal degrees (500.0 if not available) + + + Longitude position in decimal degrees (500.0 if not available) + + + Altitude in meters (from GPS) + + + + + + Speed relative to the North (when drone moves to the north, speed is > 0) (in m/s) + + + Speed relative to the East (when drone moves to the east, speed is > 0) (in m/s) + + + Speed on the z axis (when drone moves down, speed is > 0) (in m/s) + + + + + + roll value (in radian) + + + Pitch value (in radian) + + + Yaw value (in radian) + + + + + + State of automatic take off mode (1 if enabled) + + + + + + Altitude in meters + + + + + + Latitude location in decimal degrees (500.0 if not available) + + + Longitude location in decimal degrees (500.0 if not available) + + + Altitude location in meters. + + + Latitude location error in meters (1 sigma/standard deviation) + -1 if not available. + + + Longitude location error in meters (1 sigma/standard deviation) + -1 if not available. + + + Altitude location error in meters (1 sigma/standard deviation) + -1 if not available. + + + + + + Drone landing state + + Linear landing + + + Spiral landing + + + + + + + Speed relative to air on x axis + (speed is always > 0) (in m/s) + + + + + + Latitude of the location (in degrees) to reach + + + Longitude of the location (in degrees) to reach + + + Altitude above sea level (in m) to reach + + + Orientation mode of the move to + + The drone won't change its orientation + + + The drone will make a rotation to look in direction of the given location + + + The drone will orientate itself to the given heading before moving to the location + + + The drone will orientate itself to the given heading while moving to the location + + + + Heading (relative to the North in degrees). + This value is only used if the orientation mode is HEADING_START or HEADING_DURING + + + Status of the move to + + The drone is actually flying to the given position + + + The drone has reached the target + + + The move to has been canceled, either by a CancelMoveTo command + or when a disconnection appears. + + + The move to has not been finished or started because of an error. + + + + + + + Motion state + + Drone is steady + + + Drone is moving + + + + + + + Latitude of the location (in degrees) to look at. + This information is only valid when the state is pending or running. + + + Longitude of the location (in degrees) to look at. + This information is only valid when the state is pending or running. + + + Altitude above sea level (in m) to look at. + This information is only valid when the state is pending or running. + + + Status of the move to + + The piloted POI is not available + + + The piloted POI is available + + + Piloted POI has been requested. Waiting to be in state that allow the piloted POI to start + + + Piloted POI is running + + + + + + + Status of battery to return home + + The battery is full enough to do a return home + + + The battery is about to be too discharged to do a return home + + + The battery level is too low to return to the home position + + + Battery capacity to do a return home is unknown. + This can be either because the home is unknown or the position of the drone is unknown, + or the drone has not enough information to determine how long it takes to fly home. + + + + + + Events of Piloting + + + + Distance traveled along the front axis [m] + + + Distance traveled along the right axis [m] + + + Distance traveled along the down axis [m] + + + Applied angle on heading [rad] + + + Error to explain the event + + No Error ; The relative displacement + + + Unknown generic error + + + The Device is busy ; command moveBy ignored + + + Command moveBy is not available ; command moveBy ignored + + + Command moveBy interrupted + + + + + + Network related commands + + + + The band(s) : 2.4 Ghz, 5 Ghz, or both + + 2.4 GHz band + + + 5 GHz band + + + Both 2.4 and 5 GHz bands + + + + + + + + + Network state from Product + + + + SSID of the AP + + + RSSI of the AP in dbm (negative value) + + + The band : 2.4 GHz or 5 GHz + + 2.4 GHz band + + + 5 GHz band + + + + Channel of the AP + + + + + + + + + The band of this channel : 2.4 GHz or 5 GHz + + 2.4 GHz band + + + 5 GHz band + + + + The authorized channel. + + + Bit 0 is 1 if channel is authorized outside (0 otherwise) ; Bit 1 is 1 if channel is authorized inside (0 otherwise) + + + + + + + + Piloting Settings commands + + + + Current altitude max in m + + + + + + Current tilt max in degree + + + + + + 1 to enable, 0 to disable + + + + + + Current max distance in meter + + + + + + 1 if the drone can't fly further than max distance, 0 if no limitation on the drone should be done + + + + + + maximum horizontal speed [m/s] + + + + + + maximum vertical speed [m/s] + + + + + + maximum horizontal acceleration [m/s2] + + + + + + maximum vertical acceleration [m/s2] + + + + + + maximum yaw rotation speed [deg/s] + + + + + + 1 to enable, 0 to disable + + + + + + Current altitude min in m + + + + + + The circling direction + + Circling ClockWise + + + Circling Counter ClockWise + + + + + + + The circling radius in meter + + + + + + The circling altitude in meter + + + + + + The Pitch mode + + Positive pitch values will make the drone lower its nose. + Negative pitch values will make the drone raise its nose. + + + Pitch commands are inverted. + Positive pitch values will make the drone raise its nose. + Negative pitch values will make the drone lower its nose. + + + + + + + 1 to enable the motion detection, 0 to disable it. + + + + + Piloting Settings state from product + + + + Current altitude max + + + Range min of altitude + + + Range max of altitude + + + + + + Current max tilt + + + Range min of tilt + + + Range max of tilt + + + + + + 1 if enabled, 0 if disabled + + + + + + Current max distance in meter + + + Minimal possible max distance + + + Maximal possible max distance + + + + + + 1 if the drone won't fly further than max distance, 0 if no limitation on the drone will be done + + + + + + maximum horizontal speed [m/s] + + + + + + maximum vertical speed [m/s] + + + + + + maximum horizontal acceleration [m/s2] + + + + + + maximum vertical acceleration [m/s2] + + + + + + maximum yaw rotation speed [deg/s] + + + + + + 1 if enabled, 0 if disabled + + + + + + Current altitude min + + + Range min of altitude min + + + Range max of altitude min + + + + + + The circling direction + + Circling ClockWise + + + Circling Counter ClockWise + + + + + + + The current circling radius in meter + + + Range min of circling radius in meter + + + Range max of circling radius in meter + + + + + + The current circling altitude in meter + + + Range min of circling altitude in meter + + + Range max of circling altitude in meter + + + + + + The Pitch mode + + Positive pitch values will make the drone lower its nose. + Negative pitch values will make the drone raise its nose. + + + Pitch commands are inverted. + Positive pitch values will make the drone raise its nose. + Negative pitch values will make the drone lower its nose. + + + + + + + 1 if motion detection is enabled, 0 otherwise. + + + + + Speed Settings commands + + + + Current max vertical speed in m/s + + + + + + Current max yaw rotation speed in degree/s + + + + + + 1 if present, 0 if not present + + + + + + 1 if outdoor flight, 0 if indoor flight + + + + + + Current max pitch/roll rotation speed in degree/s + + + + + Speed Settings state from product + + + + Current max vertical speed in m/s + + + Range min of vertical speed + + + Range max of vertical speed + + + + + + Current max yaw rotation speed in degree/s + + + Range min of yaw rotation speed + + + Range max of yaw rotation speed + + + + + + 1 if present, 0 if not present + + + + + + 1 if outdoor flight, 0 if indoor flight + + + + + + Current max pitch/roll rotation speed in degree/s + + + Range min of pitch/roll rotation speed + + + Range max of pitch/roll rotation speed + + + + + Network settings commands + + + + The type of wifi selection (auto, manual) + + Auto selection + + + Manual selection + + + + The allowed band(s) : 2.4 Ghz, 5 Ghz, or all + + 2.4 GHz band + + + 5 GHz band + + + Both 2.4 and 5 GHz bands + + + + The channel (not used in auto mode) + + + + + + The type of wifi security (open, wpa2) + + Wifi is not protected by any security (default) + + + Wifi is protected by wpa2 + + + + The key to secure the network (empty if type is open) + + + Type of the key + + Key is plain text, not encrypted + + + + + + Network settings state from product + + + + The type of wifi selection settings + + Auto selection + + + Auto selection 2.4ghz + + + Auto selection 5 ghz + + + Manual selection + + + + The actual wifi band state + + 2.4 GHz band + + + 5 GHz band + + + Both 2.4 and 5 GHz bands + + + + The channel (depends of the band) + + + + + + The type of wifi security (open, wpa2) + + Wifi is not protected by any security (default) + + + Wifi is protected by wpa2 + + + + + + + The type of wifi security (open, wpa2) + + Wifi is not protected by any security (default) + + + Wifi is protected by wpa2 + + + + The key used to secure the network (empty if type is open) + + + Type of the key + + Key is plain text, not encrypted + + + + + + Settings state from product + + + + Product Motor number + + + Product Motor type + + + Product Motors software version + + + Product Motors hardware version + + + + + + Product GPS software version + + + Product GPS hardware version + + + + + + Bit field for concerned motor. If bit 0 = 1, motor 1 is affected by this error. Same with bit 1, 2 and 3. + Motor 1: front left + Motor 2: front right + Motor 3: back right + Motor 4: back left + + + Enumeration of the motor error + + No error detected + + + EEPROM access failure + + + Motor stalled + + + Propeller cutout security triggered + + + Communication with motor failed by timeout + + + RC emergency stop + + + Motor controler scheduler real-time out of bounds + + + One or several incorrect values in motor settings + + + Too hot or too cold Cypress temperature + + + Battery voltage out of bounds + + + Incorrect number of LIPO cells + + + Defectuous MOSFET or broken motor phases + + + Not use for BLDC but useful for HAL + + + Error Made by BLDC_ASSERT() + + + + + + + name of the version : dot separated fields (major version - minor version - firmware type - nb motors handled). Firmware types : Release, Debug, Alpha, Test-bench + + + + + + total number of flights + + + Duration of the last flight (in seconds) + + + Duration of all flights (in seconds) + + + + + + Enumeration of the motor error + + No error detected + + + EEPROM access failure + + + Motor stalled + + + Propeller cutout security triggered + + + Communication with motor failed by timeout + + + RC emergency stop + + + Motor controler scheduler real-time out of bounds + + + One or several incorrect values in motor settings + + + Battery voltage out of bounds + + + Incorrect number of LIPO cells + + + Defectuous MOSFET or broken motor phases + + + Too hot or too cold Cypress temperature + + + Not use for BLDC but useful for HAL + + + Error Made by BLDC_ASSERT() + + + + + + + Product P7ID + + + + Product main cpu id + + Product main cpu id + + + + + Photo settings chosen by the user + + + + The type of photo format + + Take raw image + + + Take a 4:3 jpeg photo + + + Take a 16:9 snapshot from camera + + + Take jpeg fisheye image only + + + + + + + The type auto white balance + + Auto guess of best white balance params + + + Tungsten white balance + + + Daylight white balance + + + Cloudy white balance + + + White balance for a flash + + + + + + + Exposition value (bounds given by ExpositionChanged arg min and max, by default [-3:3]) + + + + + + Saturation value (bounds given by SaturationChanged arg min and max, by default [-100:100]) + + + + + + 1 if timelapse is enabled, 0 otherwise + + + interval in seconds for taking pictures + + + + + + 1 if video autorecord is enabled, 0 otherwise + + + Mass storage id to take video + + + + + + Video stabilization mode + + Video flat on roll and pitch + + + Video flat on pitch only + + + Video flat on roll only + + + Video follows drone angles + + + + + + + Video recording mode + + Maximize recording quality. + + + Maximize recording time. + + + + + + + Video framerate + + 23.976 frames per second. + + + 25 frames per second. + + + 29.97 frames per second. + + + + + + + Video streaming and recording resolutions + + 1080p recording, 480p streaming. + + + 720p recording, 720p streaming. + + + + + + Photo settings state from product + + + + The type of photo format + + Take raw image + + + Take a 4:3 jpeg photo + + + Take a 16:9 snapshot from camera + + + Take jpeg fisheye image only + + + + + + + The type auto white balance + + Auto guess of best white balance params + + + Tungsten white balance + + + Daylight white balance + + + Cloudy white balance + + + White balance for a flash + + + + + + + Exposure value + + + Min exposure value + + + Max exposure value + + + + + + Saturation value + + + Min saturation value + + + Max saturation value + + + + + + 1 if timelapse is enabled, 0 otherwise + + + interval in seconds for taking pictures + + + Minimal interval for taking pictures + + + Maximal interval for taking pictures + + + + + + 1 if video autorecord is enabled, 0 otherwise + + + Mass storage id for the taken video + + + + + + Video stabilization mode + + Video flat on roll and pitch + + + Video flat on pitch only + + + Video flat on roll only + + + Video follows drone angles + + + + + + + Video recording mode + + Maximize recording quality. + + + Maximize recording time. + + + + + + + Video framerate + + 23.976 frames per second. + + + 25 frames per second. + + + 29.97 frames per second. + + + + + + + Video resolution type. + + 1080p recording, 480p streaming. + + + 720p recording, 720p streaming. + + + + + + Control media streaming behavior. + + + + 1 to enable, 0 to disable. + + + + + + stream mode + + Minimize latency with average reliability (best for piloting). + + + Maximize the reliability with an average latency (best when streaming quality is important but not the latency). + + + Maximize the reliability using a framerate decimation with an average latency (best when streaming quality is important but not the latency). + + + + + + Media streaming status. + + + + Current video streaming status. + + Video streaming is enabled. + + + Video streaming is disabled. + + + Video streaming failed to start. + + + + + Video stream mode state + + stream mode + + Minimize latency with average reliability (best for piloting). + + + Maximize the reliability with an average latency (best when streaming quality is important but not the latency). + + + Maximize the reliability using a framerate decimation with an average latency (best when streaming quality is important but not the latency). + + + + + + GPS settings + + + + Home latitude in decimal degrees + + + Home longitude in decimal degrees + + + Home altitude in meters + + + + + + + + + GPS latitude in decimal degrees + + + GPS longitude in decimal degrees + + + GPS altitude in meters + + + Horizontal Accuracy in meter ; equal -1 if no horizontal Accuracy + + + Vertical Accuracy in meter ; equal -1 if no vertical Accuracy + + + + + + The type of the home position + + The drone will try to return to the take off position + + + The drone will try to return to the pilot position + + + The drone will try to return to the target of the current (or last) follow me + + + + + + + Delay in second + + + + + GPS settings state + + + + Home latitude in decimal degrees + + + Home longitude in decimal degrees + + + Home altitude in meters + + + + + + Home latitude in decimal degrees + + + Home longitude in decimal degrees + + + Home altitude in meters + + + + + + 1 if gps on drone is fixed, 0 otherwise + + + + + + The state of the gps update + + Drone GPS update succeed + + + Drone GPS update In progress + + + Drone GPS update failed + + + + + + + The type of the home position + + The drone will try to return to the take off position + + + The drone will try to return to the pilot position + + + The drone will try to return to the target of the current (or last) follow me + + + + + + + Delay in second + + + + + + GPS latitude in decimal degrees + + + GPS longitude in decimal degrees + + + + + Camera state + + + + Tilt camera consign for the drone [-100;100] + + + Pan camera consign for the drone [-100;100] + + + + + + Tilt value (in degree) + + + Pan value (in degree) + + + + + + Tilt camera consign for the drone [deg] + + + Pan camera consign for the drone [deg] + + + + + + Tilt value [deg] + + + Pan value [deg] + + + + + + Absolute max tilt velocity [deg/s] + + + Absolute max pan velocity [deg/s] + + + + + Anti-flickering related commands + + + + Type of the electric frequency + + Electric frequency of the country is 50hz + + + Electric frequency of the country is 60hz + + + + + + + Mode of the anti flickering functionnality + + Anti flickering based on the electric frequency previously sent + + + Anti flickering based on a fixed frequency of 50Hz + + + Anti flickering based on a fixed frequency of 60Hz + + + + + + Anti-flickering related states + + + + Type of the electric frequency + + Electric frequency of the country is 50hz + + + Electric frequency of the country is 60hz + + + + + + + Mode of the anti flickering functionnality + + Anti flickering based on the electric frequency previously sent + + + Anti flickering based on a fixed frequency of 50Hz + + + Anti flickering based on a fixed frequency of 60Hz + + + + + + GPS related States + + + + The number of satellite + + + + + + The type of the return home + + The drone has enough information to return to the take off position + + + The drone has enough information to return to the pilot position + + + The drone has not enough information, it will return to the first GPS fix + + + The drone has enough information to return to the target of the current (or last) follow me + + + + 1 if this type is available, 0 otherwise + + + + + + The type of the return home chosen + + The drone will return to the take off position + + + The drone will return to the pilot position + In this case, the drone will use the position given by ARDrone3-SendControllerGPS + + + The drone has not enough information, it will return to the first GPS fix + + + The drone will return to the target of the current (or last) follow me + In this case, the drone will use the position of the target of the followMe (given by ControllerInfo-GPS) + + + + + + Pro features enabled on the Bebop + + + + Bitfield representing enabled features. + + + + + Information about the connected accessories + + + + Id of the accessory for the session. + + + Accessory type + + Parrot Sequoia (multispectral camera for agriculture) + + + FLIR camera (thermal+rgb camera) + + + + Unique Id of the accessory. + This id is unique by accessory_type. + + + Software Version of the accessory. + + + List entry attribute Bitfield. + 0x01: First: indicate it's the first element of the list. + 0x02: Last: indicate it's the last element of the list. + 0x04: Empty: indicate the list is empty (implies First/Last). All other arguments should be ignored. + 0x08: Remove: This value should be removed from the existing list. + + + + + + Id of the accessory for the session. + + + Battery level in percentage. + + + List entry attribute Bitfield. + 0x01: First: indicate it's the first element of the list. + 0x02: Last: indicate it's the last element of the list. + 0x04: Empty: indicate the list is empty (implies First/Last). All other arguments should be ignored. + 0x08: Remove: This value should be removed from the existing list. + + + + + Sounds related commands + + + + + + + + + Sounds related events + + + + State of the alert sound + + Alert sound is not playing + + + Alert sound is playing + + + + + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/common.xml b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/common.xml new file mode 100644 index 0000000..59af505 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/common.xml @@ -0,0 +1,1605 @@ + + + + All common commands shared between all projects + + Network related commands + + + + + + Network Event from product + + + + Cause of the disconnection of the product + + The button off has been pressed + + + Unknown generic cause + + + + + + Settings commands + + + + + + + + + + Product name + + + + + + Country code with ISO 3166 format + + + + + + Boolean : 0 : Manual / 1 : Auto + + + + + Settings state from product + + + + + + + + + + Product name + + + + + + Product software version + + + Product hardware version + + + + + + Serial high number (hexadecimal value) + + + + + + Serial low number (hexadecimal value) + + + + + + Country code with ISO 3166 format, empty string means unknown country. + + + + + + Boolean : 0 : Manual / 1 : Auto + + + + + Common commands + + + + + + + Date with ISO-8601 format + + + + + + Time with ISO-8601 format + + + + + + + + Common state from product + + + + + + + Battery percentage + + + + + + Mass storage id (unique) + + + Mass storage name + + + + + + Mass storage state id (unique) + + + Mass storage size in MBytes + + + Mass storage used size in MBytes + + + Mass storage plugged (1 if mass storage is plugged, otherwise 0) + + + Mass storage full information state (1 if mass storage full, 0 otherwise). + + + Mass storage internal type state (1 if mass storage is internal, 0 otherwise) + + + + + + Date with ISO-8601 format + + + + + + Time with ISO-8601 format + + + + + + Mass storage free space in MBytes + + + Mass storage record time reamining in minute + + + Mass storage photo remaining + + + + + + RSSI of the signal between controller and the product (in dbm) + + + + + + Sensor name + + Inertial Measurement Unit sensor + + + Barometer sensor + + + Ultrasonic sensor + + + GPS sensor + + + Magnetometer sensor + + + Vertical Camera sensor + + + + Sensor state (1 if the sensor is OK, 0 if the sensor is NOT OK) + + + + + + The Model of the product. + + Travis (RS taxi) model. + + + Mars (RS space) model + + + SWAT (RS SWAT) model + + + Mc Lane (RS police) model + + + Blaze (RS fire) model + + + Orak (RS carbon hydrofoil) model + + + New Z (RS wooden hydrofoil) model + + + Marshall (JS fire) model + + + Diesel (JS SWAT) model + + + Buzz (JS space) model + + + Max (JS F1) model + + + Jett (JS flames) model + + + Tuk-Tuk (JS taxi) model + + + Swing black model + + + Swing white model + + + + + + + List entry attribute Bitfield. + 0x01: First: indicate it's the first element of the list. + 0x02: Last: indicate it's the last element of the list. + 0x04: Empty: indicate the list is empty (implies First/Last). All other arguments should be ignored. + + + Following of country code with ISO 3166 format, separated by ";". Be careful of the command size allowed by the network used. If necessary, split the list in several commands. + + + + + + Mass storage id (unique) + + + Number of photos (does not include raw photos) + + + Number of videos + + + Number of puds + + + Number of crash logs + + + + + + Mass storage id (unique) + + + Number of photos (does not include raw photos) + + + Number of videos + + + Number of puds + + + Number of crash logs + + + Number of raw photos + + + + + + Mass storage id (unique) + + + Number of photos (does not include raw photos) + + + Number of videos + + + Number of raw photos + + + + + + Timestamp in milliseconds since 00:00:00 UTC on 1 January 1970. + + + Timestamp in milliseconds since 00:00:00 UTC on 1 January 1970. 0 mean that video is still recording. + + + + + Over heat commands + + + + + + + + + Overheat state from product + + + + + + + Type of overheat regulation : 0 for ventilation, 1 for switch off + + + + + Notify the device about the state of the controller application. + + + + 0 when the application is not in the piloting HUD, 1 when it enters the HUD. + + + + + Wifi settings commands + + + + 1 if it should use outdoor wifi settings, 0 otherwise + + + + + Wifi settings state from product + + + + 1 if it should use outdoor wifi settings, 0 otherwise + + + + + Mavlink flight plans commands + + + + flight plan file path from the mavlink ftp root + + + type of the played mavlink file + + Mavlink file for FlightPlan + + + Mavlink file for MapMyHouse + + + + + + + + + + + + Mavlink flight plans states commands + + + + State of the mavlink + + Mavlink file is playing + + + Mavlink file is stopped (arg filepath and type are useless in this state) + + + Mavlink file is paused + + + Mavlink file is loaded (it will be played at take-off) + + + + flight plan file path from the mavlink ftp root + + + type of the played mavlink file + + Mavlink file for FlightPlan + + + Mavlink file for MapMyHouse + + + + + + + State of play error + + There is no error + + + The drone is not in outdoor mode + + + The gps is not fixed + + + The magnetometer of the drone is not calibrated + + + + + + + Index of the mission item. This is the place of the mission item in the list of the items of the mission. + Begins at 0. + + + + + + + + 1 to enable, 0 to disable + + + + + + + + 1 if enabled, 0 if disabled + + + 1 if readOnly, 0 if writable + + + + + Calibration commands + + + + 1 if the calibration should be started, 0 if it should be aborted + + + + + + 1 if the calibration should be started, 0 if it should be aborted + + + + + Status of the calibration + + + + State of the x axis (roll) calibration : 1 if calibration is done, 0 otherwise + + + State of the y axis (pitch) calibration : 1 if calibration is done, 0 otherwise + + + State of the z axis (yaw) calibration : 1 if calibration is done, 0 otherwise + + + 1 if calibration has failed, 0 otherwise. If this arg is 1, consider all previous arg as 0 + + + + + + 1 if calibration is required, 0 if current calibration is still valid + + + + + + The axis to calibrate + + If the current calibration axis should be the x axis + + + If the current calibration axis should be the y axis + + + If the current calibration axis should be the z axis + + + If none of the axis should be calibrated + + + + + + Status of the calibration process + + 1 if calibration has started, 0 otherwise + + + + Sent when the state of the pitot calibration has changed + + State of pitot calibration + + Calibration is ok + + + Calibration is started, waiting user action + + + Calibration is in progress + + + Calibration is required + + + + lastError : 1 if an error occured and 0 if not + + + + + Status of the camera settings + + + + Value of the camera horizontal fov (in degree) + + + Value of max pan (right pan) (in degree) + + + Value of min pan (left pan) (in degree) + + + Value of max tilt (top tilt) (in degree) + + + Value of min tilt (bottom tilt) (in degree) + + + + + GPS related commands + + + + Controller latitude in decimal degrees + + + Controller longitude in decimal degrees + + + + + FlightPlan state commands + + + + Running a flightPlan file is available (1 running a flightPlan file is available, otherwise 0) + + + + + + Drone FlightPlan component id (unique) + + Drone GPS component. + State is 0 when the drone needs a GPS fix. + + + Drone Calibration component. + State is 0 when the sensors of the drone needs to be calibrated. + + + Mavlink file component. + State is 0 when the mavlink file is missing or contains error. + + + Drone Take off component. + State is 0 when the drone cannot take-off. + + + Component for waypoints beyond the geofence. + State is 0 when one or more waypoints are beyond the geofence. + + + + State of the FlightPlan component (1 FlightPlan component OK, otherwise 0) + + + + + + 1 if FlightPlan is locked: can't pause or stop FlightPlan. + 0 if FlightPlan is unlocked: pause or stop available. + + + + + FlightPlan Event commands + + + + + + + + + ARlibs Versions Commands + + Controller libARCommands version + + version of libARCommands ("1.2.3.4" format) + + + + SkyController libARCommands version + + version of libARCommands ("1.2.3.4" format) + + + + Device libARCommands version + + version of libARCommands ("1.2.3.4" format) + + + + + Audio-related commands. + + + + Bit field for TX and RX ready. + bit 0 is 1 if controller is ready and wants to receive sound (Drone TX) + bit 1 is 1 if controller is ready and wants to send sound (Drone RX) + + + + + Audio-related state updates. + + + + Bit field for TX and RX running + bit 0 is 1 if Drone TX is running + bit 1 is 1 if Drone RX is running + + + + + Controls the headlight LEDs of the Evo variants. + + + + Set the left LED intensity value (0 through 255). + + + Set the right LED intensity value (0 through 255). + + + + + Get information about the state of the Evo variants' LEDs. + + + + The intensity value for the left LED (0 through 255). + + + The intensity value for the right LED (0 through 255). + + + + + Animations-related commands. + + + + Animation to start. + + Flash headlights. + + + Blink headlights. + + + Oscillating headlights. + + + Spin animation. + + + Tap animation. + + + Slow shake animation. + + + Metronome animation. + + + Standing dance animation. + + + Spin jump animation. + + + Spin that end in standing posture, or in jumper if it was standing animation. + + + Spiral animation. + + + Slalom animation. + + + Boost animation. + + + Make a looping. (Only for WingX) + + + Make a barrel roll of 180 degree turning on right. (Only for WingX) + + + Make a barrel roll of 180 degree turning on left. (Only for WingX) + + + Put the drone upside down. (Only for WingX) + + + + + + + Animation to stop. + + Flash headlights. + + + Blink headlights. + + + Oscillating headlights. + + + Spin animation. + + + Tap animation. + + + Slow shake animation. + + + Metronome animation. + + + Standing dance animation. + + + Spin jump animation. + + + Spin that end in standing posture, or in jumper if it was standing animation. + + + Spiral animation. + + + Slalom animation. + + + Boost animation. + + + Make a looping. (Only for WingX) + + + Make a barrel roll of 180 degree turning on right. (Only for WingX) + + + Make a barrel roll of 180 degree turning on left. (Only for WingX) + + + Put the drone upside down. (Only for WingX) + + + + + + + + + Animations-related notification/feedback commands. + + + + Animation type. + + Flash headlights. + + + Blink headlights. + + + Oscillating headlights. + + + Spin animation. + + + Tap animation. + + + Slow shake animation. + + + Metronome animation. + + + Standing dance animation. + + + Spin jump animation. + + + Spin that end in standing posture, or in jumper if it was standing animation. + + + Spiral animation. + + + Slalom animation. + + + Boost animation. + + + Make a looping. (Only for WingX) + + + Make a barrel roll of 180 degree turning on right. (Only for WingX) + + + Make a barrel roll of 180 degree turning on left. (Only for WingX) + + + Put the drone upside down. (Only for WingX) + + + + State of the animation + + animation is stopped + + + animation is started + + + The animation is not available + + + + Error to explain the state + + No Error + + + Unknown generic error + + + + + + Accessories-related commands. + + + + Accessory configuration to set. + + No accessory. + + + Standard wheels + + + Truck wheels + + + Hull + + + Hydrofoil + + + + + + Accessories-related commands. + + + + Accessory configurations supported by the product. + + No accessory. + + + Standard wheels + + + Truck wheels + + + Hull + + + Hydrofoil + + + + + + Accessory config response. + + Accessory configuration reported by firmware. + + No accessory configuration set. Controller needs to set one. + + + No accessory. + + + Standard wheels + + + Truck wheels + + + Hull + + + Hydrofoil + + + Configuration in progress. + + + + Error code. + + No error. Accessory config change successful. + + + Cannot change accessory configuration for some reason. + + + Cannot change accessory configuration while flying. + + + + + + + 1 if the modification of the accessory Config is enabled, 0 otherwise + + + + + Commands sent by the controller to set charger parameters. + + + + The new maximum charge rate. + + Fully charge the battery at a slow rate. Typically limit max charge current to 512 mA. + + + Almost fully-charge the battery at moderate rate (> 512mA) but slower than the fastest rate. + + + Almost fully-charge the battery at the highest possible rate supported by the charger. + + + + + + Commands sent by the firmware to advertise the charger status. + + + + The current maximum charge rate. + + Fully charge the battery at a slow rate. Typically limit max charge current to 512 mA. + + + Almost fully-charge the battery at moderate rate (> 512 mA) but slower than the fastest rate. + + + Almost fully-charge the battery at the highest possible rate supported by the charger. + + + + + + + Charger status. + + The battery is discharging. + + + The battery is charging at a slow rate about 512 mA. + + + The battery is charging at a moderate rate (> 512 mA) but slower than the fastest rate. + + + The battery is charging at a the fastest rate. + + + The charger is plugged and the battery is fully charged. + + + + The current charging phase. + + The charge phase is unknown or irrelevant. + + + First phase of the charging process. The battery is charging with constant current. + + + Second phase of the charging process. The battery is charging with constant current, with a higher voltage than the first phase. + + + Last part of the charging process. The battery is charging with a constant voltage. + + + The battery is fully charged. + + + + + + + The charge rate recorded by the firmware for the last charge. + + The last charge rate is not known. + + + Slow charge rate. + + + Moderate charge rate. + + + Fast charge rate. + + + + + + + The current charging phase. + + The charge phase is unknown or irrelevant. + + + First phase of the charging process. The battery is charging with constant current. + + + Second phase of the charging process. The battery is charging with constant current, with a higher voltage than the first phase. + + + Last part of the charging process. The battery is charging with a constant voltage. + + + The battery is fully charged. + + + The battery is discharging; Other arguments refers to the last charge. + + + + The charge rate. If phase is DISCHARGING, refers to the last charge. + + The charge rate is not known. + + + Slow charge rate. + + + Moderate charge rate. + + + Fast charge rate. + + + + The charging intensity, in dA. (12dA = 1,2A) ; If phase is DISCHARGING, refers to the last charge. Equals to 0 if not known. + + + The full charging time estimated, in minute. If phase is DISCHARGING, refers to the last charge. Equals to 0 if not known. + + + + + Commands sent by the drone to inform about the run or flight state + + + + Id of the run + + + + + Factory reset commands + + + + + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/followme.xml b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/followme.xml new file mode 100644 index 0000000..88bd644 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/followme.xml @@ -0,0 +1,612 @@ + + + + FollowMe feature + + + FollowMe mode + + No follow me + + + Look at the target without moving automatically + + + Follow the target keeping the same vector + + + Follow the target keeping the same orientation to its direction + + + + FollowMe behavior + + Drone is not moving according to the target + This means that at least one required input is missing + + + Follow the target + + + Look at the target without moving + + + + Input values used by the FollowMe + + Drone is calibrated + + + Drone gps has fixed and has a good accuracy + + + Target gps data is known and has a good accuracy + + + Target barometer data is available + + + Drone is far enough from the target + + + Drone is high enough from the ground + + + Target detection is done by image detection among other things + + + + Geographic and Relative follow me configuration parameters + + Distance configuration + + + Elevation configuration + + + Azimuth configuration + + + + FollowMe animation type + + No animation + + + Turn around the target + + + Pass by the zenith and change of side + + + Fly far from the target and fly back + + + Move to the target and go high when it is near + + + Fly in line + + + + Helicoid animation configuration parameters. + + Speed parameter + + + Number of turn + + + Vertical distance + + + + Swing configure parameters. + + Speed parameter + + + Vertical distance + + + + Boomerang animation configure parameters. + + Speed parameter + + + Distance + + + + Candle animation configure parameters. + + Speed parameter + + + Follow the target keeping the same vector + + + + Dolly slide animation configure parameters. + + Speed parameter + + + Angle + + + Horizontal distance + + + + State of the image detection + + No image detection + + + Image detection is considered ok by the drone + + + Image detection is considered lost or + in contradiction with gps value. + This state will remain until a new selection of the target is done + + + + + + + + + + + + + + + Mode asked by user + + + Behavior of the drone according to the asked mode + + + Current animation. + This parameter has been deprecated. Please use the animation feature. + + + List of available animations + This parameter has been deprecated. Please use the animation feature. + + + + + + + List of missing requirements to enter this mode on start. + Bit is 0 if the input is not ok, 1 if the input is ok. + If at least one input is missing, drone won't able to follow the target. + It won't use any fallback either + + + List of inputs that can improve the mode. + Bit is 0 if the input is not ok, 1 if the input is ok. + If at least one input is missing, a downgraded mode will be used. See behavior + + + + + + + The distance leader-follower in meter + Not used when arg start is at 0 + + + The elevation leader-follower in rad (not used when arg start is at 0) + + + The azimuth north-leader-follower in rad (not used when arg start is at 0) + + + + + + + The distance leader-follower in meter + If distance is default, this value is the current drone distance + + + The elevation leader-follower in rad + If elevation is default, this value is the current leader to drone elevation + + + The azimuth north-leader-follower in rad + If azimuth is default, this value is the current leader to drone azimuth + + + + + + + The distance leader-follower in meter + + + The elevation leader-follower in rad + + + The azimuth north-leader-follower in rad + + + + + + + The distance leader-follower in meter + If distance is default, this value is the current drone distance + + + The elevation leader-follower in rad + If elevation is default, this value is the current leader to drone elevation + + + The azimuth course-leader-follower in rad + If azimuth is default, this value is the current leader to drone azimuth + + + + + + Target latitude (in degrees) + + + Target longitude (in degrees) + + + Target altitude (in meters, relative to sea level) + + + Target north speed (in m/s) + + + Target east speed (in m/s) + + + Target down speed (in m/s) + + + + + + + + + + + The desired speed of the anim in m/s + Not used when speed_is_default is 1 + + + The number of revolution (in turn) + Negative value is infinite + Example: 1.5 makes an entire turn plus half of a turn + Not used when revolutionNb_is_default is 1 + + + Distance that should be made by the product to reach the top of the helicoid in m + Not used when verticalDistance_is_default is 1 + + + + + + + The speed of the anim in m/s + + + The number of revolution (in turn) + Negative value is infinite + + + Distance that will be made by the product to reach the top of the helicoid in m + + + + + + + The desired speed of the anim in m/s + Not used when speed_is_default is 1 + Not used when start is 0 + + + Distance that should be made by the product to reach the top of the swing in m + Not used when verticalDistance_is_default is 1 + Not used when start is 0 + + + + + + + The speed of the anim in m/s + + + Distance that will be made by the product to reach the top of the swing in m + + + + + + + The desired speed of the anim in m/s + Not used when speed_is_default is 1 + Not used when start is 0 + + + Distance that should be made by the product to reach its return point in m + Not used when distance_is_default is 1 + Not used when start is 0 + + + + + + + The speed of the anim in m/s + + + Distance that will be made by the product to reach its return point in m + + + + + + + The desired speed of the anim in m/s + Not used when speed_is_default is 1 + Not used when start is 0 + + + Distance that should be made by the product to reach the top of the vertical zoom-out in m + Not used when verticalDistance_is_default is 1 + Not used when start is 0 + + + + + + + The speed of the anim in m/s + + + Distance that will be made by the product to reach the top of the vertical zoom-out in m + + + + + + + The desired speed of the anim in m/s + Not used when speed_is_default is 1 + Not used when start is 0 + + + Desired angle Product-User-Target in rad + Not used when angle_is_default is 1 + Not used when start is 0 + + + Distance that should be made by the product to reach its target in m + Not used when horizontalDistance_is_default is 1 + Not used when start is 0 + + + + + + + The speed of the anim in m/s + + + Angle Product-User-Target in rad + + + Distance that will be made by the product to reach its target in m + + + + + + Horizontal position in the video (in %, from left to right) + + + Vertical position in the video (in %, from bottom to top) + + + + + + Horizontal position in the video (in %, from left to right) + + + Vertical position in the video (in %, from bottom to top) + + + + + + Horizontal north-drone-target angle in radian + + + Vertical angle horizon-drone-target in radian + + + Normalized relative radial speed in 1/second + + + Confidence index of the detection (from 0 to 255, the highest is the best) + + + Boolean. 1 if the selection is new, 0 otherwise + + + Acquisition time of processed picture in millisecond + + + + + + + + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/generic.xml b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/generic.xml new file mode 100644 index 0000000..8a846ee --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/generic.xml @@ -0,0 +1,109 @@ + + + + All generic messages + + + Flags use by maps and lists + + indicate it's the first element of the list. + + + indicate it's the last element of the list. + + + indicate the list is empty (implies First/Last). All other arguments should be ignored. + + + This value should be removed from the existing list. + + + + + + Drone settings + + + + + + + + + + + + + + Drone settings changed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/minidrone.xml b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/minidrone.xml new file mode 100644 index 0000000..5329fe6 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/minidrone.xml @@ -0,0 +1,1127 @@ + + + + All MiniDrone-only commands + + All commands related to piloting the MiniDrone + + Do a flat trim + + + Ask the drone to take off + + + Ask the drone to move around. + + Boolean flag to activate roll/pitch movement + + + Roll consign for the MiniDrone [-100;100] + + + Pitch consign for the MiniDrone [-100;100] + + + Yaw consign for the MiniDrone [-100;100] + + + Gaz consign for the MiniDrone [-100;100] + + + Timestamp in miliseconds. Not an absolute time. (Typically 0 = time of connexion). + + + + Ask the MiniDrone to land + + + Put drone in emergency state + + + Set MiniDrone automatic take off mode + + State of automatic take off mode + + + + Set drone FlyingMode. Only supported by WingX + + Drone Flying Mode + + Fly as a quadrictopter + + + Fly as a plane in forward mode + + + Fly as a plane in backward mode + + + + + Set Plane Gear Box. Only supported by WingX + + Plane Gear Box + + Gear 1. Low speed + + + Gear 2. Middle speed + + + Gear 3. High speed + + + + + Command to toggle between "easy" piloting mode and "preferred" piloting mode. + This command only works while the drone is flying. + + + + Occasional information + + MiniDrone send flat trim was correctly processed + + + Drone flying state changed + + Drone flying state + + Landed state + + + Taking off state + + + Hovering state + + + Flying state + + + Landing state + + + Emergency state + + + Rolling state + + + Initializing state (user should let the drone steady for a while) + + + + + Drone alert state changed + + Drone alert state + + No alert + + + User emergency alert + + + Cut out alert + + + Critical battery alert + + + Low battery alert + + + + + Set MiniDrone automatic take off mode + + State of automatic take off mode + + + + FlyingMode changed. Only supported by WingX + + Drone Flying Mode + + Fly as a quadrictopter + + + Fly as a plane in forward mode + + + Fly as a plane in backward mode + + + + + Plane Gear Box changed. Only supported by WingX + + Plane Gear Box + + Gear 1. Low speed + + + Gear 2. Middle speed + + + Gear 3. High speed + + + + + Event informing about the piloting mode. + + + The flight envelope of Mambo FPV has been divided in three piloting modes. + The first one corresponds to the well-known flying mode currently used for + Mambo, which is based in an horizontal stabilisation (performed via the + vertical camera and the acceleration information) and a vertical acceleration + (by means of the ultrasound, the barometer and the vertical accelerometer) in + order for the drone to stay in fixed point position when no piloting commands + are sent by the user. + + + The second piloting mode consists of deactivating the horizontal stabilisation. + Thus, in this flying mode, when no piloting command is received, the drone will + try to stay at 0° tilt angle instead of responding to a 0 m/s horizontal speed + reference. This behaviour will result in a slight horizontal drift. + + + The third piloting mode also adds the vertical stabilisation deactivation and, + therefore, a slight vertical drift. When flying in the third mode, a closed + loop height control is no longer performed in order for the drone to keep a + certain height and vertical speed. Instead, the vertical command coming from + the user will directly generate an open loop acceleration command. + + + + + + Animation commands + + Make a flip + + Direction for the flip + + Flip direction front + + + Flip direction back + + + Flip direction right + + + Flip direction left + + + + + Change the product cap + + Change the cap with offset angle [-180;180] + + + + + Media recording management + + @deprecated + Take picture + + Mass storage id to take picture + + + + Take picture + + + + State of media recording + + @deprecated + State of picture recording + + 1 if picture has been taken, 0 otherwise + + + Mass storage id to record + + + + State of device picture recording changed + + State of device picture recording + + The picture recording is ready + + + The picture recording is busy + + + The picture recording is not available + + + + Error to explain the state + + No Error + + + Unknown generic error + + + Picture camera is out of order + + + Memory full ; cannot save one additional picture + + + Battery is too low to start/keep recording. + + + + + + Events of media recording + + Event of picture recording + + Last event of picture recording + + Picture taken and saved + + + Picture failed + + + + Error to explain the event + + No Error + + + Unknown generic error ; only when state is failed + + + Picture recording is busy ; only when state is failed + + + Picture recording not available ; only when state is failed + + + Memory full ; only when state is failed + + + Battery is too low to record. + + + + + + Piloting Settings commands + + Set Max Altitude + + Current altitude max in m + + + + Set Max Tilt + + Current tilt max in degree + + + + + + 1 to enable, 0 to disable + + + + This setting represents the vertical acceleration command that will be sent to the drone when piloting in gaz mode. + In this case, closed loop height control is no longer performed; an open loop vertical acceleration command is generated instead. + This command results from multiplying the user command coming from the joystick (float value between 0 and 1) by the MaxThrottle setting (also a value between 0 and 1) and by the gravity constant. + Thus, we obtain the corresponding value in [m/s²] that will then be mixed with the attitude commands and translated into rotation speeds. + As an example, if the value of this setting is 0.5, the maximal acceleration command that can be generated when the user command equals 1 is : + acc_cmd_max = 1 * 0.5 * 9.81 m/s² = 4.905 m/s². + + Max throttle, between 0 and 1. + + + + The flight envelope of Mambo FPV has been divided in three piloting modes. + + Piloting modes. + + The flight envelope of Mambo FPV has been divided in three piloting modes. + The first one corresponds to the well-known flying mode currently used for + Mambo, which is based in an horizontal stabilisation (performed via the + vertical camera and the acceleration information) and a vertical acceleration + (by means of the ultrasound, the barometer and the vertical accelerometer) in + order for the drone to stay in fixed point position when no piloting commands + are sent by the user. + + + The second piloting mode consists of deactivating the horizontal stabilisation. + Thus, in this flying mode, when no piloting command is received, the drone will + try to stay at 0° tilt angle instead of responding to a 0 m/s horizontal speed + reference. This behaviour will result in a slight horizontal drift. + + + The third piloting mode also adds the vertical stabilisation deactivation and, + therefore, a slight vertical drift. When flying in the third mode, a closed + loop height control is no longer performed in order for the drone to keep a + certain height and vertical speed. Instead, the vertical command coming from + the user will directly generate an open loop acceleration command. + + + + + + Piloting Settings state from product + + Max Altitude sent by product + + Current altitude max + + + Range min of altitude + + + Range max of altitude + + + + Max tilt sent by product + + Current max tilt + + + Range min of tilt + + + Range max of tilt + + + + + + 1 if enabled, 0 if disabled + + + + Event informing about the max throttle. + + Max throttle, between 0 and 1. + + + + Event informing about the preferred piloting mode. + + + The flight envelope of Mambo FPV has been divided in three piloting modes. + The first one corresponds to the well-known flying mode currently used for + Mambo, which is based in an horizontal stabilisation (performed via the + vertical camera and the acceleration information) and a vertical acceleration + (by means of the ultrasound, the barometer and the vertical accelerometer) in + order for the drone to stay in fixed point position when no piloting commands + are sent by the user. + + + The second piloting mode consists of deactivating the horizontal stabilisation. + Thus, in this flying mode, when no piloting command is received, the drone will + try to stay at 0° tilt angle instead of responding to a 0 m/s horizontal speed + reference. This behaviour will result in a slight horizontal drift. + + + The third piloting mode also adds the vertical stabilisation deactivation and, + therefore, a slight vertical drift. When flying in the third mode, a closed + loop height control is no longer performed in order for the drone to keep a + certain height and vertical speed. Instead, the vertical command coming from + the user will directly generate an open loop acceleration command. + + + + + + Speed Settings commands + + Set Max Vertical speed + + Current max vertical speed in m/s + + + + Set Max Rotation speed + + Current max rotation speed in degree/s + + + + Presence of wheels + + 1 if present, 0 if not present + + + + Set Max Horizontal speed (only used in case where PilotingSettings_MaxTilt is not used like in hydrofoil mode) + + Current max Horizontal speed in m/s + + + + Set max plane mode rotation speed (only available for wing x) + + Current max plane mode rotation speed in degree/s + + + + + Speed Settings state from product + + Max vertical speed sent by product + + Current max vertical speed in m/s + + + Range min of vertical speed + + + Range max of vertical speed + + + + Max rotation speed sent by product + + Current max rotation speed in degree/s + + + Range min of rotation speed + + + Range max of rotation speed + + + + Presence of wheels sent by product + + 1 if present, 0 if not present + + + + Max horizontal speed sent by product (only used in case where PilotingSettings_MaxTilt is not used like in hydrofoil mode) + + Current max horizontal speed in m/s + + + Range min of horizontal speed + + + Range max of horizontal speed + + + + Max plane rotation speed sent by product (only available for wing x) + + Current max plane mode rotation speed in degree/s + + + Range min of plane mode rotation speed + + + Range max of plane mode rotation speed + + + + + Settings commands + + Set MiniDrone cut out mode + + Enable cut out mode (1 if is activate, 0 otherwise) + + + + + Settings state from product + + @deprecated + Product Motors versions + + Product Motor number [1 - 4] + + + Product Motor type + + + Product Motors software version + + + Product Motors hardware version + + + + @deprecated + Product Inertial versions + + Product Inertial software version + + + Product Inertial hardware version + + + + MiniDrone cut out mode + + State of cut out mode (1 if is activate, 0 otherwise) + + + + + Settings state from product + + @deprecated + Flood control regulation + + Delay (in ms) between two PCMD + + + + + GPS related commands + + Set the controller latitude for a run. + + Controller latitude in decimal degrees + + + + Set the controller longitude for a run. + + Controller longitude in decimal degrees + + + + + Configuration related commands + + Set the controller type. + + Controller type like iOS or Android + + + + Set the controller name. + + Controller name like com.parrot.freeflight3 + + + + + USB Accessories state commands. + + USB Light accessory state cmd. + + Usb accessory id + + + Usb Light state. + + Fixed state at given intensity. + + + Blinked state. + + + Oscillated state. + + + + Light intensity from 0 (OFF) to 100 (Max intensity). + Only used in FIXED state. + + + List entry attribute Bitfield. + 0x01: First: indicate it's the first element of the list. + 0x02: Last: indicate it's the last element of the list. + 0x04: Empty: indicate the list is empty (implies First/Last). All other arguments should be ignored. + 0x08: Remove: This value should be removed from the existing list. + + + + USB Claw accessory state cmd. + + Usb accessory id + + + Usb Claw state. + + Claw is fully opened. + + + Claw open in progress. + + + Claw is fully closed. + + + Claw close in progress. + + + + List entry attribute Bitfield. + 0x01: First: indicate it's the first element of the list. + 0x02: Last: indicate it's the last element of the list. + 0x04: Empty: indicate the list is empty (implies First/Last). All other arguments should be ignored. + 0x08: Remove: This value should be removed from the existing list. + + + + USB Gun accessory state cmd. + + Usb accessory id. + + + USB Claw state. + + Gun is ready to fire. + + + Gun is busy (ie not ready to fire). + + + + List entry attribute Bitfield. + 0x01: First: indicate it's the first element of the list. + 0x02: Last: indicate it's the last element of the list. + 0x04: Empty: indicate the list is empty (implies First/Last). All other arguments should be ignored. + 0x08: Remove: This value should be removed from the existing list. + + + + + USB Accessories control commands. + + USB Light control cmd. + + Usb accessory id + + + Usb Light mode. + + Turn light in fixed state at a given intensity. + + + Turn light in blinked state. + + + Turn light in oscillated state. + + + + Light intensity from 0 (OFF) to 100 (Max intensity). + Only used in FIXED mode. + + + + USB Claw control cmd. + + Usb accessory id. + + + USB Claw action. + + Open Claw. + + + Close Claw. + + + + + USB Gun control cmd. + + Usb accessory id + + + USB Gun action. + + Fire. + + + + + + Remote controller related commands. + + Send the address of the remote controller on which the drone should be paired + This is used to pair a Tinos controller + Where mac address: MSB-MID-LSB. + + 2 most significant bytes of mac address + + + 2 middle bytes of mac address + + + 2 least significant bytes of mac address + + + + + Navigation Data. + + Get the drone position from takeoff point (0, 0, 0, 0). + The orthonormal basis is fixed at this point. The axis are + oriented the following way : + * X : From the rear of the drone to its front. + * Y : From the right of the drone to its left. + * Z : Orthogonal to X and Y and oriented upward. + * Psi : From 180 to -180 in the clockwise direction, + + Position on X axis, relative to take off position (cm). + + + Position on Y axis, relative to take off position (cm). + + + Position on Z axis, relative to take off position (cm). + + + Psi angle [-180; 180], relative to take off orientation. + + + Time elapsed since last data send (ms). + + + + Event informing about the estimated drone speed in horizontal frame. + It is similar to NED frame but with drone heading. + Down speed is positive when the drone is going down. + Speed is in m/s. + + Speed on the x axis (when drone moves forward, speed is > 0). + + + Speed on the y axis (when drone moves right, speed is > 0). + + + Speed on the z axis (when drone moves down, speed is > 0). + + + Acquisition timestamp (ms). + + + + Event informing about the estimated altitude above takeoff level. + + Altitude in meters. + + + Acquisition timestamp (ms). + + + + Event informing about the estimated quaternion. + They represent the rotation from the NED frame (determined at drone startup) to the estimated drone body frame. + Its elements are between -1 and 1. + + Element w. + + + Element x. + + + Element y. + + + Element z. + + + Acquisition timestamp (ms). + + + + + Minicam related events. + + Event informing about the minicam power mode. + + Power mode of the camera. + + Low power: most hardware is powered off, wake up via USB resume. + + Used when charging. + + + Medium power: video hardware is powered off. + + Used when drone is not flying during more than 3 minutes. + Note that it can still stream the SD content. + + + Normal power: all features are available. + + Used when flying. + + + + + Event informing about the minicam product serial number. + + Serial number. + + + + Event informing about the state of the camera. + + State of the camera. + + Minicam is unplugged. + + + Minicam is plugged, but not ready. + + + Minicam is ready. + + + + + Get the accessory Version. + + Accessory software version. + + + Accessory hardware version. + + + + Event informing that the picture has been taken. + + State of device picture recording. + + Picture recording is ready. + + + Picture recording is busy. + + + Picture recording is not available. + + + + Result of device picture recording. + + Success. + + + Device is full. + + + Continuous shooting is already running. + + + Over snapshot max queue size. + + + Couldn't take picture. + + + SD card doesn't exist. + + + SD card format error. + + + SD card is formatting. + + + + + Event informing about the video recording state. + + State of device video recording. + + Video is stopped. + + + Video is started. + + + The video recording is not available. + + + + Error to explain the state. + + No Error. + + + Unknown generic error. + + + Video camera is out of order. + + + Memory full ; cannot save one additional video. + + + Battery is too low to start/keep recording. + + + SD card doesn't exist. + + + + + Event informing that the mass storage has been formatted. + + 1 if Mass Storage has been formatted, 0 otherwise. + + + + + Video settings. + + Set video automatic recording state. + + 0: disabled + 1: enabled + + + + Set the electric frequency (Anti-flickering). + + Type of the electric frequency. + + Electric frequency of the country is 50hz. + + + Electric frequency of the country is 60hz. + + + + + Set video streaming and recording resolution. + + Video resolution type. + + 16/9 VGA streaming (640 x 360). + + + HD streaming (1280 x 720). + + + + + + Video settings state. + + Event informing about the video automatic recording status. + + 0: disabled + 1: enabled + + + + Event informing about the electric frequency (Anti-flickering). + + Type of the electric frequency. + + Electric frequency of the country is 50hz. + + + Electric frequency of the country is 60hz. + + + + + Event informing about the streaming resolution. + + Video resolution type. + + 16/9 VGA streaming (640 x 360). + + + HD streaming (1280 x 720). + + + + + + Minicam related commands. + + Take picture. + + + Start/Stop video recording. + + Command to record video + + Stop the video recording. + + + Start the video recording. + + + + + Format mass storage. + + + + State changes related to the remote controller. + + State of the connection to the remote controller changed. + + New connection state. + 0=disconnected + 1=connected + + + + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/wifi.xml b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/wifi.xml new file mode 100644 index 0000000..9383394 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/commandsandsensors/wifi.xml @@ -0,0 +1,267 @@ + + + + All commands/events related to the Wifi + + + The band : 2.4 Ghz or 5 Ghz + + 2.4 GHz band + + + 5 GHz band + + + + The wifi selection type available + + Auto selection on all channels + + + Auto selection 2.4ghz + + + Auto selection 5 ghz + + + manual selection + + + + The type of wifi security (open, wpa2) + + Wifi is not protected by any security (default) + + + Wifi is protected by wpa2 + + + + Type of the key sent + + Key is plain text, not encrypted + + + + Type of environment + + indoor environment + + + outdoor environment + + + + Type of country selection + + Manual selection. + + + Automatic selection. + + + + + + + + + + + + SSID of the AP + + + RSSI of the AP. + + + + + Channel of the AP + + + + + + + + + + + + + The channel number + + + + + + + + + + + The channel you want to select. Used only when type is manual. + + + + + + + + The channel of the drone's access point + + + + + + + The key to secure the network. Not used if type is open + + + + + + + The key to secure the network. Not used if type is open + + + + + + + + Country code with ISO 3166 format. Not used if automatic is 1. + + + + + + + Country code with ISO 3166 format, empty string means unknown country. + + + + + + + + + + 1 if it uses outdoor wifi settings, 0 otherwise + + + + + + Rssi on the connected wifi network. Rssi values are generally between -30 and -120dBm. The nearest of 0 is the better. + + + + + + List of country code in ISO 3166 format separated by ";" + + + + diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/images/.gitignore b/RPI Code/pyparrot_/pyparrot/pyparrot/images/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/images/__init__.py b/RPI Code/pyparrot_/pyparrot/pyparrot/images/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/networking/__init__.py b/RPI Code/pyparrot_/pyparrot/pyparrot/networking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RPI Code/pyparrot_/pyparrot/pyparrot/networking/bleConnection.py b/RPI Code/pyparrot_/pyparrot/pyparrot/networking/bleConnection.py new file mode 100644 index 0000000..99f562e --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/pyparrot/networking/bleConnection.py @@ -0,0 +1,590 @@ +from bluepy.btle import Peripheral, UUID, DefaultDelegate, BTLEException +from pyparrot.utils.colorPrint import color_print +import struct +import time +from pyparrot.commandsandsensors.DroneSensorParser import get_data_format_and_size +from datetime import datetime + +class MinidroneDelegate(DefaultDelegate): + """ + Handle BLE notififications + """ + def __init__(self, handle_map, minidrone, ble_connection): + DefaultDelegate.__init__(self) + self.handle_map = handle_map + self.minidrone = minidrone + self.ble_connection = ble_connection + color_print("initializing notification delegate", "INFO") + + def handleNotification(self, cHandle, data): + #print "handling notificiation from channel %d" % cHandle + #print "handle map is %s " % self.handle_map[cHandle] + #print "channel map is %s " % self.minidrone.characteristic_receive_uuids[self.handle_map[cHandle]] + #print "data is %s " % data + + channel = self.ble_connection.characteristic_receive_uuids[self.handle_map[cHandle]] + + (packet_type, packet_seq_num) = struct.unpack(' 0): + my_data = data[0:-1] + self.udp_data = json.loads(str(my_data)) + + # if the drone refuses the connection, return false + if (self.udp_data['status'] != 0): + return False + + print(self.udp_data) + self.udp_send_port = self.udp_data['c2d_port'] + print("c2d_port is %d" % self.udp_send_port) + finished = True + else: + num_try += 1 + + # cleanup + tcp_sock.close() + + return finished + + + def _create_udp_connection(self): + """ + Create the UDP connection + """ + self.udp_send_sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) + #self.udp_send_sock.connect((self.drone_ip, self.udp_send_port)) + + self.udp_receive_sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) + # don't use the connect, use bind instead + # learned from bybop code + # https://github.com/N-Bz/bybop/blob/8d4c569c8e66bd1f0fdd768851409ca4b86c4ecd/src/Bybop_NetworkAL.py + #self.udp_receive_sock.connect((self.drone_ip, self.udp_receive_port)) + self.udp_receive_sock.settimeout(5.0) + + #Some computers having connection refused error (error was some kind of that, I dont remember actually) + #These new setsockopt lines solving it (at least at my device) + self.udp_receive_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) + self.udp_send_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) + + self.udp_receive_sock.bind(('0.0.0.0', int(self.udp_receive_port))) + + + def _connect_listener_called(self, connection_info): + """ + Save the connection info and set the connected to be true. This si called within the listener + for the connection. + + :param connection_info: + :return: + """ + self.connection_info = connection_info + self.is_connected = True + + def disconnect(self): + """ + Disconnect cleanly from the sockets + """ + self.is_listening = False + + # Sleep for a moment to allow all socket activity to cease before closing + # This helps to avoids a Winsock error regarding a operations on a closed socket + self.smart_sleep(0.5) + + # then put the close in a try/except to catch any further winsock errors + # the errors seem to be mostly occurring on windows for some reason + try: + self.udp_receive_sock.close() + self.udp_send_sock.close() + except: + pass + + def safe_send(self, packet): + + packet_sent = False + #print "inside safe send" + + try_num = 0 + + while (not packet_sent and try_num < self.max_packet_retries): + try: + self.udp_send_sock.sendto(packet, (self.drone_ip, self.udp_send_port)) + packet_sent = True + except: + #print "resetting connection" + self.udp_send_sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) + #self.udp_send_sock.connect((self.drone_ip, self.udp_send_port)) + try_num += 1 + + + def send_command_packet_ack(self, packet, seq_id): + """ + Sends the actual packet on the ack channel. Internal function only. + + :param packet: packet constructed according to the command rules (variable size, constructed elsewhere) + :return: True if the command was sent and False otherwise + """ + try_num = 0 + self._set_command_received('SEND_WITH_ACK', False, seq_id) + while (try_num < self.max_packet_retries and not self._is_command_received('SEND_WITH_ACK', seq_id)): + color_print("sending packet on try %d", try_num) + self.safe_send(packet) + try_num += 1 + self.smart_sleep(0.5) + + return self._is_command_received('SEND_WITH_ACK', seq_id) + + def send_command_packet_noack(self, packet): + """ + Sends the actual packet on the No-ack channel. Internal function only. + + :param packet: packet constructed according to the command rules (variable size, constructed elsewhere) + :return: True if the command was sent and False otherwise + """ + try_num = 0 + color_print("sending packet on try %d", try_num) + self.safe_send(packet) + + def send_noparam_high_priority_command_packet(self, command_tuple): + """ + Send a no parameter command packet on the high priority channel + :param command_tuple: + :return: + """ + self.sequence_counter['SEND_HIGH_PRIORITY'] = (self.sequence_counter['SEND_HIGH_PRIORITY'] + 1) % 256 + + packet = struct.pack(" +# Jean Brouwers +# Geoff Salmon +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + +"""This module provides bindings for the LibVLC public API, see +U{http://wiki.videolan.org/LibVLC}. + +You can find the documentation and a README file with some examples +at U{http://www.olivieraubert.net/vlc/python-ctypes/}. + +Basically, the most important class is L{Instance}, which is used +to create a libvlc instance. From this instance, you then create +L{MediaPlayer} and L{MediaListPlayer} instances. + +Alternatively, you may create instances of the L{MediaPlayer} and +L{MediaListPlayer} class directly and an instance of L{Instance} +will be implicitly created. The latter can be obtained using the +C{get_instance} method of L{MediaPlayer} and L{MediaListPlayer}. +""" + +import ctypes +from ctypes.util import find_library +import os +import sys +import functools + +# Used by EventManager in override.py +from inspect import getargspec + +import logging +logger = logging.getLogger(__name__) + +__version__ = "3.0.0102" +__libvlc_version__ = "3.0.0" +__generator_version__ = "1.2" +build_date = "Mon Feb 19 18:13:20 2018 3.0.0" + +# The libvlc doc states that filenames are expected to be in UTF8, do +# not rely on sys.getfilesystemencoding() which will be confused, +# esp. on windows. +DEFAULT_ENCODING = 'utf-8' + +if sys.version_info[0] > 2: + str = str + unicode = str + bytes = bytes + basestring = (str, bytes) + PYTHON3 = True + def str_to_bytes(s): + """Translate string or bytes to bytes. + """ + if isinstance(s, str): + return bytes(s, DEFAULT_ENCODING) + else: + return s + + def bytes_to_str(b): + """Translate bytes to string. + """ + if isinstance(b, bytes): + return b.decode(DEFAULT_ENCODING) + else: + return b +else: + str = str + unicode = unicode + bytes = str + basestring = basestring + PYTHON3 = False + def str_to_bytes(s): + """Translate string or bytes to bytes. + """ + if isinstance(s, unicode): + return s.encode(DEFAULT_ENCODING) + else: + return s + + def bytes_to_str(b): + """Translate bytes to unicode string. + """ + if isinstance(b, str): + return unicode(b, DEFAULT_ENCODING) + else: + return b + +# Internal guard to prevent internal classes to be directly +# instanciated. +_internal_guard = object() + +def find_lib(): + dll = None + plugin_path = os.environ.get('PYTHON_VLC_MODULE_PATH', None) + if 'PYTHON_VLC_LIB_PATH' in os.environ: + try: + dll = ctypes.CDLL(os.environ['PYTHON_VLC_LIB_PATH']) + except OSError: + logger.error("Cannot load lib specified by PYTHON_VLC_LIB_PATH env. variable") + sys.exit(1) + if plugin_path and not os.path.isdir(plugin_path): + logger.error("Invalid PYTHON_VLC_MODULE_PATH specified. Please fix.") + sys.exit(1) + if dll is not None: + return dll, plugin_path + + if sys.platform.startswith('linux'): + p = find_library('vlc') + try: + dll = ctypes.CDLL(p) + except OSError: # may fail + dll = ctypes.CDLL('libvlc.so.5') + elif sys.platform.startswith('win'): + libname = 'libvlc.dll' + p = find_library(libname) + if p is None: + try: # some registry settings + # leaner than win32api, win32con + if PYTHON3: + import winreg as w + else: + import _winreg as w + for r in w.HKEY_LOCAL_MACHINE, w.HKEY_CURRENT_USER: + try: + r = w.OpenKey(r, 'Software\\VideoLAN\\VLC') + plugin_path, _ = w.QueryValueEx(r, 'InstallDir') + w.CloseKey(r) + break + except w.error: + pass + except ImportError: # no PyWin32 + pass + if plugin_path is None: + # try some standard locations. + programfiles = os.environ["ProgramFiles"] + homedir = os.environ["HOMEDRIVE"] + for p in ('{programfiles}\\VideoLan{libname}', '{homedir}:\\VideoLan{libname}', + '{programfiles}{libname}', '{homedir}:{libname}'): + p = p.format(homedir = homedir, + programfiles = programfiles, + libname = '\\VLC\\' + libname) + if os.path.exists(p): + plugin_path = os.path.dirname(p) + break + if plugin_path is not None: # try loading + p = os.getcwd() + os.chdir(plugin_path) + # if chdir failed, this will raise an exception + dll = ctypes.CDLL(libname) + # restore cwd after dll has been loaded + os.chdir(p) + else: # may fail + dll = ctypes.CDLL(libname) + else: + plugin_path = os.path.dirname(p) + dll = ctypes.CDLL(p) + + elif sys.platform.startswith('darwin'): + # FIXME: should find a means to configure path + d = '/Applications/VLC.app/Contents/MacOS/' + c = d + 'lib/libvlccore.dylib' + p = d + 'lib/libvlc.dylib' + if os.path.exists(p) and os.path.exists(c): + # pre-load libvlccore VLC 2.2.8+ + ctypes.CDLL(c) + dll = ctypes.CDLL(p) + for p in ('modules', 'plugins'): + p = d + p + if os.path.isdir(p): + plugin_path = p + break + else: # hope, some [DY]LD_LIBRARY_PATH is set... + # pre-load libvlccore VLC 2.2.8+ + ctypes.CDLL('libvlccore.dylib') + dll = ctypes.CDLL('libvlc.dylib') + + else: + raise NotImplementedError('%s: %s not supported' % (sys.argv[0], sys.platform)) + + return (dll, plugin_path) + +# plugin_path used on win32 and MacOS in override.py +dll, plugin_path = find_lib() + +class VLCException(Exception): + """Exception raised by libvlc methods. + """ + pass + +try: + _Ints = (int, long) +except NameError: # no long in Python 3+ + _Ints = int +_Seqs = (list, tuple) + +# Used for handling *event_manager() methods. +class memoize_parameterless(object): + """Decorator. Caches a parameterless method's return value each time it is called. + + If called later with the same arguments, the cached value is returned + (not reevaluated). + Adapted from https://wiki.python.org/moin/PythonDecoratorLibrary + """ + def __init__(self, func): + self.func = func + self._cache = {} + + def __call__(self, obj): + try: + return self._cache[obj] + except KeyError: + v = self._cache[obj] = self.func(obj) + return v + + def __repr__(self): + """Return the function's docstring. + """ + return self.func.__doc__ + + def __get__(self, obj, objtype): + """Support instance methods. + """ + return functools.partial(self.__call__, obj) + +# Default instance. It is used to instanciate classes directly in the +# OO-wrapper. +_default_instance = None + +def get_default_instance(): + """Return the default VLC.Instance. + """ + global _default_instance + if _default_instance is None: + _default_instance = Instance() + return _default_instance + +_Cfunctions = {} # from LibVLC __version__ +_Globals = globals() # sys.modules[__name__].__dict__ + +def _Cfunction(name, flags, errcheck, *types): + """(INTERNAL) New ctypes function binding. + """ + if hasattr(dll, name) and name in _Globals: + p = ctypes.CFUNCTYPE(*types) + f = p((name, dll), flags) + if errcheck is not None: + f.errcheck = errcheck + # replace the Python function + # in this module, but only when + # running as python -O or -OO + if __debug__: + _Cfunctions[name] = f + else: + _Globals[name] = f + return f + raise NameError('no function %r' % (name,)) + +def _Cobject(cls, ctype): + """(INTERNAL) New instance from ctypes. + """ + o = object.__new__(cls) + o._as_parameter_ = ctype + return o + +def _Constructor(cls, ptr=_internal_guard): + """(INTERNAL) New wrapper from ctypes. + """ + if ptr == _internal_guard: + raise VLCException("(INTERNAL) ctypes class. You should get references for this class through methods of the LibVLC API.") + if ptr is None or ptr == 0: + return None + return _Cobject(cls, ctypes.c_void_p(ptr)) + +class _Cstruct(ctypes.Structure): + """(INTERNAL) Base class for ctypes structures. + """ + _fields_ = [] # list of 2-tuples ('name', ctyptes.) + + def __str__(self): + l = [' %s:\t%s' % (n, getattr(self, n)) for n, _ in self._fields_] + return '\n'.join([self.__class__.__name__] + l) + + def __repr__(self): + return '%s.%s' % (self.__class__.__module__, self) + +class _Ctype(object): + """(INTERNAL) Base class for ctypes. + """ + @staticmethod + def from_param(this): # not self + """(INTERNAL) ctypes parameter conversion method. + """ + if this is None: + return None + return this._as_parameter_ + +class ListPOINTER(object): + """Just like a POINTER but accept a list of ctype as an argument. + """ + def __init__(self, etype): + self.etype = etype + + def from_param(self, param): + if isinstance(param, _Seqs): + return (self.etype * len(param))(*param) + else: + return ctypes.POINTER(param) + +# errcheck functions for some native functions. +def string_result(result, func, arguments): + """Errcheck function. Returns a string and frees the original pointer. + + It assumes the result is a char *. + """ + if result: + # make a python string copy + s = bytes_to_str(ctypes.string_at(result)) + # free original string ptr + libvlc_free(result) + return s + return None + +def class_result(classname): + """Errcheck function. Returns a function that creates the specified class. + """ + def wrap_errcheck(result, func, arguments): + if result is None: + return None + return classname(result) + return wrap_errcheck + +# Wrapper for the opaque struct libvlc_log_t +class Log(ctypes.Structure): + pass +Log_ptr = ctypes.POINTER(Log) + +# FILE* ctypes wrapper, copied from +# http://svn.python.org/projects/ctypes/trunk/ctypeslib/ctypeslib/contrib/pythonhdr.py +class FILE(ctypes.Structure): + pass +FILE_ptr = ctypes.POINTER(FILE) + +if PYTHON3: + PyFile_FromFd = ctypes.pythonapi.PyFile_FromFd + PyFile_FromFd.restype = ctypes.py_object + PyFile_FromFd.argtypes = [ctypes.c_int, + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_int, + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_int ] + + PyFile_AsFd = ctypes.pythonapi.PyObject_AsFileDescriptor + PyFile_AsFd.restype = ctypes.c_int + PyFile_AsFd.argtypes = [ctypes.py_object] +else: + PyFile_FromFile = ctypes.pythonapi.PyFile_FromFile + PyFile_FromFile.restype = ctypes.py_object + PyFile_FromFile.argtypes = [FILE_ptr, + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.CFUNCTYPE(ctypes.c_int, FILE_ptr)] + + PyFile_AsFile = ctypes.pythonapi.PyFile_AsFile + PyFile_AsFile.restype = FILE_ptr + PyFile_AsFile.argtypes = [ctypes.py_object] + + # Generated enum types # + +class _Enum(ctypes.c_uint): + '''(INTERNAL) Base class + ''' + _enum_names_ = {} + + def __str__(self): + n = self._enum_names_.get(self.value, '') or ('FIXME_(%r)' % (self.value,)) + return '.'.join((self.__class__.__name__, n)) + + def __hash__(self): + return self.value + + def __repr__(self): + return '.'.join((self.__class__.__module__, self.__str__())) + + def __eq__(self, other): + return ( (isinstance(other, _Enum) and self.value == other.value) + or (isinstance(other, _Ints) and self.value == other) ) + + def __ne__(self, other): + return not self.__eq__(other) + +class LogLevel(_Enum): + '''Logging messages level. +\note future libvlc versions may define new levels. + ''' + _enum_names_ = { + 0: 'DEBUG', + 2: 'NOTICE', + 3: 'WARNING', + 4: 'ERROR', + } +LogLevel.DEBUG = LogLevel(0) +LogLevel.ERROR = LogLevel(4) +LogLevel.NOTICE = LogLevel(2) +LogLevel.WARNING = LogLevel(3) + +class MediaDiscovererCategory(_Enum): + '''Category of a media discoverer +See libvlc_media_discoverer_list_get(). + ''' + _enum_names_ = { + 0: 'devices', + 1: 'lan', + 2: 'podcasts', + 3: 'localdirs', + } +MediaDiscovererCategory.devices = MediaDiscovererCategory(0) +MediaDiscovererCategory.lan = MediaDiscovererCategory(1) +MediaDiscovererCategory.localdirs = MediaDiscovererCategory(3) +MediaDiscovererCategory.podcasts = MediaDiscovererCategory(2) + +class DialogQuestionType(_Enum): + '''@defgroup libvlc_dialog libvlc dialog +@ingroup libvlc +@{ +@file +libvlc dialog external api. + ''' + _enum_names_ = { + 0: 'NORMAL', + 1: 'WARNING', + 2: 'CRITICAL', + } +DialogQuestionType.CRITICAL = DialogQuestionType(2) +DialogQuestionType.NORMAL = DialogQuestionType(0) +DialogQuestionType.WARNING = DialogQuestionType(1) + +class EventType(_Enum): + '''Event types. + ''' + _enum_names_ = { + 0: 'MediaMetaChanged', + 1: 'MediaSubItemAdded', + 2: 'MediaDurationChanged', + 3: 'MediaParsedChanged', + 4: 'MediaFreed', + 5: 'MediaStateChanged', + 6: 'MediaSubItemTreeAdded', + 0x100: 'MediaPlayerMediaChanged', + 257: 'MediaPlayerNothingSpecial', + 258: 'MediaPlayerOpening', + 259: 'MediaPlayerBuffering', + 260: 'MediaPlayerPlaying', + 261: 'MediaPlayerPaused', + 262: 'MediaPlayerStopped', + 263: 'MediaPlayerForward', + 264: 'MediaPlayerBackward', + 265: 'MediaPlayerEndReached', + 266: 'MediaPlayerEncounteredError', + 267: 'MediaPlayerTimeChanged', + 268: 'MediaPlayerPositionChanged', + 269: 'MediaPlayerSeekableChanged', + 270: 'MediaPlayerPausableChanged', + 271: 'MediaPlayerTitleChanged', + 272: 'MediaPlayerSnapshotTaken', + 273: 'MediaPlayerLengthChanged', + 274: 'MediaPlayerVout', + 275: 'MediaPlayerScrambledChanged', + 276: 'MediaPlayerESAdded', + 277: 'MediaPlayerESDeleted', + 278: 'MediaPlayerESSelected', + 279: 'MediaPlayerCorked', + 280: 'MediaPlayerUncorked', + 281: 'MediaPlayerMuted', + 282: 'MediaPlayerUnmuted', + 283: 'MediaPlayerAudioVolume', + 284: 'MediaPlayerAudioDevice', + 285: 'MediaPlayerChapterChanged', + 0x200: 'MediaListItemAdded', + 513: 'MediaListWillAddItem', + 514: 'MediaListItemDeleted', + 515: 'MediaListWillDeleteItem', + 516: 'MediaListEndReached', + 0x300: 'MediaListViewItemAdded', + 769: 'MediaListViewWillAddItem', + 770: 'MediaListViewItemDeleted', + 771: 'MediaListViewWillDeleteItem', + 0x400: 'MediaListPlayerPlayed', + 1025: 'MediaListPlayerNextItemSet', + 1026: 'MediaListPlayerStopped', + 0x500: 'MediaDiscovererStarted', + 1281: 'MediaDiscovererEnded', + 1282: 'RendererDiscovererItemAdded', + 1283: 'RendererDiscovererItemDeleted', + 0x600: 'VlmMediaAdded', + 1537: 'VlmMediaRemoved', + 1538: 'VlmMediaChanged', + 1539: 'VlmMediaInstanceStarted', + 1540: 'VlmMediaInstanceStopped', + 1541: 'VlmMediaInstanceStatusInit', + 1542: 'VlmMediaInstanceStatusOpening', + 1543: 'VlmMediaInstanceStatusPlaying', + 1544: 'VlmMediaInstanceStatusPause', + 1545: 'VlmMediaInstanceStatusEnd', + 1546: 'VlmMediaInstanceStatusError', + } +EventType.MediaDiscovererEnded = EventType(1281) +EventType.MediaDiscovererStarted = EventType(0x500) +EventType.MediaDurationChanged = EventType(2) +EventType.MediaFreed = EventType(4) +EventType.MediaListEndReached = EventType(516) +EventType.MediaListItemAdded = EventType(0x200) +EventType.MediaListItemDeleted = EventType(514) +EventType.MediaListPlayerNextItemSet = EventType(1025) +EventType.MediaListPlayerPlayed = EventType(0x400) +EventType.MediaListPlayerStopped = EventType(1026) +EventType.MediaListViewItemAdded = EventType(0x300) +EventType.MediaListViewItemDeleted = EventType(770) +EventType.MediaListViewWillAddItem = EventType(769) +EventType.MediaListViewWillDeleteItem = EventType(771) +EventType.MediaListWillAddItem = EventType(513) +EventType.MediaListWillDeleteItem = EventType(515) +EventType.MediaMetaChanged = EventType(0) +EventType.MediaParsedChanged = EventType(3) +EventType.MediaPlayerAudioDevice = EventType(284) +EventType.MediaPlayerAudioVolume = EventType(283) +EventType.MediaPlayerBackward = EventType(264) +EventType.MediaPlayerBuffering = EventType(259) +EventType.MediaPlayerChapterChanged = EventType(285) +EventType.MediaPlayerCorked = EventType(279) +EventType.MediaPlayerESAdded = EventType(276) +EventType.MediaPlayerESDeleted = EventType(277) +EventType.MediaPlayerESSelected = EventType(278) +EventType.MediaPlayerEncounteredError = EventType(266) +EventType.MediaPlayerEndReached = EventType(265) +EventType.MediaPlayerForward = EventType(263) +EventType.MediaPlayerLengthChanged = EventType(273) +EventType.MediaPlayerMediaChanged = EventType(0x100) +EventType.MediaPlayerMuted = EventType(281) +EventType.MediaPlayerNothingSpecial = EventType(257) +EventType.MediaPlayerOpening = EventType(258) +EventType.MediaPlayerPausableChanged = EventType(270) +EventType.MediaPlayerPaused = EventType(261) +EventType.MediaPlayerPlaying = EventType(260) +EventType.MediaPlayerPositionChanged = EventType(268) +EventType.MediaPlayerScrambledChanged = EventType(275) +EventType.MediaPlayerSeekableChanged = EventType(269) +EventType.MediaPlayerSnapshotTaken = EventType(272) +EventType.MediaPlayerStopped = EventType(262) +EventType.MediaPlayerTimeChanged = EventType(267) +EventType.MediaPlayerTitleChanged = EventType(271) +EventType.MediaPlayerUncorked = EventType(280) +EventType.MediaPlayerUnmuted = EventType(282) +EventType.MediaPlayerVout = EventType(274) +EventType.MediaStateChanged = EventType(5) +EventType.MediaSubItemAdded = EventType(1) +EventType.MediaSubItemTreeAdded = EventType(6) +EventType.RendererDiscovererItemAdded = EventType(1282) +EventType.RendererDiscovererItemDeleted = EventType(1283) +EventType.VlmMediaAdded = EventType(0x600) +EventType.VlmMediaChanged = EventType(1538) +EventType.VlmMediaInstanceStarted = EventType(1539) +EventType.VlmMediaInstanceStatusEnd = EventType(1545) +EventType.VlmMediaInstanceStatusError = EventType(1546) +EventType.VlmMediaInstanceStatusInit = EventType(1541) +EventType.VlmMediaInstanceStatusOpening = EventType(1542) +EventType.VlmMediaInstanceStatusPause = EventType(1544) +EventType.VlmMediaInstanceStatusPlaying = EventType(1543) +EventType.VlmMediaInstanceStopped = EventType(1540) +EventType.VlmMediaRemoved = EventType(1537) + +class Meta(_Enum): + '''Meta data types. + ''' + _enum_names_ = { + 0: 'Title', + 1: 'Artist', + 2: 'Genre', + 3: 'Copyright', + 4: 'Album', + 5: 'TrackNumber', + 6: 'Description', + 7: 'Rating', + 8: 'Date', + 9: 'Setting', + 10: 'URL', + 11: 'Language', + 12: 'NowPlaying', + 13: 'Publisher', + 14: 'EncodedBy', + 15: 'ArtworkURL', + 16: 'TrackID', + 17: 'TrackTotal', + 18: 'Director', + 19: 'Season', + 20: 'Episode', + 21: 'ShowName', + 22: 'Actors', + 23: 'AlbumArtist', + 24: 'DiscNumber', + 25: 'DiscTotal', + } +Meta.Actors = Meta(22) +Meta.Album = Meta(4) +Meta.AlbumArtist = Meta(23) +Meta.Artist = Meta(1) +Meta.ArtworkURL = Meta(15) +Meta.Copyright = Meta(3) +Meta.Date = Meta(8) +Meta.Description = Meta(6) +Meta.Director = Meta(18) +Meta.DiscNumber = Meta(24) +Meta.DiscTotal = Meta(25) +Meta.EncodedBy = Meta(14) +Meta.Episode = Meta(20) +Meta.Genre = Meta(2) +Meta.Language = Meta(11) +Meta.NowPlaying = Meta(12) +Meta.Publisher = Meta(13) +Meta.Rating = Meta(7) +Meta.Season = Meta(19) +Meta.Setting = Meta(9) +Meta.ShowName = Meta(21) +Meta.Title = Meta(0) +Meta.TrackID = Meta(16) +Meta.TrackNumber = Meta(5) +Meta.TrackTotal = Meta(17) +Meta.URL = Meta(10) + +class State(_Enum): + '''Note the order of libvlc_state_t enum must match exactly the order of +See mediacontrol_playerstatus, See input_state_e enums, +and videolan.libvlc.state (at bindings/cil/src/media.cs). +expected states by web plugins are: +idle/close=0, opening=1, playing=3, paused=4, +stopping=5, ended=6, error=7. + ''' + _enum_names_ = { + 0: 'NothingSpecial', + 1: 'Opening', + 2: 'Buffering', + 3: 'Playing', + 4: 'Paused', + 5: 'Stopped', + 6: 'Ended', + 7: 'Error', + } +State.Buffering = State(2) +State.Ended = State(6) +State.Error = State(7) +State.NothingSpecial = State(0) +State.Opening = State(1) +State.Paused = State(4) +State.Playing = State(3) +State.Stopped = State(5) + +class TrackType(_Enum): + '''N/A + ''' + _enum_names_ = { + -1: 'unknown', + 0: 'audio', + 1: 'video', + 2: 'text', + } +TrackType.audio = TrackType(0) +TrackType.text = TrackType(2) +TrackType.unknown = TrackType(-1) +TrackType.video = TrackType(1) + +class VideoOrient(_Enum): + '''N/A + ''' + _enum_names_ = { + 0: 'left', + 1: 'right', + 2: 'left', + 3: 'right', + 4: 'top', + 5: 'bottom', + 6: 'top', + 7: 'bottom', + } +VideoOrient.bottom = VideoOrient(5) +VideoOrient.bottom = VideoOrient(7) +VideoOrient.left = VideoOrient(0) +VideoOrient.left = VideoOrient(2) +VideoOrient.right = VideoOrient(1) +VideoOrient.right = VideoOrient(3) +VideoOrient.top = VideoOrient(4) +VideoOrient.top = VideoOrient(6) + +class VideoProjection(_Enum): + '''N/A + ''' + _enum_names_ = { + 0: 'rectangular', + 1: 'equirectangular', + 0x100: 'standard', + } +VideoProjection.equirectangular = VideoProjection(1) +VideoProjection.rectangular = VideoProjection(0) +VideoProjection.standard = VideoProjection(0x100) + +class MediaType(_Enum): + '''Media type +See libvlc_media_get_type. + ''' + _enum_names_ = { + 0: 'unknown', + 1: 'file', + 2: 'directory', + 3: 'disc', + 4: 'stream', + 5: 'playlist', + } +MediaType.directory = MediaType(2) +MediaType.disc = MediaType(3) +MediaType.file = MediaType(1) +MediaType.playlist = MediaType(5) +MediaType.stream = MediaType(4) +MediaType.unknown = MediaType(0) + +class MediaParseFlag(_Enum): + '''Parse flags used by libvlc_media_parse_with_options() +See libvlc_media_parse_with_options. + ''' + _enum_names_ = { + 0x0: 'local', + 0x1: 'network', + 0x2: 'local', + 0x4: 'network', + 0x8: 'interact', + } +MediaParseFlag.interact = MediaParseFlag(0x8) +MediaParseFlag.local = MediaParseFlag(0x0) +MediaParseFlag.local = MediaParseFlag(0x2) +MediaParseFlag.network = MediaParseFlag(0x1) +MediaParseFlag.network = MediaParseFlag(0x4) + +class MediaParsedStatus(_Enum): + '''Parse status used sent by libvlc_media_parse_with_options() or returned by +libvlc_media_get_parsed_status() +See libvlc_media_parse_with_options +See libvlc_media_get_parsed_status. + ''' + _enum_names_ = { + 1: 'skipped', + 2: 'failed', + 3: 'timeout', + 4: 'done', + } +MediaParsedStatus.done = MediaParsedStatus(4) +MediaParsedStatus.failed = MediaParsedStatus(2) +MediaParsedStatus.skipped = MediaParsedStatus(1) +MediaParsedStatus.timeout = MediaParsedStatus(3) + +class MediaSlaveType(_Enum): + '''Type of a media slave: subtitle or audio. + ''' + _enum_names_ = { + 0: 'subtitle', + 1: 'audio', + } +MediaSlaveType.audio = MediaSlaveType(1) +MediaSlaveType.subtitle = MediaSlaveType(0) + +class VideoMarqueeOption(_Enum): + '''Marq options definition. + ''' + _enum_names_ = { + 0: 'Enable', + 1: 'Text', + 2: 'Color', + 3: 'Opacity', + 4: 'Position', + 5: 'Refresh', + 6: 'Size', + 7: 'Timeout', + 8: 'marquee_X', + 9: 'marquee_Y', + } +VideoMarqueeOption.Color = VideoMarqueeOption(2) +VideoMarqueeOption.Enable = VideoMarqueeOption(0) +VideoMarqueeOption.Opacity = VideoMarqueeOption(3) +VideoMarqueeOption.Position = VideoMarqueeOption(4) +VideoMarqueeOption.Refresh = VideoMarqueeOption(5) +VideoMarqueeOption.Size = VideoMarqueeOption(6) +VideoMarqueeOption.Text = VideoMarqueeOption(1) +VideoMarqueeOption.Timeout = VideoMarqueeOption(7) +VideoMarqueeOption.marquee_X = VideoMarqueeOption(8) +VideoMarqueeOption.marquee_Y = VideoMarqueeOption(9) + +class NavigateMode(_Enum): + '''Navigation mode. + ''' + _enum_names_ = { + 0: 'activate', + 1: 'up', + 2: 'down', + 3: 'left', + 4: 'right', + 5: 'popup', + } +NavigateMode.activate = NavigateMode(0) +NavigateMode.down = NavigateMode(2) +NavigateMode.left = NavigateMode(3) +NavigateMode.popup = NavigateMode(5) +NavigateMode.right = NavigateMode(4) +NavigateMode.up = NavigateMode(1) + +class Position(_Enum): + '''Enumeration of values used to set position (e.g. of video title). + ''' + _enum_names_ = { + -1: 'disable', + 0: 'center', + 1: 'left', + 2: 'right', + 3: 'top', + 4: 'left', + 5: 'right', + 6: 'bottom', + 7: 'left', + 8: 'right', + } +Position.bottom = Position(6) +Position.center = Position(0) +Position.disable = Position(-1) +Position.left = Position(1) +Position.left = Position(4) +Position.left = Position(7) +Position.right = Position(2) +Position.right = Position(5) +Position.right = Position(8) +Position.top = Position(3) + +class TeletextKey(_Enum): + '''Enumeration of teletext keys than can be passed via +libvlc_video_set_teletext(). + ''' + _enum_names_ = { + 7471104: 'red', + 6750208: 'green', + 7929856: 'yellow', + 6422528: 'blue', + 6881280: 'index', + } +TeletextKey.blue = TeletextKey(6422528) +TeletextKey.green = TeletextKey(6750208) +TeletextKey.index = TeletextKey(6881280) +TeletextKey.red = TeletextKey(7471104) +TeletextKey.yellow = TeletextKey(7929856) + +class VideoLogoOption(_Enum): + '''Option values for libvlc_video_{get,set}_logo_{int,string}. + ''' + _enum_names_ = { + 0: 'enable', + 1: 'file', + 2: 'logo_x', + 3: 'logo_y', + 4: 'delay', + 5: 'repeat', + 6: 'opacity', + 7: 'position', + } +VideoLogoOption.delay = VideoLogoOption(4) +VideoLogoOption.enable = VideoLogoOption(0) +VideoLogoOption.file = VideoLogoOption(1) +VideoLogoOption.logo_x = VideoLogoOption(2) +VideoLogoOption.logo_y = VideoLogoOption(3) +VideoLogoOption.opacity = VideoLogoOption(6) +VideoLogoOption.position = VideoLogoOption(7) +VideoLogoOption.repeat = VideoLogoOption(5) + +class VideoAdjustOption(_Enum): + '''Option values for libvlc_video_{get,set}_adjust_{int,float,bool}. + ''' + _enum_names_ = { + 0: 'Enable', + 1: 'Contrast', + 2: 'Brightness', + 3: 'Hue', + 4: 'Saturation', + 5: 'Gamma', + } +VideoAdjustOption.Brightness = VideoAdjustOption(2) +VideoAdjustOption.Contrast = VideoAdjustOption(1) +VideoAdjustOption.Enable = VideoAdjustOption(0) +VideoAdjustOption.Gamma = VideoAdjustOption(5) +VideoAdjustOption.Hue = VideoAdjustOption(3) +VideoAdjustOption.Saturation = VideoAdjustOption(4) + +class AudioOutputDeviceTypes(_Enum): + '''Audio device types. + ''' + _enum_names_ = { + -1: 'Error', + 1: 'Mono', + 2: 'Stereo', + 4: '_2F2R', + 5: '_3F2R', + 6: '_5_1', + 7: '_6_1', + 8: '_7_1', + 10: 'SPDIF', + } +AudioOutputDeviceTypes.Error = AudioOutputDeviceTypes(-1) +AudioOutputDeviceTypes.Mono = AudioOutputDeviceTypes(1) +AudioOutputDeviceTypes.SPDIF = AudioOutputDeviceTypes(10) +AudioOutputDeviceTypes.Stereo = AudioOutputDeviceTypes(2) +AudioOutputDeviceTypes._2F2R = AudioOutputDeviceTypes(4) +AudioOutputDeviceTypes._3F2R = AudioOutputDeviceTypes(5) +AudioOutputDeviceTypes._5_1 = AudioOutputDeviceTypes(6) +AudioOutputDeviceTypes._6_1 = AudioOutputDeviceTypes(7) +AudioOutputDeviceTypes._7_1 = AudioOutputDeviceTypes(8) + +class AudioOutputChannel(_Enum): + '''Audio channels. + ''' + _enum_names_ = { + -1: 'Error', + 1: 'Stereo', + 2: 'RStereo', + 3: 'Left', + 4: 'Right', + 5: 'Dolbys', + } +AudioOutputChannel.Dolbys = AudioOutputChannel(5) +AudioOutputChannel.Error = AudioOutputChannel(-1) +AudioOutputChannel.Left = AudioOutputChannel(3) +AudioOutputChannel.RStereo = AudioOutputChannel(2) +AudioOutputChannel.Right = AudioOutputChannel(4) +AudioOutputChannel.Stereo = AudioOutputChannel(1) + +class MediaPlayerRole(_Enum): + '''Media player roles. +\version libvlc 3.0.0 and later. +see \ref libvlc_media_player_set_role(). + ''' + _enum_names_ = { + 0: '_None', + 1: 'Music', + 2: 'Video', + 3: 'Communication', + 4: 'Game', + 5: 'Notification', + 6: 'Animation', + 7: 'Production', + 8: 'Accessibility', + 9: 'Test', + } +MediaPlayerRole.Accessibility = MediaPlayerRole(8) +MediaPlayerRole.Animation = MediaPlayerRole(6) +MediaPlayerRole.Communication = MediaPlayerRole(3) +MediaPlayerRole.Game = MediaPlayerRole(4) +MediaPlayerRole.Music = MediaPlayerRole(1) +MediaPlayerRole.Notification = MediaPlayerRole(5) +MediaPlayerRole.Production = MediaPlayerRole(7) +MediaPlayerRole.Test = MediaPlayerRole(9) +MediaPlayerRole.Video = MediaPlayerRole(2) +MediaPlayerRole._None = MediaPlayerRole(0) + +class PlaybackMode(_Enum): + '''Defines playback modes for playlist. + ''' + _enum_names_ = { + 0: 'default', + 1: 'loop', + 2: 'repeat', + } +PlaybackMode.default = PlaybackMode(0) +PlaybackMode.loop = PlaybackMode(1) +PlaybackMode.repeat = PlaybackMode(2) + +class Callback(ctypes.c_void_p): + """Callback function notification. + @param p_event: the event triggering the callback. + """ + pass +class LogCb(ctypes.c_void_p): + """Callback prototype for LibVLC log message handler. + @param data: data pointer as given to L{libvlc_log_set}(). + @param level: message level (@ref libvlc_log_level). + @param ctx: message context (meta-information about the message). + @param fmt: printf() format string (as defined by ISO C11). + @param args: variable argument list for the format @note Log message handlers B{must} be thread-safe. @warning The message context pointer, the format string parameters and the variable arguments are only valid until the callback returns. + """ + pass +class MediaOpenCb(ctypes.c_void_p): + """Callback prototype to open a custom bitstream input media. + The same media item can be opened multiple times. Each time, this callback + is invoked. It should allocate and initialize any instance-specific + resources, then store them in *datap. The instance resources can be freed + in the @ref libvlc_media_close_cb callback. + @param opaque: private pointer as passed to L{libvlc_media_new_callbacks}(). + @return: datap storage space for a private data pointer, sizep byte length of the bitstream or UINT64_MAX if unknown. + """ + pass +class MediaReadCb(ctypes.c_void_p): + """Callback prototype to read data from a custom bitstream input media. + @param opaque: private pointer as set by the @ref libvlc_media_open_cb callback. + @param buf: start address of the buffer to read data into. + @param len: bytes length of the buffer. + @return: strictly positive number of bytes read, 0 on end-of-stream, or -1 on non-recoverable error @note If no data is immediately available, then the callback should sleep. @warning The application is responsible for avoiding deadlock situations. In particular, the callback should return an error if playback is stopped; if it does not return, then L{libvlc_media_player_stop}() will never return. + """ + pass +class MediaSeekCb(ctypes.c_void_p): + """Callback prototype to seek a custom bitstream input media. + @param opaque: private pointer as set by the @ref libvlc_media_open_cb callback. + @param offset: absolute byte offset to seek to. + @return: 0 on success, -1 on error. + """ + pass +class MediaCloseCb(ctypes.c_void_p): + """Callback prototype to close a custom bitstream input media. + @param opaque: private pointer as set by the @ref libvlc_media_open_cb callback. + """ + pass +class VideoLockCb(ctypes.c_void_p): + """Callback prototype to allocate and lock a picture buffer. + Whenever a new video frame needs to be decoded, the lock callback is + invoked. Depending on the video chroma, one or three pixel planes of + adequate dimensions must be returned via the second parameter. Those + planes must be aligned on 32-bytes boundaries. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() [IN]. + @param planes: start address of the pixel planes (LibVLC allocates the array of void pointers, this callback must initialize the array) [OUT]. + @return: a private pointer for the display and unlock callbacks to identify the picture buffers. + """ + pass +class VideoUnlockCb(ctypes.c_void_p): + """Callback prototype to unlock a picture buffer. + When the video frame decoding is complete, the unlock callback is invoked. + This callback might not be needed at all. It is only an indication that the + application can now read the pixel values if it needs to. + @note: A picture buffer is unlocked after the picture is decoded, + but before the picture is displayed. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() [IN]. + @param picture: private pointer returned from the @ref libvlc_video_lock_cb callback [IN]. + @param planes: pixel planes as defined by the @ref libvlc_video_lock_cb callback (this parameter is only for convenience) [IN]. + """ + pass +class VideoDisplayCb(ctypes.c_void_p): + """Callback prototype to display a picture. + When the video frame needs to be shown, as determined by the media playback + clock, the display callback is invoked. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() [IN]. + @param picture: private pointer returned from the @ref libvlc_video_lock_cb callback [IN]. + """ + pass +class VideoFormatCb(ctypes.c_void_p): + """Callback prototype to configure picture buffers format. + This callback gets the format of the video as output by the video decoder + and the chain of video filters (if any). It can opt to change any parameter + as it needs. In that case, LibVLC will attempt to convert the video format + (rescaling and chroma conversion) but these operations can be CPU intensive. + @param opaque: pointer to the private pointer passed to L{libvlc_video_set_callbacks}() [IN/OUT]. + @param chroma: pointer to the 4 bytes video format identifier [IN/OUT]. + @param width: pointer to the pixel width [IN/OUT]. + @param height: pointer to the pixel height [IN/OUT]. + @param pitches: table of scanline pitches in bytes for each pixel plane (the table is allocated by LibVLC) [OUT]. + @return: lines table of scanlines count for each plane. + """ + pass +class VideoCleanupCb(ctypes.c_void_p): + """Callback prototype to configure picture buffers format. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() (and possibly modified by @ref libvlc_video_format_cb) [IN]. + """ + pass +class AudioPlayCb(ctypes.c_void_p): + """Callback prototype for audio playback. + The LibVLC media player decodes and post-processes the audio signal + asynchronously (in an internal thread). Whenever audio samples are ready + to be queued to the output, this callback is invoked. + The number of samples provided per invocation may depend on the file format, + the audio coding algorithm, the decoder plug-in, the post-processing + filters and timing. Application must not assume a certain number of samples. + The exact format of audio samples is determined by L{libvlc_audio_set_format}() + or L{libvlc_audio_set_format_callbacks}() as is the channels layout. + Note that the number of samples is per channel. For instance, if the audio + track sampling rate is 48000 Hz, then 1200 samples represent 25 milliseconds + of audio signal - regardless of the number of audio channels. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param samples: pointer to a table of audio samples to play back [IN]. + @param count: number of audio samples to play back. + @param pts: expected play time stamp (see libvlc_delay()). + """ + pass +class AudioPauseCb(ctypes.c_void_p): + """Callback prototype for audio pause. + LibVLC invokes this callback to pause audio playback. + @note: The pause callback is never called if the audio is already paused. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param pts: time stamp of the pause request (should be elapsed already). + """ + pass +class AudioResumeCb(ctypes.c_void_p): + """Callback prototype for audio resumption. + LibVLC invokes this callback to resume audio playback after it was + previously paused. + @note: The resume callback is never called if the audio is not paused. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param pts: time stamp of the resumption request (should be elapsed already). + """ + pass +class AudioFlushCb(ctypes.c_void_p): + """Callback prototype for audio buffer flush. + LibVLC invokes this callback if it needs to discard all pending buffers and + stop playback as soon as possible. This typically occurs when the media is + stopped. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + """ + pass +class AudioDrainCb(ctypes.c_void_p): + """Callback prototype for audio buffer drain. + LibVLC may invoke this callback when the decoded audio track is ending. + There will be no further decoded samples for the track, but playback should + nevertheless continue until all already pending buffers are rendered. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + """ + pass +class AudioSetVolumeCb(ctypes.c_void_p): + """Callback prototype for audio volume change. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param volume: software volume (1. = nominal, 0. = mute). + @param mute: muted flag. + """ + pass +class AudioSetupCb(ctypes.c_void_p): + """Callback prototype to setup the audio playback. + This is called when the media player needs to create a new audio output. + @param opaque: pointer to the data pointer passed to L{libvlc_audio_set_callbacks}() [IN/OUT]. + @param format: 4 bytes sample format [IN/OUT]. + @param rate: sample rate [IN/OUT]. + @param channels: channels count [IN/OUT]. + @return: 0 on success, anything else to skip audio playback. + """ + pass +class AudioCleanupCb(ctypes.c_void_p): + """Callback prototype for audio playback cleanup. + This is called when the media player no longer needs an audio output. + @param opaque: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + """ + pass +class CallbackDecorators(object): + "Class holding various method decorators for callback functions." + Callback = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) + Callback.__doc__ = '''Callback function notification. + @param p_event: the event triggering the callback. + ''' + LogCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, Log_ptr, ctypes.c_char_p, ctypes.c_void_p) + LogCb.__doc__ = '''Callback prototype for LibVLC log message handler. + @param data: data pointer as given to L{libvlc_log_set}(). + @param level: message level (@ref libvlc_log_level). + @param ctx: message context (meta-information about the message). + @param fmt: printf() format string (as defined by ISO C11). + @param args: variable argument list for the format @note Log message handlers B{must} be thread-safe. @warning The message context pointer, the format string parameters and the variable arguments are only valid until the callback returns. + ''' + MediaOpenCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_int), ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_uint64)) + MediaOpenCb.__doc__ = '''Callback prototype to open a custom bitstream input media. + The same media item can be opened multiple times. Each time, this callback + is invoked. It should allocate and initialize any instance-specific + resources, then store them in *datap. The instance resources can be freed + in the @ref libvlc_media_close_cb callback. + @param opaque: private pointer as passed to L{libvlc_media_new_callbacks}(). + @return: datap storage space for a private data pointer, sizep byte length of the bitstream or UINT64_MAX if unknown. + ''' + MediaReadCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_ssize_t), ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t) + MediaReadCb.__doc__ = '''Callback prototype to read data from a custom bitstream input media. + @param opaque: private pointer as set by the @ref libvlc_media_open_cb callback. + @param buf: start address of the buffer to read data into. + @param len: bytes length of the buffer. + @return: strictly positive number of bytes read, 0 on end-of-stream, or -1 on non-recoverable error @note If no data is immediately available, then the callback should sleep. @warning The application is responsible for avoiding deadlock situations. In particular, the callback should return an error if playback is stopped; if it does not return, then L{libvlc_media_player_stop}() will never return. + ''' + MediaSeekCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_int), ctypes.c_void_p, ctypes.c_uint64) + MediaSeekCb.__doc__ = '''Callback prototype to seek a custom bitstream input media. + @param opaque: private pointer as set by the @ref libvlc_media_open_cb callback. + @param offset: absolute byte offset to seek to. + @return: 0 on success, -1 on error. + ''' + MediaCloseCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) + MediaCloseCb.__doc__ = '''Callback prototype to close a custom bitstream input media. + @param opaque: private pointer as set by the @ref libvlc_media_open_cb callback. + ''' + VideoLockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p)) + VideoLockCb.__doc__ = '''Callback prototype to allocate and lock a picture buffer. + Whenever a new video frame needs to be decoded, the lock callback is + invoked. Depending on the video chroma, one or three pixel planes of + adequate dimensions must be returned via the second parameter. Those + planes must be aligned on 32-bytes boundaries. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() [IN]. + @param planes: start address of the pixel planes (LibVLC allocates the array of void pointers, this callback must initialize the array) [OUT]. + @return: a private pointer for the display and unlock callbacks to identify the picture buffers. + ''' + VideoUnlockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p)) + VideoUnlockCb.__doc__ = '''Callback prototype to unlock a picture buffer. + When the video frame decoding is complete, the unlock callback is invoked. + This callback might not be needed at all. It is only an indication that the + application can now read the pixel values if it needs to. + @note: A picture buffer is unlocked after the picture is decoded, + but before the picture is displayed. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() [IN]. + @param picture: private pointer returned from the @ref libvlc_video_lock_cb callback [IN]. + @param planes: pixel planes as defined by the @ref libvlc_video_lock_cb callback (this parameter is only for convenience) [IN]. + ''' + VideoDisplayCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) + VideoDisplayCb.__doc__ = '''Callback prototype to display a picture. + When the video frame needs to be shown, as determined by the media playback + clock, the display callback is invoked. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() [IN]. + @param picture: private pointer returned from the @ref libvlc_video_lock_cb callback [IN]. + ''' + VideoFormatCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) + VideoFormatCb.__doc__ = '''Callback prototype to configure picture buffers format. + This callback gets the format of the video as output by the video decoder + and the chain of video filters (if any). It can opt to change any parameter + as it needs. In that case, LibVLC will attempt to convert the video format + (rescaling and chroma conversion) but these operations can be CPU intensive. + @param opaque: pointer to the private pointer passed to L{libvlc_video_set_callbacks}() [IN/OUT]. + @param chroma: pointer to the 4 bytes video format identifier [IN/OUT]. + @param width: pointer to the pixel width [IN/OUT]. + @param height: pointer to the pixel height [IN/OUT]. + @param pitches: table of scanline pitches in bytes for each pixel plane (the table is allocated by LibVLC) [OUT]. + @return: lines table of scanlines count for each plane. + ''' + VideoCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) + VideoCleanupCb.__doc__ = '''Callback prototype to configure picture buffers format. + @param opaque: private pointer as passed to L{libvlc_video_set_callbacks}() (and possibly modified by @ref libvlc_video_format_cb) [IN]. + ''' + AudioPlayCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_int64) + AudioPlayCb.__doc__ = '''Callback prototype for audio playback. + The LibVLC media player decodes and post-processes the audio signal + asynchronously (in an internal thread). Whenever audio samples are ready + to be queued to the output, this callback is invoked. + The number of samples provided per invocation may depend on the file format, + the audio coding algorithm, the decoder plug-in, the post-processing + filters and timing. Application must not assume a certain number of samples. + The exact format of audio samples is determined by L{libvlc_audio_set_format}() + or L{libvlc_audio_set_format_callbacks}() as is the channels layout. + Note that the number of samples is per channel. For instance, if the audio + track sampling rate is 48000 Hz, then 1200 samples represent 25 milliseconds + of audio signal - regardless of the number of audio channels. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param samples: pointer to a table of audio samples to play back [IN]. + @param count: number of audio samples to play back. + @param pts: expected play time stamp (see libvlc_delay()). + ''' + AudioPauseCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64) + AudioPauseCb.__doc__ = '''Callback prototype for audio pause. + LibVLC invokes this callback to pause audio playback. + @note: The pause callback is never called if the audio is already paused. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param pts: time stamp of the pause request (should be elapsed already). + ''' + AudioResumeCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64) + AudioResumeCb.__doc__ = '''Callback prototype for audio resumption. + LibVLC invokes this callback to resume audio playback after it was + previously paused. + @note: The resume callback is never called if the audio is not paused. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param pts: time stamp of the resumption request (should be elapsed already). + ''' + AudioFlushCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64) + AudioFlushCb.__doc__ = '''Callback prototype for audio buffer flush. + LibVLC invokes this callback if it needs to discard all pending buffers and + stop playback as soon as possible. This typically occurs when the media is + stopped. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + ''' + AudioDrainCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) + AudioDrainCb.__doc__ = '''Callback prototype for audio buffer drain. + LibVLC may invoke this callback when the decoded audio track is ending. + There will be no further decoded samples for the track, but playback should + nevertheless continue until all already pending buffers are rendered. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + ''' + AudioSetVolumeCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_float, ctypes.c_bool) + AudioSetVolumeCb.__doc__ = '''Callback prototype for audio volume change. + @param data: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + @param volume: software volume (1. = nominal, 0. = mute). + @param mute: muted flag. + ''' + AudioSetupCb = ctypes.CFUNCTYPE(ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) + AudioSetupCb.__doc__ = '''Callback prototype to setup the audio playback. + This is called when the media player needs to create a new audio output. + @param opaque: pointer to the data pointer passed to L{libvlc_audio_set_callbacks}() [IN/OUT]. + @param format: 4 bytes sample format [IN/OUT]. + @param rate: sample rate [IN/OUT]. + @param channels: channels count [IN/OUT]. + @return: 0 on success, anything else to skip audio playback. + ''' + AudioCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) + AudioCleanupCb.__doc__ = '''Callback prototype for audio playback cleanup. + This is called when the media player no longer needs an audio output. + @param opaque: data pointer as passed to L{libvlc_audio_set_callbacks}() [IN]. + ''' +cb = CallbackDecorators + # End of generated enum types # + + # From libvlc_structures.h + +class AudioOutput(_Cstruct): + + def __str__(self): + return '%s(%s:%s)' % (self.__class__.__name__, self.name, self.description) + +AudioOutput._fields_ = [ # recursive struct + ('name', ctypes.c_char_p), + ('description', ctypes.c_char_p), + ('next', ctypes.POINTER(AudioOutput)), + ] + +class LogMessage(_Cstruct): + _fields_ = [ + ('size', ctypes.c_uint ), + ('severity', ctypes.c_int ), + ('type', ctypes.c_char_p), + ('name', ctypes.c_char_p), + ('header', ctypes.c_char_p), + ('message', ctypes.c_char_p), + ] + + def __init__(self): + super(LogMessage, self).__init__() + self.size = ctypes.sizeof(self) + + def __str__(self): + return '%s(%d:%s): %s' % (self.__class__.__name__, self.severity, self.type, self.message) + +class MediaEvent(_Cstruct): + _fields_ = [ + ('media_name', ctypes.c_char_p), + ('instance_name', ctypes.c_char_p), + ] + +class MediaStats(_Cstruct): + _fields_ = [ + ('read_bytes', ctypes.c_int ), + ('input_bitrate', ctypes.c_float), + ('demux_read_bytes', ctypes.c_int ), + ('demux_bitrate', ctypes.c_float), + ('demux_corrupted', ctypes.c_int ), + ('demux_discontinuity', ctypes.c_int ), + ('decoded_video', ctypes.c_int ), + ('decoded_audio', ctypes.c_int ), + ('displayed_pictures', ctypes.c_int ), + ('lost_pictures', ctypes.c_int ), + ('played_abuffers', ctypes.c_int ), + ('lost_abuffers', ctypes.c_int ), + ('sent_packets', ctypes.c_int ), + ('sent_bytes', ctypes.c_int ), + ('send_bitrate', ctypes.c_float), + ] + +class MediaTrackInfo(_Cstruct): + _fields_ = [ + ('codec', ctypes.c_uint32), + ('id', ctypes.c_int ), + ('type', TrackType ), + ('profile', ctypes.c_int ), + ('level', ctypes.c_int ), + ('channels_or_height', ctypes.c_uint ), + ('rate_or_width', ctypes.c_uint ), + ] + +class AudioTrack(_Cstruct): + _fields_ = [ + ('channels', ctypes.c_uint), + ('rate', ctypes.c_uint), + ] + +class VideoTrack(_Cstruct): + _fields_ = [ + ('height', ctypes.c_uint), + ('width', ctypes.c_uint), + ('sar_num', ctypes.c_uint), + ('sar_den', ctypes.c_uint), + ('frame_rate_num', ctypes.c_uint), + ('frame_rate_den', ctypes.c_uint), + ] + +class SubtitleTrack(_Cstruct): + _fields_ = [ + ('encoding', ctypes.c_char_p), + ] + +class MediaTrackTracks(ctypes.Union): + _fields_ = [ + ('audio', ctypes.POINTER(AudioTrack)), + ('video', ctypes.POINTER(VideoTrack)), + ('subtitle', ctypes.POINTER(SubtitleTrack)), + ] + +class MediaTrack(_Cstruct): + _anonymous_ = ("u",) + _fields_ = [ + ('codec', ctypes.c_uint32), + ('original_fourcc', ctypes.c_uint32), + ('id', ctypes.c_int ), + ('type', TrackType ), + ('profile', ctypes.c_int ), + ('level', ctypes.c_int ), + + ('u', MediaTrackTracks), + ('bitrate', ctypes.c_uint), + ('language', ctypes.c_char_p), + ('description', ctypes.c_char_p), + ] + +class PlaylistItem(_Cstruct): + _fields_ = [ + ('id', ctypes.c_int ), + ('uri', ctypes.c_char_p), + ('name', ctypes.c_char_p), + ] + + def __str__(self): + return '%s #%d %s (uri %s)' % (self.__class__.__name__, self.id, self.name, self.uri) + +class Position(object): + """Enum-like, immutable window position constants. + + See e.g. VideoMarqueeOption.Position. + """ + Center = 0 + Left = 1 + CenterLeft = 1 + Right = 2 + CenterRight = 2 + Top = 4 + TopCenter = 4 + TopLeft = 5 + TopRight = 6 + Bottom = 8 + BottomCenter = 8 + BottomLeft = 9 + BottomRight = 10 + def __init__(self, *unused): + raise TypeError('constants only') + def __setattr__(self, *unused): #PYCHOK expected + raise TypeError('immutable constants') + +class Rectangle(_Cstruct): + _fields_ = [ + ('top', ctypes.c_int), + ('left', ctypes.c_int), + ('bottom', ctypes.c_int), + ('right', ctypes.c_int), + ] + +class TrackDescription(_Cstruct): + + def __str__(self): + return '%s(%d:%s)' % (self.__class__.__name__, self.id, self.name) + +TrackDescription._fields_ = [ # recursive struct + ('id', ctypes.c_int ), + ('name', ctypes.c_char_p), + ('next', ctypes.POINTER(TrackDescription)), + ] + +def track_description_list(head): + """Convert a TrackDescription linked list to a Python list (and release the former). + """ + r = [] + if head: + item = head + while item: + item = item.contents + r.append((item.id, item.name)) + item = item.next + try: + libvlc_track_description_release(head) + except NameError: + libvlc_track_description_list_release(head) + + return r + +class EventUnion(ctypes.Union): + _fields_ = [ + ('meta_type', ctypes.c_uint ), + ('new_child', ctypes.c_uint ), + ('new_duration', ctypes.c_longlong), + ('new_status', ctypes.c_int ), + ('media', ctypes.c_void_p ), + ('new_state', ctypes.c_uint ), + # FIXME: Media instance + ('new_cache', ctypes.c_float ), + ('new_position', ctypes.c_float ), + ('new_time', ctypes.c_longlong), + ('new_title', ctypes.c_int ), + ('new_seekable', ctypes.c_longlong), + ('new_pausable', ctypes.c_longlong), + ('new_scrambled', ctypes.c_longlong), + ('new_count', ctypes.c_longlong), + # FIXME: Skipped MediaList and MediaListView... + ('filename', ctypes.c_char_p ), + ('new_length', ctypes.c_longlong), + ('media_event', MediaEvent ), + ] + +class Event(_Cstruct): + _fields_ = [ + ('type', EventType ), + ('object', ctypes.c_void_p), + ('u', EventUnion ), + ] + +class ModuleDescription(_Cstruct): + + def __str__(self): + return '%s %s (%s)' % (self.__class__.__name__, self.shortname, self.name) + +ModuleDescription._fields_ = [ # recursive struct + ('name', ctypes.c_char_p), + ('shortname', ctypes.c_char_p), + ('longname', ctypes.c_char_p), + ('help', ctypes.c_char_p), + ('next', ctypes.POINTER(ModuleDescription)), + ] + +def module_description_list(head): + """Convert a ModuleDescription linked list to a Python list (and release the former). + """ + r = [] + if head: + item = head + while item: + item = item.contents + r.append((item.name, item.shortname, item.longname, item.help)) + item = item.next + libvlc_module_description_list_release(head) + return r + +class AudioOutputDevice(_Cstruct): + + def __str__(self): + return '%s(%d:%s)' % (self.__class__.__name__, self.id, self.name) + +AudioOutputDevice._fields_ = [ # recursive struct + ('next', ctypes.POINTER(AudioOutputDevice)), + ('device', ctypes.c_char_p ), + ('description', ctypes.c_char_p), + ] + +class TitleDescription(_Cstruct): + _fields = [ + ('duration', ctypes.c_longlong), + ('name', ctypes.c_char_p), + ('menu', ctypes.c_bool), + ] + +class ChapterDescription(_Cstruct): + _fields = [ + ('time_offset', ctypes.c_longlong), + ('duration', ctypes.c_longlong), + ('name', ctypes.c_char_p), + ] + +class VideoViewpoint(_Cstruct): + _fields = [ + ('yaw', ctypes.c_float), + ('pitch', ctypes.c_float), + ('roll', ctypes.c_float), + ('field_of_view', ctypes.c_float), + ] + +# This struct depends on the MediaSlaveType enum that is defined only +# in > 2.2 +if 'MediaSlaveType' in locals(): + class MediaSlave(_Cstruct): + _fields = [ + ('psz_uri', ctypes.c_char_p), + ('i_type', MediaSlaveType), + ('i_priority', ctypes.c_uint) + ] + +class RDDescription(_Cstruct): + _fields = [ + ('name', ctypes.c_char_p), + ('longname', ctypes.c_char_p) + ] + +# End of header.py # +class EventManager(_Ctype): + '''Create an event manager with callback handler. + + This class interposes the registration and handling of + event notifications in order to (a) remove the need for + decorating each callback functions with the decorator + '@callbackmethod', (b) allow any number of positional + and/or keyword arguments to the callback (in addition + to the Event instance) and (c) to preserve the Python + objects such that the callback and argument objects + remain alive (i.e. are not garbage collected) until + B{after} the notification has been unregistered. + + @note: Only a single notification can be registered + for each event type in an EventManager instance. + + ''' + + _callback_handler = None + _callbacks = {} + + def __new__(cls, ptr=_internal_guard): + if ptr == _internal_guard: + raise VLCException("(INTERNAL) ctypes class.\nYou should get a reference to EventManager through the MediaPlayer.event_manager() method.") + return _Constructor(cls, ptr) + + def event_attach(self, eventtype, callback, *args, **kwds): + """Register an event notification. + + @param eventtype: the desired event type to be notified about. + @param callback: the function to call when the event occurs. + @param args: optional positional arguments for the callback. + @param kwds: optional keyword arguments for the callback. + @return: 0 on success, ENOMEM on error. + + @note: The callback function must have at least one argument, + an Event instance. Any other, optional positional and keyword + arguments are in B{addition} to the first one. + """ + if not isinstance(eventtype, EventType): + raise VLCException("%s required: %r" % ('EventType', eventtype)) + if not hasattr(callback, '__call__'): # callable() + raise VLCException("%s required: %r" % ('callable', callback)) + # check that the callback expects arguments + if not any(getargspec(callback)[:2]): # list(...) + raise VLCException("%s required: %r" % ('argument', callback)) + + if self._callback_handler is None: + _called_from_ctypes = ctypes.CFUNCTYPE(None, ctypes.POINTER(Event), ctypes.c_void_p) + @_called_from_ctypes + def _callback_handler(event, k): + """(INTERNAL) handle callback call from ctypes. + + @note: We cannot simply make this an EventManager + method since ctypes does not prepend self as the + first parameter, hence this closure. + """ + try: # retrieve Python callback and arguments + call, args, kwds = self._callbacks[k] + # deref event.contents to simplify callback code + call(event.contents, *args, **kwds) + except KeyError: # detached? + pass + self._callback_handler = _callback_handler + self._callbacks = {} + + k = eventtype.value + r = libvlc_event_attach(self, k, self._callback_handler, k) + if not r: + self._callbacks[k] = (callback, args, kwds) + return r + + def event_detach(self, eventtype): + """Unregister an event notification. + + @param eventtype: the event type notification to be removed. + """ + if not isinstance(eventtype, EventType): + raise VLCException("%s required: %r" % ('EventType', eventtype)) + + k = eventtype.value + if k in self._callbacks: + del self._callbacks[k] # remove, regardless of libvlc return value + libvlc_event_detach(self, k, self._callback_handler, k) + +class Instance(_Ctype): + '''Create a new Instance instance. + + It may take as parameter either: + - a string + - a list of strings as first parameters + - the parameters given as the constructor parameters (must be strings) + + ''' + + def __new__(cls, *args): + if len(args) == 1: + # Only 1 arg. It is either a C pointer, or an arg string, + # or a tuple. + i = args[0] + if isinstance(i, _Ints): + return _Constructor(cls, i) + elif isinstance(i, basestring): + args = i.strip().split() + elif isinstance(i, _Seqs): + args = list(i) + else: + raise VLCException('Instance %r' % (args,)) + else: + args = list(args) + + if not args: # no parameters passed + args = ['vlc'] + elif args[0] != 'vlc': + args.insert(0, 'vlc') + + if plugin_path is not None: + # set plugin_path if detected, win32 and MacOS, + # if the user did not specify it itself. + os.environ.setdefault('VLC_PLUGIN_PATH', plugin_path) + + if PYTHON3: + args = [ str_to_bytes(a) for a in args ] + return libvlc_new(len(args), args) + + def media_player_new(self, uri=None): + """Create a new MediaPlayer instance. + + @param uri: an optional URI to play in the player. + """ + p = libvlc_media_player_new(self) + if uri: + p.set_media(self.media_new(uri)) + p._instance = self + return p + + def media_list_player_new(self): + """Create a new MediaListPlayer instance. + """ + p = libvlc_media_list_player_new(self) + p._instance = self + return p + + def media_new(self, mrl, *options): + """Create a new Media instance. + + If mrl contains a colon (:) preceded by more than 1 letter, it + will be treated as a URL. Else, it will be considered as a + local path. If you need more control, directly use + media_new_location/media_new_path methods. + + Options can be specified as supplementary string parameters, + but note that many options cannot be set at the media level, + and rather at the Instance level. For instance, the marquee + filter must be specified when creating the vlc.Instance or + vlc.MediaPlayer. + + Alternatively, options can be added to the media using the + Media.add_options method (with the same limitation). + + @param options: optional media option=value strings + """ + if ':' in mrl and mrl.index(':') > 1: + # Assume it is a URL + m = libvlc_media_new_location(self, str_to_bytes(mrl)) + else: + # Else it should be a local path. + m = libvlc_media_new_path(self, str_to_bytes(os.path.normpath(mrl))) + for o in options: + libvlc_media_add_option(m, str_to_bytes(o)) + m._instance = self + return m + + def media_list_new(self, mrls=None): + """Create a new MediaList instance. + @param mrls: optional list of MRL strings + """ + l = libvlc_media_list_new(self) + # We should take the lock, but since we did not leak the + # reference, nobody else can access it. + if mrls: + for m in mrls: + l.add_media(m) + l._instance = self + return l + + def audio_output_enumerate_devices(self): + """Enumerate the defined audio output devices. + + @return: list of dicts {name:, description:, devices:} + """ + r = [] + head = libvlc_audio_output_list_get(self) + if head: + i = head + while i: + i = i.contents + d = [{'id': libvlc_audio_output_device_id (self, i.name, d), + 'longname': libvlc_audio_output_device_longname(self, i.name, d)} + for d in range(libvlc_audio_output_device_count (self, i.name))] + r.append({'name': i.name, 'description': i.description, 'devices': d}) + i = i.next + libvlc_audio_output_list_release(head) + return r + + def audio_filter_list_get(self): + """Returns a list of available audio filters. + + """ + return module_description_list(libvlc_audio_filter_list_get(self)) + + def video_filter_list_get(self): + """Returns a list of available video filters. + + """ + return module_description_list(libvlc_video_filter_list_get(self)) + + + + def release(self): + '''Decrement the reference count of a libvlc instance, and destroy it + if it reaches zero. + ''' + return libvlc_release(self) + + + def retain(self): + '''Increments the reference count of a libvlc instance. + The initial reference count is 1 after L{new}() returns. + ''' + return libvlc_retain(self) + + + def add_intf(self, name): + '''Try to start a user interface for the libvlc instance. + @param name: interface name, or None for default. + @return: 0 on success, -1 on error. + ''' + return libvlc_add_intf(self, str_to_bytes(name)) + + + def set_user_agent(self, name, http): + '''Sets the application name. LibVLC passes this as the user agent string + when a protocol requires it. + @param name: human-readable application name, e.g. "FooBar player 1.2.3". + @param http: HTTP User Agent, e.g. "FooBar/1.2.3 Python/2.6.0". + @version: LibVLC 1.1.1 or later. + ''' + return libvlc_set_user_agent(self, str_to_bytes(name), str_to_bytes(http)) + + + def set_app_id(self, id, version, icon): + '''Sets some meta-information about the application. + See also L{set_user_agent}(). + @param id: Java-style application identifier, e.g. "com.acme.foobar". + @param version: application version numbers, e.g. "1.2.3". + @param icon: application icon name, e.g. "foobar". + @version: LibVLC 2.1.0 or later. + ''' + return libvlc_set_app_id(self, str_to_bytes(id), str_to_bytes(version), str_to_bytes(icon)) + + + def log_unset(self): + '''Unsets the logging callback. + This function deregisters the logging callback for a LibVLC instance. + This is rarely needed as the callback is implicitly unset when the instance + is destroyed. + @note: This function will wait for any pending callbacks invocation to + complete (causing a deadlock if called from within the callback). + @version: LibVLC 2.1.0 or later. + ''' + return libvlc_log_unset(self) + + + def log_set(self, cb, data): + '''Sets the logging callback for a LibVLC instance. + This function is thread-safe: it will wait for any pending callbacks + invocation to complete. + @param data: opaque data pointer for the callback function @note Some log messages (especially debug) are emitted by LibVLC while is being initialized. These messages cannot be captured with this interface. @warning A deadlock may occur if this function is called from the callback. + @param p_instance: libvlc instance. + @version: LibVLC 2.1.0 or later. + ''' + return libvlc_log_set(self, cb, data) + + + def log_set_file(self, stream): + '''Sets up logging to a file. + @param stream: FILE pointer opened for writing (the FILE pointer must remain valid until L{log_unset}()). + @version: LibVLC 2.1.0 or later. + ''' + return libvlc_log_set_file(self, stream) + + + def media_discoverer_new(self, psz_name): + '''Create a media discoverer object by name. + After this object is created, you should attach to media_list events in + order to be notified of new items discovered. + You need to call L{media_discoverer_start}() in order to start the + discovery. + See L{media_discoverer_media_list} + See L{media_discoverer_event_manager} + See L{media_discoverer_start}. + @param psz_name: service name; use L{media_discoverer_list_get}() to get a list of the discoverer names available in this libVLC instance. + @return: media discover object or None in case of error. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_discoverer_new(self, str_to_bytes(psz_name)) + + + def media_discoverer_list_get(self, i_cat, ppp_services): + '''Get media discoverer services by category. + @param i_cat: category of services to fetch. + @param ppp_services: address to store an allocated array of media discoverer services (must be freed with L{media_discoverer_list_release}() by the caller) [OUT]. + @return: the number of media discoverer services (0 on error). + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_media_discoverer_list_get(self, i_cat, ppp_services) + + + def media_library_new(self): + '''Create an new Media Library object. + @return: a new object or None on error. + ''' + return libvlc_media_library_new(self) + + + def vlm_release(self): + '''Release the vlm instance related to the given L{Instance}. + ''' + return libvlc_vlm_release(self) + + + def vlm_add_broadcast(self, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop): + '''Add a broadcast, with one input. + @param psz_name: the name of the new broadcast. + @param psz_input: the input MRL. + @param psz_output: the output MRL (the parameter to the "sout" variable). + @param i_options: number of additional options. + @param ppsz_options: additional options. + @param b_enabled: boolean for enabling the new broadcast. + @param b_loop: Should this broadcast be played in loop ? + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_add_broadcast(self, str_to_bytes(psz_name), str_to_bytes(psz_input), str_to_bytes(psz_output), i_options, ppsz_options, b_enabled, b_loop) + + + def vlm_add_vod(self, psz_name, psz_input, i_options, ppsz_options, b_enabled, psz_mux): + '''Add a vod, with one input. + @param psz_name: the name of the new vod media. + @param psz_input: the input MRL. + @param i_options: number of additional options. + @param ppsz_options: additional options. + @param b_enabled: boolean for enabling the new vod. + @param psz_mux: the muxer of the vod media. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_add_vod(self, str_to_bytes(psz_name), str_to_bytes(psz_input), i_options, ppsz_options, b_enabled, str_to_bytes(psz_mux)) + + + def vlm_del_media(self, psz_name): + '''Delete a media (VOD or broadcast). + @param psz_name: the media to delete. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_del_media(self, str_to_bytes(psz_name)) + + + def vlm_set_enabled(self, psz_name, b_enabled): + '''Enable or disable a media (VOD or broadcast). + @param psz_name: the media to work on. + @param b_enabled: the new status. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_set_enabled(self, str_to_bytes(psz_name), b_enabled) + + + def vlm_set_output(self, psz_name, psz_output): + '''Set the output for a media. + @param psz_name: the media to work on. + @param psz_output: the output MRL (the parameter to the "sout" variable). + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_set_output(self, str_to_bytes(psz_name), str_to_bytes(psz_output)) + + + def vlm_set_input(self, psz_name, psz_input): + '''Set a media's input MRL. This will delete all existing inputs and + add the specified one. + @param psz_name: the media to work on. + @param psz_input: the input MRL. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_set_input(self, str_to_bytes(psz_name), str_to_bytes(psz_input)) + + + def vlm_add_input(self, psz_name, psz_input): + '''Add a media's input MRL. This will add the specified one. + @param psz_name: the media to work on. + @param psz_input: the input MRL. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_add_input(self, str_to_bytes(psz_name), str_to_bytes(psz_input)) + + + def vlm_set_loop(self, psz_name, b_loop): + '''Set a media's loop status. + @param psz_name: the media to work on. + @param b_loop: the new status. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_set_loop(self, str_to_bytes(psz_name), b_loop) + + + def vlm_set_mux(self, psz_name, psz_mux): + '''Set a media's vod muxer. + @param psz_name: the media to work on. + @param psz_mux: the new muxer. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_set_mux(self, str_to_bytes(psz_name), str_to_bytes(psz_mux)) + + + def vlm_change_media(self, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop): + '''Edit the parameters of a media. This will delete all existing inputs and + add the specified one. + @param psz_name: the name of the new broadcast. + @param psz_input: the input MRL. + @param psz_output: the output MRL (the parameter to the "sout" variable). + @param i_options: number of additional options. + @param ppsz_options: additional options. + @param b_enabled: boolean for enabling the new broadcast. + @param b_loop: Should this broadcast be played in loop ? + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_change_media(self, str_to_bytes(psz_name), str_to_bytes(psz_input), str_to_bytes(psz_output), i_options, ppsz_options, b_enabled, b_loop) + + + def vlm_play_media(self, psz_name): + '''Play the named broadcast. + @param psz_name: the name of the broadcast. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_play_media(self, str_to_bytes(psz_name)) + + + def vlm_stop_media(self, psz_name): + '''Stop the named broadcast. + @param psz_name: the name of the broadcast. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_stop_media(self, str_to_bytes(psz_name)) + + + def vlm_pause_media(self, psz_name): + '''Pause the named broadcast. + @param psz_name: the name of the broadcast. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_pause_media(self, str_to_bytes(psz_name)) + + + def vlm_seek_media(self, psz_name, f_percentage): + '''Seek in the named broadcast. + @param psz_name: the name of the broadcast. + @param f_percentage: the percentage to seek to. + @return: 0 on success, -1 on error. + ''' + return libvlc_vlm_seek_media(self, str_to_bytes(psz_name), f_percentage) + + + def vlm_show_media(self, psz_name): + '''Return information about the named media as a JSON + string representation. + This function is mainly intended for debugging use, + if you want programmatic access to the state of + a vlm_media_instance_t, please use the corresponding + libvlc_vlm_get_media_instance_xxx -functions. + Currently there are no such functions available for + vlm_media_t though. + @param psz_name: the name of the media, if the name is an empty string, all media is described. + @return: string with information about named media, or None on error. + ''' + return libvlc_vlm_show_media(self, str_to_bytes(psz_name)) + + + def vlm_get_media_instance_position(self, psz_name, i_instance): + '''Get vlm_media instance position by name or instance id. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: position as float or -1. on error. + ''' + return libvlc_vlm_get_media_instance_position(self, str_to_bytes(psz_name), i_instance) + + + def vlm_get_media_instance_time(self, psz_name, i_instance): + '''Get vlm_media instance time by name or instance id. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: time as integer or -1 on error. + ''' + return libvlc_vlm_get_media_instance_time(self, str_to_bytes(psz_name), i_instance) + + + def vlm_get_media_instance_length(self, psz_name, i_instance): + '''Get vlm_media instance length by name or instance id. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: length of media item or -1 on error. + ''' + return libvlc_vlm_get_media_instance_length(self, str_to_bytes(psz_name), i_instance) + + + def vlm_get_media_instance_rate(self, psz_name, i_instance): + '''Get vlm_media instance playback rate by name or instance id. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: playback rate or -1 on error. + ''' + return libvlc_vlm_get_media_instance_rate(self, str_to_bytes(psz_name), i_instance) + + + def vlm_get_media_instance_title(self, psz_name, i_instance): + '''Get vlm_media instance title number by name or instance id. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: title as number or -1 on error. + @bug: will always return 0. + ''' + return libvlc_vlm_get_media_instance_title(self, str_to_bytes(psz_name), i_instance) + + + def vlm_get_media_instance_chapter(self, psz_name, i_instance): + '''Get vlm_media instance chapter number by name or instance id. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: chapter as number or -1 on error. + @bug: will always return 0. + ''' + return libvlc_vlm_get_media_instance_chapter(self, str_to_bytes(psz_name), i_instance) + + + def vlm_get_media_instance_seekable(self, psz_name, i_instance): + '''Is libvlc instance seekable ? + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: 1 if seekable, 0 if not, -1 if media does not exist. + @bug: will always return 0. + ''' + return libvlc_vlm_get_media_instance_seekable(self, str_to_bytes(psz_name), i_instance) + + @memoize_parameterless + def vlm_get_event_manager(self): + '''Get libvlc_event_manager from a vlm media. + The p_event_manager is immutable, so you don't have to hold the lock. + @return: libvlc_event_manager. + ''' + return libvlc_vlm_get_event_manager(self) + + + def media_new_location(self, psz_mrl): + '''Create a media with a certain given media resource location, + for instance a valid URL. + @note: To refer to a local file with this function, + the file://... URI syntax B{must} be used (see IETF RFC3986). + We recommend using L{media_new_path}() instead when dealing with + local files. + See L{media_release}. + @param psz_mrl: the media location. + @return: the newly created media or None on error. + ''' + return libvlc_media_new_location(self, str_to_bytes(psz_mrl)) + + + def media_new_path(self, path): + '''Create a media for a certain file path. + See L{media_release}. + @param path: local filesystem path. + @return: the newly created media or None on error. + ''' + return libvlc_media_new_path(self, str_to_bytes(path)) + + + def media_new_fd(self, fd): + '''Create a media for an already open file descriptor. + The file descriptor shall be open for reading (or reading and writing). + Regular file descriptors, pipe read descriptors and character device + descriptors (including TTYs) are supported on all platforms. + Block device descriptors are supported where available. + Directory descriptors are supported on systems that provide fdopendir(). + Sockets are supported on all platforms where they are file descriptors, + i.e. all except Windows. + @note: This library will B{not} automatically close the file descriptor + under any circumstance. Nevertheless, a file descriptor can usually only be + rendered once in a media player. To render it a second time, the file + descriptor should probably be rewound to the beginning with lseek(). + See L{media_release}. + @param fd: open file descriptor. + @return: the newly created media or None on error. + @version: LibVLC 1.1.5 and later. + ''' + return libvlc_media_new_fd(self, fd) + + + def media_new_callbacks(self, open_cb, read_cb, seek_cb, close_cb, opaque): + '''Create a media with custom callbacks to read the data from. + @param open_cb: callback to open the custom bitstream input media. + @param read_cb: callback to read data (must not be None). + @param seek_cb: callback to seek, or None if seeking is not supported. + @param close_cb: callback to close the media, or None if unnecessary. + @param opaque: data pointer for the open callback. + @return: the newly created media or None on error @note If open_cb is None, the opaque pointer will be passed to read_cb, seek_cb and close_cb, and the stream size will be treated as unknown. @note The callbacks may be called asynchronously (from another thread). A single stream instance need not be reentrant. However the open_cb needs to be reentrant if the media is used by multiple player instances. @warning The callbacks may be used until all or any player instances that were supplied the media item are stopped. See L{media_release}. + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_media_new_callbacks(self, open_cb, read_cb, seek_cb, close_cb, opaque) + + + def media_new_as_node(self, psz_name): + '''Create a media as an empty node with a given name. + See L{media_release}. + @param psz_name: the name of the node. + @return: the new empty media or None on error. + ''' + return libvlc_media_new_as_node(self, str_to_bytes(psz_name)) + + + def renderer_discoverer_new(self, psz_name): + '''Create a renderer discoverer object by name + After this object is created, you should attach to events in order to be + notified of the discoverer events. + You need to call L{renderer_discoverer_start}() in order to start the + discovery. + See L{renderer_discoverer_event_manager}() + See L{renderer_discoverer_start}(). + @param psz_name: service name; use L{renderer_discoverer_list_get}() to get a list of the discoverer names available in this libVLC instance. + @return: media discover object or None in case of error. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_renderer_discoverer_new(self, str_to_bytes(psz_name)) + + + def renderer_discoverer_list_get(self, ppp_services): + '''Get media discoverer services + See libvlc_renderer_list_release(). + @param ppp_services: address to store an allocated array of renderer discoverer services (must be freed with libvlc_renderer_list_release() by the caller) [OUT]. + @return: the number of media discoverer services (0 on error). + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_renderer_discoverer_list_get(self, ppp_services) + + + def audio_output_device_count(self, psz_audio_output): + '''Backward compatibility stub. Do not use in new code. + \deprecated Use L{audio_output_device_list_get}() instead. + @return: always 0. + ''' + return libvlc_audio_output_device_count(self, str_to_bytes(psz_audio_output)) + + + def audio_output_device_longname(self, psz_output, i_device): + '''Backward compatibility stub. Do not use in new code. + \deprecated Use L{audio_output_device_list_get}() instead. + @return: always None. + ''' + return libvlc_audio_output_device_longname(self, str_to_bytes(psz_output), i_device) + + + def audio_output_device_id(self, psz_audio_output, i_device): + '''Backward compatibility stub. Do not use in new code. + \deprecated Use L{audio_output_device_list_get}() instead. + @return: always None. + ''' + return libvlc_audio_output_device_id(self, str_to_bytes(psz_audio_output), i_device) + + + def media_discoverer_new_from_name(self, psz_name): + '''\deprecated Use L{media_discoverer_new}() and L{media_discoverer_start}(). + ''' + return libvlc_media_discoverer_new_from_name(self, str_to_bytes(psz_name)) + + + def wait(self): + '''Waits until an interface causes the instance to exit. + You should start at least one interface first, using L{add_intf}(). + ''' + return libvlc_wait(self) + + + def get_log_verbosity(self): + '''Always returns minus one. + This function is only provided for backward compatibility. + @return: always -1. + ''' + return libvlc_get_log_verbosity(self) + + + def set_log_verbosity(self, level): + '''This function does nothing. + It is only provided for backward compatibility. + @param level: ignored. + ''' + return libvlc_set_log_verbosity(self, level) + + + def log_open(self): + '''This function does nothing useful. + It is only provided for backward compatibility. + @return: an unique pointer or None on error. + ''' + return libvlc_log_open(self) + + + def playlist_play(self, i_id, i_options, ppsz_options): + '''Start playing (if there is any item in the playlist). + Additionnal playlist item options can be specified for addition to the + item before it is played. + @param i_id: the item to play. If this is a negative number, the next item will be selected. Otherwise, the item with the given ID will be played. + @param i_options: the number of options to add to the item. + @param ppsz_options: the options to add to the item. + ''' + return libvlc_playlist_play(self, i_id, i_options, ppsz_options) + + + def audio_output_list_get(self): + '''Gets the list of available audio output modules. + @return: list of available audio outputs. It must be freed with In case of error, None is returned. + ''' + return libvlc_audio_output_list_get(self) + + + def audio_output_device_list_get(self, aout): + '''Gets a list of audio output devices for a given audio output module, + See L{audio_output_device_set}(). + @note: Not all audio outputs support this. In particular, an empty (None) + list of devices does B{not} imply that the specified audio output does + not work. + @note: The list might not be exhaustive. + @warning: Some audio output devices in the list might not actually work in + some circumstances. By default, it is recommended to not specify any + explicit audio device. + @param aout: audio output name (as returned by L{audio_output_list_get}()). + @return: A None-terminated linked list of potential audio output devices. It must be freed with L{audio_output_device_list_release}(). + @version: LibVLC 2.1.0 or later. + ''' + return libvlc_audio_output_device_list_get(self, str_to_bytes(aout)) + +class LogIterator(_Ctype): + '''Create a new VLC log iterator. + + ''' + + def __new__(cls, ptr=_internal_guard): + '''(INTERNAL) ctypes wrapper constructor. + ''' + return _Constructor(cls, ptr) + + def __iter__(self): + return self + + def next(self): + if self.has_next(): + b = LogMessage() + i = libvlc_log_iterator_next(self, b) + return i.contents + raise StopIteration + + def __next__(self): + return self.next() + + + + def free(self): + '''Frees memory allocated by L{log_get_iterator}(). + ''' + return libvlc_log_iterator_free(self) + + + def has_next(self): + '''Always returns zero. + This function is only provided for backward compatibility. + @return: always zero. + ''' + return libvlc_log_iterator_has_next(self) + +class Media(_Ctype): + '''Create a new Media instance. + + Usage: Media(MRL, *options) + + See vlc.Instance.media_new documentation for details. + + ''' + + def __new__(cls, *args): + if args: + i = args[0] + if isinstance(i, _Ints): + return _Constructor(cls, i) + if isinstance(i, Instance): + return i.media_new(*args[1:]) + + o = get_default_instance().media_new(*args) + return o + + def get_instance(self): + return getattr(self, '_instance', None) + + def add_options(self, *options): + """Add a list of options to the media. + + Options must be written without the double-dash. Warning: most + audio and video options, such as text renderer, have no + effects on an individual media. These options must be set at + the vlc.Instance or vlc.MediaPlayer instanciation. + + @param options: optional media option=value strings + """ + for o in options: + self.add_option(o) + + def tracks_get(self): + """Get media descriptor's elementary streams description + Note, you need to call L{parse}() or play the media at least once + before calling this function. + Not doing this will result in an empty array. + The result must be freed with L{tracks_release}. + @version: LibVLC 2.1.0 and later. + """ + mediaTrack_pp = ctypes.POINTER(MediaTrack)() + n = libvlc_media_tracks_get(self, ctypes.byref(mediaTrack_pp)) + info = ctypes.cast(mediaTrack_pp, ctypes.POINTER(ctypes.POINTER(MediaTrack) * n)) + try: + contents = info.contents + except ValueError: + # Media not parsed, no info. + return None + tracks = ( contents[i].contents for i in range(len(contents)) ) + # libvlc_media_tracks_release(mediaTrack_pp, n) + return tracks + + + + def add_option(self, psz_options): + '''Add an option to the media. + This option will be used to determine how the media_player will + read the media. This allows to use VLC's advanced + reading/streaming options on a per-media basis. + @note: The options are listed in 'vlc --long-help' from the command line, + e.g. "-sout-all". Keep in mind that available options and their semantics + vary across LibVLC versions and builds. + @warning: Not all options affects L{Media} objects: + Specifically, due to architectural issues most audio and video options, + such as text renderer options, have no effects on an individual media. + These options must be set through L{new}() instead. + @param psz_options: the options (as a string). + ''' + return libvlc_media_add_option(self, str_to_bytes(psz_options)) + + + def add_option_flag(self, psz_options, i_flags): + '''Add an option to the media with configurable flags. + This option will be used to determine how the media_player will + read the media. This allows to use VLC's advanced + reading/streaming options on a per-media basis. + The options are detailed in vlc --long-help, for instance + "--sout-all". Note that all options are not usable on medias: + specifically, due to architectural issues, video-related options + such as text renderer options cannot be set on a single media. They + must be set on the whole libvlc instance instead. + @param psz_options: the options (as a string). + @param i_flags: the flags for this option. + ''' + return libvlc_media_add_option_flag(self, str_to_bytes(psz_options), i_flags) + + + def retain(self): + '''Retain a reference to a media descriptor object (libvlc_media_t). Use + L{release}() to decrement the reference count of a + media descriptor object. + ''' + return libvlc_media_retain(self) + + + def release(self): + '''Decrement the reference count of a media descriptor object. If the + reference count is 0, then L{release}() will release the + media descriptor object. It will send out an libvlc_MediaFreed event + to all listeners. If the media descriptor object has been released it + should not be used again. + ''' + return libvlc_media_release(self) + + + def get_mrl(self): + '''Get the media resource locator (mrl) from a media descriptor object. + @return: string with mrl of media descriptor object. + ''' + return libvlc_media_get_mrl(self) + + + def duplicate(self): + '''Duplicate a media descriptor object. + ''' + return libvlc_media_duplicate(self) + + + def get_meta(self, e_meta): + '''Read the meta of the media. + If the media has not yet been parsed this will return None. + See L{parse} + See L{parse_with_options} + See libvlc_MediaMetaChanged. + @param e_meta: the meta to read. + @return: the media's meta. + ''' + return libvlc_media_get_meta(self, e_meta) + + + def set_meta(self, e_meta, psz_value): + '''Set the meta of the media (this function will not save the meta, call + L{save_meta} in order to save the meta). + @param e_meta: the meta to write. + @param psz_value: the media's meta. + ''' + return libvlc_media_set_meta(self, e_meta, str_to_bytes(psz_value)) + + + def save_meta(self): + '''Save the meta previously set. + @return: true if the write operation was successful. + ''' + return libvlc_media_save_meta(self) + + + def get_state(self): + '''Get current state of media descriptor object. Possible media states are + libvlc_NothingSpecial=0, libvlc_Opening, libvlc_Playing, libvlc_Paused, + libvlc_Stopped, libvlc_Ended, libvlc_Error. + See libvlc_state_t. + @return: state of media descriptor object. + ''' + return libvlc_media_get_state(self) + + + def get_stats(self, p_stats): + '''Get the current statistics about the media. + @param p_stats:: structure that contain the statistics about the media (this structure must be allocated by the caller). + @return: true if the statistics are available, false otherwise \libvlc_return_bool. + ''' + return libvlc_media_get_stats(self, p_stats) + + + def subitems(self): + '''Get subitems of media descriptor object. This will increment + the reference count of supplied media descriptor object. Use + L{list_release}() to decrement the reference counting. + @return: list of media descriptor subitems or None. + ''' + return libvlc_media_subitems(self) + + @memoize_parameterless + def event_manager(self): + '''Get event manager from media descriptor object. + NOTE: this function doesn't increment reference counting. + @return: event manager object. + ''' + return libvlc_media_event_manager(self) + + + def get_duration(self): + '''Get duration (in ms) of media descriptor object item. + @return: duration of media item or -1 on error. + ''' + return libvlc_media_get_duration(self) + + + def parse_with_options(self, parse_flag, timeout): + '''Parse the media asynchronously with options. + This fetches (local or network) art, meta data and/or tracks information. + This method is the extended version of L{parse_with_options}(). + To track when this is over you can listen to libvlc_MediaParsedChanged + event. However if this functions returns an error, you will not receive any + events. + It uses a flag to specify parse options (see libvlc_media_parse_flag_t). All + these flags can be combined. By default, media is parsed if it's a local + file. + @note: Parsing can be aborted with L{parse_stop}(). + See libvlc_MediaParsedChanged + See L{get_meta} + See L{tracks_get} + See L{get_parsed_status} + See libvlc_media_parse_flag_t. + @param parse_flag: parse options: + @param timeout: maximum time allowed to preparse the media. If -1, the default "preparse-timeout" option will be used as a timeout. If 0, it will wait indefinitely. If > 0, the timeout will be used (in milliseconds). + @return: -1 in case of error, 0 otherwise. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_parse_with_options(self, parse_flag, timeout) + + + def parse_stop(self): + '''Stop the parsing of the media + When the media parsing is stopped, the libvlc_MediaParsedChanged event will + be sent with the libvlc_media_parsed_status_timeout status. + See L{parse_with_options}. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_parse_stop(self) + + + def get_parsed_status(self): + '''Get Parsed status for media descriptor object. + See libvlc_MediaParsedChanged + See libvlc_media_parsed_status_t. + @return: a value of the libvlc_media_parsed_status_t enum. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_get_parsed_status(self) + + + def set_user_data(self, p_new_user_data): + '''Sets media descriptor's user_data. user_data is specialized data + accessed by the host application, VLC.framework uses it as a pointer to + an native object that references a L{Media} pointer. + @param p_new_user_data: pointer to user data. + ''' + return libvlc_media_set_user_data(self, p_new_user_data) + + + def get_user_data(self): + '''Get media descriptor's user_data. user_data is specialized data + accessed by the host application, VLC.framework uses it as a pointer to + an native object that references a L{Media} pointer. + ''' + return libvlc_media_get_user_data(self) + + + def get_type(self): + '''Get the media type of the media descriptor object. + @return: media type. + @version: LibVLC 3.0.0 and later. See libvlc_media_type_t. + ''' + return libvlc_media_get_type(self) + + + def slaves_add(self, i_type, i_priority, psz_uri): + '''Add a slave to the current media. + A slave is an external input source that may contains an additional subtitle + track (like a .srt) or an additional audio track (like a .ac3). + @note: This function must be called before the media is parsed (via + L{parse_with_options}()) or before the media is played (via + L{player_play}()). + @param i_type: subtitle or audio. + @param i_priority: from 0 (low priority) to 4 (high priority). + @param psz_uri: Uri of the slave (should contain a valid scheme). + @return: 0 on success, -1 on error. + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_media_slaves_add(self, i_type, i_priority, str_to_bytes(psz_uri)) + + + def slaves_clear(self): + '''Clear all slaves previously added by L{slaves_add}() or + internally. + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_media_slaves_clear(self) + + + def slaves_get(self, ppp_slaves): + '''Get a media descriptor's slave list + The list will contain slaves parsed by VLC or previously added by + L{slaves_add}(). The typical use case of this function is to save + a list of slave in a database for a later use. + @param ppp_slaves: address to store an allocated array of slaves (must be freed with L{slaves_release}()) [OUT]. + @return: the number of slaves (zero on error). + @version: LibVLC 3.0.0 and later. See L{slaves_add}. + ''' + return libvlc_media_slaves_get(self, ppp_slaves) + + + def parse(self): + '''Parse a media. + This fetches (local) art, meta data and tracks information. + The method is synchronous. + \deprecated This function could block indefinitely. + Use L{parse_with_options}() instead + See L{parse_with_options} + See L{get_meta} + See L{get_tracks_info}. + ''' + return libvlc_media_parse(self) + + + def parse_async(self): + '''Parse a media. + This fetches (local) art, meta data and tracks information. + The method is the asynchronous of L{parse}(). + To track when this is over you can listen to libvlc_MediaParsedChanged + event. However if the media was already parsed you will not receive this + event. + \deprecated You can't be sure to receive the libvlc_MediaParsedChanged + event (you can wait indefinitely for this event). + Use L{parse_with_options}() instead + See L{parse} + See libvlc_MediaParsedChanged + See L{get_meta} + See L{get_tracks_info}. + ''' + return libvlc_media_parse_async(self) + + + def is_parsed(self): + '''Return true is the media descriptor object is parsed + \deprecated This can return true in case of failure. + Use L{get_parsed_status}() instead + See libvlc_MediaParsedChanged. + @return: true if media object has been parsed otherwise it returns false \libvlc_return_bool. + ''' + return libvlc_media_is_parsed(self) + + + def get_tracks_info(self): + '''Get media descriptor's elementary streams description + Note, you need to call L{parse}() or play the media at least once + before calling this function. + Not doing this will result in an empty array. + \deprecated Use L{tracks_get}() instead. + @param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller) [OUT]. + @return: the number of Elementary Streams. + ''' + return libvlc_media_get_tracks_info(self) + + + def player_new_from_media(self): + '''Create a Media Player object from a Media. + @return: a new media player object, or None on error. + ''' + return libvlc_media_player_new_from_media(self) + +class MediaDiscoverer(_Ctype): + '''N/A + ''' + + def __new__(cls, ptr=_internal_guard): + '''(INTERNAL) ctypes wrapper constructor. + ''' + return _Constructor(cls, ptr) + + def start(self): + '''Start media discovery. + To stop it, call L{stop}() or + L{list_release}() directly. + See L{stop}. + @return: -1 in case of error, 0 otherwise. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_discoverer_start(self) + + + def stop(self): + '''Stop media discovery. + See L{start}. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_discoverer_stop(self) + + + def release(self): + '''Release media discover object. If the reference count reaches 0, then + the object will be released. + ''' + return libvlc_media_discoverer_release(self) + + + def media_list(self): + '''Get media service discover media list. + @return: list of media items. + ''' + return libvlc_media_discoverer_media_list(self) + + + def is_running(self): + '''Query if media service discover object is running. + @return: true if running, false if not \libvlc_return_bool. + ''' + return libvlc_media_discoverer_is_running(self) + + + def localized_name(self): + '''Get media service discover object its localized name. + \deprecated Useless, use L{list_get}() to get the + longname of the service discovery. + @return: localized name or None if the media_discoverer is not started. + ''' + return libvlc_media_discoverer_localized_name(self) + + @memoize_parameterless + def event_manager(self): + '''Get event manager from media service discover object. + \deprecated Useless, media_discoverer events are only triggered when calling + L{start}() and L{stop}(). + @return: event manager object. + ''' + return libvlc_media_discoverer_event_manager(self) + +class MediaLibrary(_Ctype): + '''N/A + ''' + + def __new__(cls, ptr=_internal_guard): + '''(INTERNAL) ctypes wrapper constructor. + ''' + return _Constructor(cls, ptr) + + def release(self): + '''Release media library object. This functions decrements the + reference count of the media library object. If it reaches 0, + then the object will be released. + ''' + return libvlc_media_library_release(self) + + + def retain(self): + '''Retain a reference to a media library object. This function will + increment the reference counting for this object. Use + L{release}() to decrement the reference count. + ''' + return libvlc_media_library_retain(self) + + + def load(self): + '''Load media library. + @return: 0 on success, -1 on error. + ''' + return libvlc_media_library_load(self) + + + def media_list(self): + '''Get media library subitems. + @return: media list subitems. + ''' + return libvlc_media_library_media_list(self) + +class MediaList(_Ctype): + '''Create a new MediaList instance. + + Usage: MediaList(list_of_MRLs) + + See vlc.Instance.media_list_new documentation for details. + + ''' + + def __new__(cls, *args): + if args: + i = args[0] + if isinstance(i, _Ints): + return _Constructor(cls, i) + if isinstance(i, Instance): + return i.media_list_new(*args[1:]) + + o = get_default_instance().media_list_new(*args) + return o + + def get_instance(self): + return getattr(self, '_instance', None) + + def add_media(self, mrl): + """Add media instance to media list. + + The L{lock} should be held upon entering this function. + @param mrl: a media instance or a MRL. + @return: 0 on success, -1 if the media list is read-only. + """ + if isinstance(mrl, basestring): + mrl = (self.get_instance() or get_default_instance()).media_new(mrl) + return libvlc_media_list_add_media(self, mrl) + + + + def release(self): + '''Release media list created with L{new}(). + ''' + return libvlc_media_list_release(self) + + + def retain(self): + '''Retain reference to a media list. + ''' + return libvlc_media_list_retain(self) + + + def set_media(self, p_md): + '''Associate media instance with this media list instance. + If another media instance was present it will be released. + The L{lock} should NOT be held upon entering this function. + @param p_md: media instance to add. + ''' + return libvlc_media_list_set_media(self, p_md) + + + def media(self): + '''Get media instance from this media list instance. This action will increase + the refcount on the media instance. + The L{lock} should NOT be held upon entering this function. + @return: media instance. + ''' + return libvlc_media_list_media(self) + + + def insert_media(self, p_md, i_pos): + '''Insert media instance in media list on a position + The L{lock} should be held upon entering this function. + @param p_md: a media instance. + @param i_pos: position in array where to insert. + @return: 0 on success, -1 if the media list is read-only. + ''' + return libvlc_media_list_insert_media(self, p_md, i_pos) + + + def remove_index(self, i_pos): + '''Remove media instance from media list on a position + The L{lock} should be held upon entering this function. + @param i_pos: position in array where to insert. + @return: 0 on success, -1 if the list is read-only or the item was not found. + ''' + return libvlc_media_list_remove_index(self, i_pos) + + + def count(self): + '''Get count on media list items + The L{lock} should be held upon entering this function. + @return: number of items in media list. + ''' + return libvlc_media_list_count(self) + + def __len__(self): + return libvlc_media_list_count(self) + + + def item_at_index(self, i_pos): + '''List media instance in media list at a position + The L{lock} should be held upon entering this function. + @param i_pos: position in array where to insert. + @return: media instance at position i_pos, or None if not found. In case of success, L{media_retain}() is called to increase the refcount on the media. + ''' + return libvlc_media_list_item_at_index(self, i_pos) + + def __getitem__(self, i): + return libvlc_media_list_item_at_index(self, i) + + def __iter__(self): + for i in range(len(self)): + yield self[i] + + + def index_of_item(self, p_md): + '''Find index position of List media instance in media list. + Warning: the function will return the first matched position. + The L{lock} should be held upon entering this function. + @param p_md: media instance. + @return: position of media instance or -1 if media not found. + ''' + return libvlc_media_list_index_of_item(self, p_md) + + + def is_readonly(self): + '''This indicates if this media list is read-only from a user point of view. + @return: 1 on readonly, 0 on readwrite \libvlc_return_bool. + ''' + return libvlc_media_list_is_readonly(self) + + + def lock(self): + '''Get lock on media list items. + ''' + return libvlc_media_list_lock(self) + + + def unlock(self): + '''Release lock on media list items + The L{lock} should be held upon entering this function. + ''' + return libvlc_media_list_unlock(self) + + @memoize_parameterless + def event_manager(self): + '''Get libvlc_event_manager from this media list instance. + The p_event_manager is immutable, so you don't have to hold the lock. + @return: libvlc_event_manager. + ''' + return libvlc_media_list_event_manager(self) + +class MediaListPlayer(_Ctype): + '''Create a new MediaListPlayer instance. + + It may take as parameter either: + - a vlc.Instance + - nothing + + ''' + + def __new__(cls, arg=None): + if arg is None: + i = get_default_instance() + elif isinstance(arg, Instance): + i = arg + elif isinstance(arg, _Ints): + return _Constructor(cls, arg) + else: + raise TypeError('MediaListPlayer %r' % (arg,)) + + return i.media_list_player_new() + + def get_instance(self): + """Return the associated Instance. + """ + return self._instance #PYCHOK expected + + + + def release(self): + '''Release a media_list_player after use + Decrement the reference count of a media player object. If the + reference count is 0, then L{release}() will + release the media player object. If the media player object + has been released, then it should not be used again. + ''' + return libvlc_media_list_player_release(self) + + + def retain(self): + '''Retain a reference to a media player list object. Use + L{release}() to decrement reference count. + ''' + return libvlc_media_list_player_retain(self) + + @memoize_parameterless + def event_manager(self): + '''Return the event manager of this media_list_player. + @return: the event manager. + ''' + return libvlc_media_list_player_event_manager(self) + + + def set_media_player(self, p_mi): + '''Replace media player in media_list_player with this instance. + @param p_mi: media player instance. + ''' + return libvlc_media_list_player_set_media_player(self, p_mi) + + + def get_media_player(self): + '''Get media player of the media_list_player instance. + @return: media player instance @note the caller is responsible for releasing the returned instance. + ''' + return libvlc_media_list_player_get_media_player(self) + + + def set_media_list(self, p_mlist): + '''Set the media list associated with the player. + @param p_mlist: list of media. + ''' + return libvlc_media_list_player_set_media_list(self, p_mlist) + + + def play(self): + '''Play media list. + ''' + return libvlc_media_list_player_play(self) + + + def pause(self): + '''Toggle pause (or resume) media list. + ''' + return libvlc_media_list_player_pause(self) + + + def set_pause(self, do_pause): + '''Pause or resume media list. + @param do_pause: play/resume if zero, pause if non-zero. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_list_player_set_pause(self, do_pause) + + + def is_playing(self): + '''Is media list playing? + @return: true for playing and false for not playing \libvlc_return_bool. + ''' + return libvlc_media_list_player_is_playing(self) + + + def get_state(self): + '''Get current libvlc_state of media list player. + @return: libvlc_state_t for media list player. + ''' + return libvlc_media_list_player_get_state(self) + + + def play_item_at_index(self, i_index): + '''Play media list item at position index. + @param i_index: index in media list to play. + @return: 0 upon success -1 if the item wasn't found. + ''' + return libvlc_media_list_player_play_item_at_index(self, i_index) + + def __getitem__(self, i): + return libvlc_media_list_player_play_item_at_index(self, i) + + def __iter__(self): + for i in range(len(self)): + yield self[i] + + + def play_item(self, p_md): + '''Play the given media item. + @param p_md: the media instance. + @return: 0 upon success, -1 if the media is not part of the media list. + ''' + return libvlc_media_list_player_play_item(self, p_md) + + + def stop(self): + '''Stop playing media list. + ''' + return libvlc_media_list_player_stop(self) + + + def next(self): + '''Play next item from media list. + @return: 0 upon success -1 if there is no next item. + ''' + return libvlc_media_list_player_next(self) + + + def previous(self): + '''Play previous item from media list. + @return: 0 upon success -1 if there is no previous item. + ''' + return libvlc_media_list_player_previous(self) + + + def set_playback_mode(self, e_mode): + '''Sets the playback mode for the playlist. + @param e_mode: playback mode specification. + ''' + return libvlc_media_list_player_set_playback_mode(self, e_mode) + +class MediaPlayer(_Ctype): + '''Create a new MediaPlayer instance. + + It may take as parameter either: + - a string (media URI), options... In this case, a vlc.Instance will be created. + - a vlc.Instance, a string (media URI), options... + + ''' + + def __new__(cls, *args): + if len(args) == 1 and isinstance(args[0], _Ints): + return _Constructor(cls, args[0]) + + if args and isinstance(args[0], Instance): + instance = args[0] + args = args[1:] + else: + instance = get_default_instance() + + o = instance.media_player_new() + if args: + o.set_media(instance.media_new(*args)) + return o + + def get_instance(self): + """Return the associated Instance. + """ + return self._instance #PYCHOK expected + + def set_mrl(self, mrl, *options): + """Set the MRL to play. + + Warning: most audio and video options, such as text renderer, + have no effects on an individual media. These options must be + set at the vlc.Instance or vlc.MediaPlayer instanciation. + + @param mrl: The MRL + @param options: optional media option=value strings + @return: the Media object + """ + m = self.get_instance().media_new(mrl, *options) + self.set_media(m) + return m + + def video_get_spu_description(self): + """Get the description of available video subtitles. + """ + return track_description_list(libvlc_video_get_spu_description(self)) + + def video_get_title_description(self): + """Get the description of available titles. + """ + return track_description_list(libvlc_video_get_title_description(self)) + + def video_get_chapter_description(self, title): + """Get the description of available chapters for specific title. + + @param title: selected title (int) + """ + return track_description_list(libvlc_video_get_chapter_description(self, title)) + + def video_get_track_description(self): + """Get the description of available video tracks. + """ + return track_description_list(libvlc_video_get_track_description(self)) + + def audio_get_track_description(self): + """Get the description of available audio tracks. + """ + return track_description_list(libvlc_audio_get_track_description(self)) + + def get_full_title_descriptions(self): + '''Get the full description of available titles. + @return: the titles list + @version: LibVLC 3.0.0 and later. + ''' + titleDescription_pp = ctypes.POINTER(TitleDescription)() + n = libvlc_media_player_get_full_title_descriptions(self, ctypes.byref(titleDescription_pp)) + info = ctypes.cast(ctypes.titleDescription_pp, ctypes.POINTER(ctypes.POINTER(TitleDescription) * n)) + return info + + def get_full_chapter_descriptions(self, i_chapters_of_title): + '''Get the full description of available chapters. + @param i_chapters_of_title: index of the title to query for chapters (uses current title if set to -1). + @return: the chapters list + @version: LibVLC 3.0.0 and later. + ''' + chapterDescription_pp = ctypes.POINTER(ChapterDescription)() + n = libvlc_media_player_get_full_chapter_descriptions(self, ctypes.byref(chapterDescription_pp)) + info = ctypes.cast(ctypes.chapterDescription_pp, ctypes.POINTER(ctypes.POINTER(ChapterDescription) * n)) + return info + + def video_get_size(self, num=0): + """Get the video size in pixels as 2-tuple (width, height). + + @param num: video number (default 0). + """ + r = libvlc_video_get_size(self, num) + if isinstance(r, tuple) and len(r) == 2: + return r + else: + raise VLCException('invalid video number (%s)' % (num,)) + + def set_hwnd(self, drawable): + """Set a Win32/Win64 API window handle (HWND). + + Specify where the media player should render its video + output. If LibVLC was built without Win32/Win64 API output + support, then this has no effects. + + @param drawable: windows handle of the drawable. + """ + if not isinstance(drawable, ctypes.c_void_p): + drawable = ctypes.c_void_p(int(drawable)) + libvlc_media_player_set_hwnd(self, drawable) + + def video_get_width(self, num=0): + """Get the width of a video in pixels. + + @param num: video number (default 0). + """ + return self.video_get_size(num)[0] + + def video_get_height(self, num=0): + """Get the height of a video in pixels. + + @param num: video number (default 0). + """ + return self.video_get_size(num)[1] + + def video_get_cursor(self, num=0): + """Get the mouse pointer coordinates over a video as 2-tuple (x, y). + + Coordinates are expressed in terms of the decoded video resolution, + B{not} in terms of pixels on the screen/viewport. To get the + latter, you must query your windowing system directly. + + Either coordinate may be negative or larger than the corresponding + size of the video, if the cursor is outside the rendering area. + + @warning: The coordinates may be out-of-date if the pointer is not + located on the video rendering area. LibVLC does not track the + mouse pointer if the latter is outside the video widget. + + @note: LibVLC does not support multiple mouse pointers (but does + support multiple input devices sharing the same pointer). + + @param num: video number (default 0). + """ + r = libvlc_video_get_cursor(self, num) + if isinstance(r, tuple) and len(r) == 2: + return r + raise VLCException('invalid video number (%s)' % (num,)) + + + + def get_fps(self): + '''Get movie fps rate + This function is provided for backward compatibility. It cannot deal with + multiple video tracks. In LibVLC versions prior to 3.0, it would also fail + if the file format did not convey the frame rate explicitly. + \deprecated Consider using L{media_tracks_get}() instead. + @return: frames per second (fps) for this playing movie, or 0 if unspecified. + ''' + return libvlc_media_player_get_fps(self) + + + def set_agl(self, drawable): + '''\deprecated Use L{set_nsobject}() instead. + ''' + return libvlc_media_player_set_agl(self, drawable) + + + def get_agl(self): + '''\deprecated Use L{get_nsobject}() instead. + ''' + return libvlc_media_player_get_agl(self) + + + def video_set_subtitle_file(self, psz_subtitle): + '''Set new video subtitle file. + \deprecated Use L{add_slave}() instead. + @param psz_subtitle: new video subtitle file. + @return: the success status (boolean). + ''' + return libvlc_video_set_subtitle_file(self, str_to_bytes(psz_subtitle)) + + + def toggle_teletext(self): + '''Toggle teletext transparent status on video output. + \deprecated use L{video_set_teletext}() instead. + ''' + return libvlc_toggle_teletext(self) + + + def release(self): + '''Release a media_player after use + Decrement the reference count of a media player object. If the + reference count is 0, then L{release}() will + release the media player object. If the media player object + has been released, then it should not be used again. + ''' + return libvlc_media_player_release(self) + + + def retain(self): + '''Retain a reference to a media player object. Use + L{release}() to decrement reference count. + ''' + return libvlc_media_player_retain(self) + + + def set_media(self, p_md): + '''Set the media that will be used by the media_player. If any, + previous md will be released. + @param p_md: the Media. Afterwards the p_md can be safely destroyed. + ''' + return libvlc_media_player_set_media(self, p_md) + + + def get_media(self): + '''Get the media used by the media_player. + @return: the media associated with p_mi, or None if no media is associated. + ''' + return libvlc_media_player_get_media(self) + + @memoize_parameterless + def event_manager(self): + '''Get the Event Manager from which the media player send event. + @return: the event manager associated with p_mi. + ''' + return libvlc_media_player_event_manager(self) + + + def is_playing(self): + '''is_playing. + @return: 1 if the media player is playing, 0 otherwise \libvlc_return_bool. + ''' + return libvlc_media_player_is_playing(self) + + + def play(self): + '''Play. + @return: 0 if playback started (and was already started), or -1 on error. + ''' + return libvlc_media_player_play(self) + + + def set_pause(self, do_pause): + '''Pause or resume (no effect if there is no media). + @param do_pause: play/resume if zero, pause if non-zero. + @version: LibVLC 1.1.1 or later. + ''' + return libvlc_media_player_set_pause(self, do_pause) + + + def pause(self): + '''Toggle pause (no effect if there is no media). + ''' + return libvlc_media_player_pause(self) + + + def stop(self): + '''Stop (no effect if there is no media). + ''' + return libvlc_media_player_stop(self) + + + def set_renderer(self, p_item): + '''Set a renderer to the media player + @note: must be called before the first call of L{play}() to + take effect. + See L{renderer_discoverer_new}. + @param p_item: an item discovered by L{renderer_discoverer_start}(). + @return: 0 on success, -1 on error. + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_media_player_set_renderer(self, p_item) + + + def video_set_callbacks(self, lock, unlock, display, opaque): + '''Set callbacks and private data to render decoded video to a custom area + in memory. + Use L{video_set_format}() or L{video_set_format_callbacks}() + to configure the decoded format. + @warning: Rendering video into custom memory buffers is considerably less + efficient than rendering in a custom window as normal. + For optimal perfomances, VLC media player renders into a custom window, and + does not use this function and associated callbacks. It is B{highly + recommended} that other LibVLC-based application do likewise. + To embed video in a window, use libvlc_media_player_set_xid() or equivalent + depending on the operating system. + If window embedding does not fit the application use case, then a custom + LibVLC video output display plugin is required to maintain optimal video + rendering performances. + The following limitations affect performance: + - Hardware video decoding acceleration will either be disabled completely, + or require (relatively slow) copy from video/DSP memory to main memory. + - Sub-pictures (subtitles, on-screen display, etc.) must be blent into the + main picture by the CPU instead of the GPU. + - Depending on the video format, pixel format conversion, picture scaling, + cropping and/or picture re-orientation, must be performed by the CPU + instead of the GPU. + - Memory copying is required between LibVLC reference picture buffers and + application buffers (between lock and unlock callbacks). + @param lock: callback to lock video memory (must not be None). + @param unlock: callback to unlock video memory (or None if not needed). + @param display: callback to display video (or None if not needed). + @param opaque: private pointer for the three callbacks (as first parameter). + @version: LibVLC 1.1.1 or later. + ''' + return libvlc_video_set_callbacks(self, lock, unlock, display, opaque) + + + def video_set_format(self, chroma, width, height, pitch): + '''Set decoded video chroma and dimensions. + This only works in combination with L{video_set_callbacks}(), + and is mutually exclusive with L{video_set_format_callbacks}(). + @param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV"). + @param width: pixel width. + @param height: pixel height. + @param pitch: line pitch (in bytes). + @version: LibVLC 1.1.1 or later. + @bug: All pixel planes are expected to have the same pitch. To use the YCbCr color space with chrominance subsampling, consider using L{video_set_format_callbacks}() instead. + ''' + return libvlc_video_set_format(self, str_to_bytes(chroma), width, height, pitch) + + + def video_set_format_callbacks(self, setup, cleanup): + '''Set decoded video chroma and dimensions. This only works in combination with + L{video_set_callbacks}(). + @param setup: callback to select the video format (cannot be None). + @param cleanup: callback to release any allocated resources (or None). + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_video_set_format_callbacks(self, setup, cleanup) + + + def set_nsobject(self, drawable): + '''Set the NSView handler where the media player should render its video output. + Use the vout called "macosx". + The drawable is an NSObject that follow the VLCOpenGLVideoViewEmbedding + protocol: + @code.m + \@protocol VLCOpenGLVideoViewEmbedding + - (void)addVoutSubview:(NSView *)view; + - (void)removeVoutSubview:(NSView *)view; + \@end + @endcode + Or it can be an NSView object. + If you want to use it along with Qt see the QMacCocoaViewContainer. Then + the following code should work: + @code.mm + + NSView *video = [[NSView alloc] init]; + QMacCocoaViewContainer *container = new QMacCocoaViewContainer(video, parent); + L{set_nsobject}(mp, video); + [video release]; + + @endcode + You can find a live example in VLCVideoView in VLCKit.framework. + @param drawable: the drawable that is either an NSView or an object following the VLCOpenGLVideoViewEmbedding protocol. + ''' + return libvlc_media_player_set_nsobject(self, drawable) + + + def get_nsobject(self): + '''Get the NSView handler previously set with L{set_nsobject}(). + @return: the NSView handler or 0 if none where set. + ''' + return libvlc_media_player_get_nsobject(self) + + + def set_xwindow(self, drawable): + '''Set an X Window System drawable where the media player should render its + video output. The call takes effect when the playback starts. If it is + already started, it might need to be stopped before changes apply. + If LibVLC was built without X11 output support, then this function has no + effects. + By default, LibVLC will capture input events on the video rendering area. + Use L{video_set_mouse_input}() and L{video_set_key_input}() to + disable that and deliver events to the parent window / to the application + instead. By design, the X11 protocol delivers input events to only one + recipient. + @warning + The application must call the XInitThreads() function from Xlib before + L{new}(), and before any call to XOpenDisplay() directly or via any + other library. Failure to call XInitThreads() will seriously impede LibVLC + performance. Calling XOpenDisplay() before XInitThreads() will eventually + crash the process. That is a limitation of Xlib. + @param drawable: X11 window ID @note The specified identifier must correspond to an existing Input/Output class X11 window. Pixmaps are B{not} currently supported. The default X11 server is assumed, i.e. that specified in the DISPLAY environment variable. @warning LibVLC can deal with invalid X11 handle errors, however some display drivers (EGL, GLX, VA and/or VDPAU) can unfortunately not. Thus the window handle must remain valid until playback is stopped, otherwise the process may abort or crash. + @bug No more than one window handle per media player instance can be specified. If the media has multiple simultaneously active video tracks, extra tracks will be rendered into external windows beyond the control of the application. + ''' + return libvlc_media_player_set_xwindow(self, drawable) + + + def get_xwindow(self): + '''Get the X Window System window identifier previously set with + L{set_xwindow}(). Note that this will return the identifier + even if VLC is not currently using it (for instance if it is playing an + audio-only input). + @return: an X window ID, or 0 if none where set. + ''' + return libvlc_media_player_get_xwindow(self) + + + def get_hwnd(self): + '''Get the Windows API window handle (HWND) previously set with + L{set_hwnd}(). The handle will be returned even if LibVLC + is not currently outputting any video to it. + @return: a window handle or None if there are none. + ''' + return libvlc_media_player_get_hwnd(self) + + + def set_android_context(self, p_awindow_handler): + '''Set the android context. + @param p_awindow_handler: org.videolan.libvlc.AWindow jobject owned by the org.videolan.libvlc.MediaPlayer class from the libvlc-android project. + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_media_player_set_android_context(self, p_awindow_handler) + + + def set_evas_object(self, p_evas_object): + '''Set the EFL Evas Object. + @param p_evas_object: a valid EFL Evas Object (Evas_Object). + @return: -1 if an error was detected, 0 otherwise. + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_media_player_set_evas_object(self, p_evas_object) + + + def audio_set_callbacks(self, play, pause, resume, flush, drain, opaque): + '''Sets callbacks and private data for decoded audio. + Use L{audio_set_format}() or L{audio_set_format_callbacks}() + to configure the decoded audio format. + @note: The audio callbacks override any other audio output mechanism. + If the callbacks are set, LibVLC will B{not} output audio in any way. + @param play: callback to play audio samples (must not be None). + @param pause: callback to pause playback (or None to ignore). + @param resume: callback to resume playback (or None to ignore). + @param flush: callback to flush audio buffers (or None to ignore). + @param drain: callback to drain audio buffers (or None to ignore). + @param opaque: private pointer for the audio callbacks (as first parameter). + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_audio_set_callbacks(self, play, pause, resume, flush, drain, opaque) + + + def audio_set_volume_callback(self, set_volume): + '''Set callbacks and private data for decoded audio. This only works in + combination with L{audio_set_callbacks}(). + Use L{audio_set_format}() or L{audio_set_format_callbacks}() + to configure the decoded audio format. + @param set_volume: callback to apply audio volume, or None to apply volume in software. + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_audio_set_volume_callback(self, set_volume) + + + def audio_set_format_callbacks(self, setup, cleanup): + '''Sets decoded audio format via callbacks. + This only works in combination with L{audio_set_callbacks}(). + @param setup: callback to select the audio format (cannot be None). + @param cleanup: callback to release any allocated resources (or None). + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_audio_set_format_callbacks(self, setup, cleanup) + + + def audio_set_format(self, format, rate, channels): + '''Sets a fixed decoded audio format. + This only works in combination with L{audio_set_callbacks}(), + and is mutually exclusive with L{audio_set_format_callbacks}(). + @param format: a four-characters string identifying the sample format (e.g. "S16N" or "FL32"). + @param rate: sample rate (expressed in Hz). + @param channels: channels count. + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_audio_set_format(self, str_to_bytes(format), rate, channels) + + + def get_length(self): + '''Get the current movie length (in ms). + @return: the movie length (in ms), or -1 if there is no media. + ''' + return libvlc_media_player_get_length(self) + + + def get_time(self): + '''Get the current movie time (in ms). + @return: the movie time (in ms), or -1 if there is no media. + ''' + return libvlc_media_player_get_time(self) + + + def set_time(self, i_time): + '''Set the movie time (in ms). This has no effect if no media is being played. + Not all formats and protocols support this. + @param i_time: the movie time (in ms). + ''' + return libvlc_media_player_set_time(self, i_time) + + + def get_position(self): + '''Get movie position as percentage between 0.0 and 1.0. + @return: movie position, or -1. in case of error. + ''' + return libvlc_media_player_get_position(self) + + + def set_position(self, f_pos): + '''Set movie position as percentage between 0.0 and 1.0. + This has no effect if playback is not enabled. + This might not work depending on the underlying input format and protocol. + @param f_pos: the position. + ''' + return libvlc_media_player_set_position(self, f_pos) + + + def set_chapter(self, i_chapter): + '''Set movie chapter (if applicable). + @param i_chapter: chapter number to play. + ''' + return libvlc_media_player_set_chapter(self, i_chapter) + + + def get_chapter(self): + '''Get movie chapter. + @return: chapter number currently playing, or -1 if there is no media. + ''' + return libvlc_media_player_get_chapter(self) + + + def get_chapter_count(self): + '''Get movie chapter count. + @return: number of chapters in movie, or -1. + ''' + return libvlc_media_player_get_chapter_count(self) + + + def will_play(self): + '''Is the player able to play. + @return: boolean \libvlc_return_bool. + ''' + return libvlc_media_player_will_play(self) + + + def get_chapter_count_for_title(self, i_title): + '''Get title chapter count. + @param i_title: title. + @return: number of chapters in title, or -1. + ''' + return libvlc_media_player_get_chapter_count_for_title(self, i_title) + + + def set_title(self, i_title): + '''Set movie title. + @param i_title: title number to play. + ''' + return libvlc_media_player_set_title(self, i_title) + + + def get_title(self): + '''Get movie title. + @return: title number currently playing, or -1. + ''' + return libvlc_media_player_get_title(self) + + + def get_title_count(self): + '''Get movie title count. + @return: title number count, or -1. + ''' + return libvlc_media_player_get_title_count(self) + + + def previous_chapter(self): + '''Set previous chapter (if applicable). + ''' + return libvlc_media_player_previous_chapter(self) + + + def next_chapter(self): + '''Set next chapter (if applicable). + ''' + return libvlc_media_player_next_chapter(self) + + + def get_rate(self): + '''Get the requested movie play rate. + @warning: Depending on the underlying media, the requested rate may be + different from the real playback rate. + @return: movie play rate. + ''' + return libvlc_media_player_get_rate(self) + + + def set_rate(self, rate): + '''Set movie play rate. + @param rate: movie play rate to set. + @return: -1 if an error was detected, 0 otherwise (but even then, it might not actually work depending on the underlying media protocol). + ''' + return libvlc_media_player_set_rate(self, rate) + + + def get_state(self): + '''Get current movie state. + @return: the current state of the media player (playing, paused, ...) See libvlc_state_t. + ''' + return libvlc_media_player_get_state(self) + + + def has_vout(self): + '''How many video outputs does this media player have? + @return: the number of video outputs. + ''' + return libvlc_media_player_has_vout(self) + + + def is_seekable(self): + '''Is this media player seekable? + @return: true if the media player can seek \libvlc_return_bool. + ''' + return libvlc_media_player_is_seekable(self) + + + def can_pause(self): + '''Can this media player be paused? + @return: true if the media player can pause \libvlc_return_bool. + ''' + return libvlc_media_player_can_pause(self) + + + def program_scrambled(self): + '''Check if the current program is scrambled. + @return: true if the current program is scrambled \libvlc_return_bool. + @version: LibVLC 2.2.0 or later. + ''' + return libvlc_media_player_program_scrambled(self) + + + def next_frame(self): + '''Display the next frame (if supported). + ''' + return libvlc_media_player_next_frame(self) + + + def navigate(self, navigate): + '''Navigate through DVD Menu. + @param navigate: the Navigation mode. + @version: libVLC 2.0.0 or later. + ''' + return libvlc_media_player_navigate(self, navigate) + + + def set_video_title_display(self, position, timeout): + '''Set if, and how, the video title will be shown when media is played. + @param position: position at which to display the title, or libvlc_position_disable to prevent the title from being displayed. + @param timeout: title display timeout in milliseconds (ignored if libvlc_position_disable). + @version: libVLC 2.1.0 or later. + ''' + return libvlc_media_player_set_video_title_display(self, position, timeout) + + + def add_slave(self, i_type, psz_uri, b_select): + '''Add a slave to the current media player. + @note: If the player is playing, the slave will be added directly. This call + will also update the slave list of the attached L{Media}. + @param i_type: subtitle or audio. + @param psz_uri: Uri of the slave (should contain a valid scheme). + @param b_select: True if this slave should be selected when it's loaded. + @return: 0 on success, -1 on error. + @version: LibVLC 3.0.0 and later. See L{media_slaves_add}. + ''' + return libvlc_media_player_add_slave(self, i_type, str_to_bytes(psz_uri), b_select) + + + def toggle_fullscreen(self): + '''Toggle fullscreen status on non-embedded video outputs. + @warning: The same limitations applies to this function + as to L{set_fullscreen}(). + ''' + return libvlc_toggle_fullscreen(self) + + + def set_fullscreen(self, b_fullscreen): + '''Enable or disable fullscreen. + @warning: With most window managers, only a top-level windows can be in + full-screen mode. Hence, this function will not operate properly if + L{set_xwindow}() was used to embed the video in a + non-top-level window. In that case, the embedding window must be reparented + to the root window B{before} fullscreen mode is enabled. You will want + to reparent it back to its normal parent when disabling fullscreen. + @param b_fullscreen: boolean for fullscreen status. + ''' + return libvlc_set_fullscreen(self, b_fullscreen) + + + def get_fullscreen(self): + '''Get current fullscreen status. + @return: the fullscreen status (boolean) \libvlc_return_bool. + ''' + return libvlc_get_fullscreen(self) + + + def video_set_key_input(self, on): + '''Enable or disable key press events handling, according to the LibVLC hotkeys + configuration. By default and for historical reasons, keyboard events are + handled by the LibVLC video widget. + @note: On X11, there can be only one subscriber for key press and mouse + click events per window. If your application has subscribed to those events + for the X window ID of the video widget, then LibVLC will not be able to + handle key presses and mouse clicks in any case. + @warning: This function is only implemented for X11 and Win32 at the moment. + @param on: true to handle key press events, false to ignore them. + ''' + return libvlc_video_set_key_input(self, on) + + + def video_set_mouse_input(self, on): + '''Enable or disable mouse click events handling. By default, those events are + handled. This is needed for DVD menus to work, as well as a few video + filters such as "puzzle". + See L{video_set_key_input}(). + @warning: This function is only implemented for X11 and Win32 at the moment. + @param on: true to handle mouse click events, false to ignore them. + ''' + return libvlc_video_set_mouse_input(self, on) + + + def video_get_scale(self): + '''Get the current video scaling factor. + See also L{video_set_scale}(). + @return: the currently configured zoom factor, or 0. if the video is set to fit to the output window/drawable automatically. + ''' + return libvlc_video_get_scale(self) + + + def video_set_scale(self, f_factor): + '''Set the video scaling factor. That is the ratio of the number of pixels on + screen to the number of pixels in the original decoded video in each + dimension. Zero is a special value; it will adjust the video to the output + window/drawable (in windowed mode) or the entire screen. + Note that not all video outputs support scaling. + @param f_factor: the scaling factor, or zero. + ''' + return libvlc_video_set_scale(self, f_factor) + + + def video_get_aspect_ratio(self): + '''Get current video aspect ratio. + @return: the video aspect ratio or None if unspecified (the result must be released with free() or L{free}()). + ''' + return libvlc_video_get_aspect_ratio(self) + + + def video_set_aspect_ratio(self, psz_aspect): + '''Set new video aspect ratio. + @param psz_aspect: new video aspect-ratio or None to reset to default @note Invalid aspect ratios are ignored. + ''' + return libvlc_video_set_aspect_ratio(self, str_to_bytes(psz_aspect)) + + + def video_update_viewpoint(self, p_viewpoint, b_absolute): + '''Update the video viewpoint information. + @note: It is safe to call this function before the media player is started. + @param p_viewpoint: video viewpoint allocated via L{video_new_viewpoint}(). + @param b_absolute: if true replace the old viewpoint with the new one. If false, increase/decrease it. + @return: -1 in case of error, 0 otherwise @note the values are set asynchronously, it will be used by the next frame displayed. + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_video_update_viewpoint(self, p_viewpoint, b_absolute) + + + def video_get_spu(self): + '''Get current video subtitle. + @return: the video subtitle selected, or -1 if none. + ''' + return libvlc_video_get_spu(self) + + + def video_get_spu_count(self): + '''Get the number of available video subtitles. + @return: the number of available video subtitles. + ''' + return libvlc_video_get_spu_count(self) + + + def video_set_spu(self, i_spu): + '''Set new video subtitle. + @param i_spu: video subtitle track to select (i_id from track description). + @return: 0 on success, -1 if out of range. + ''' + return libvlc_video_set_spu(self, i_spu) + + + def video_get_spu_delay(self): + '''Get the current subtitle delay. Positive values means subtitles are being + displayed later, negative values earlier. + @return: time (in microseconds) the display of subtitles is being delayed. + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_video_get_spu_delay(self) + + + def video_set_spu_delay(self, i_delay): + '''Set the subtitle delay. This affects the timing of when the subtitle will + be displayed. Positive values result in subtitles being displayed later, + while negative values will result in subtitles being displayed earlier. + The subtitle delay will be reset to zero each time the media changes. + @param i_delay: time (in microseconds) the display of subtitles should be delayed. + @return: 0 on success, -1 on error. + @version: LibVLC 2.0.0 or later. + ''' + return libvlc_video_set_spu_delay(self, i_delay) + + + def video_get_crop_geometry(self): + '''Get current crop filter geometry. + @return: the crop filter geometry or None if unset. + ''' + return libvlc_video_get_crop_geometry(self) + + + def video_set_crop_geometry(self, psz_geometry): + '''Set new crop filter geometry. + @param psz_geometry: new crop filter geometry (None to unset). + ''' + return libvlc_video_set_crop_geometry(self, str_to_bytes(psz_geometry)) + + + def video_get_teletext(self): + '''Get current teletext page requested or 0 if it's disabled. + Teletext is disabled by default, call L{video_set_teletext}() to enable + it. + @return: the current teletext page requested. + ''' + return libvlc_video_get_teletext(self) + + + def video_set_teletext(self, i_page): + '''Set new teletext page to retrieve. + This function can also be used to send a teletext key. + @param i_page: teletex page number requested. This value can be 0 to disable teletext, a number in the range ]0;1000[ to show the requested page, or a \ref libvlc_teletext_key_t. 100 is the default teletext page. + ''' + return libvlc_video_set_teletext(self, i_page) + + + def video_get_track_count(self): + '''Get number of available video tracks. + @return: the number of available video tracks (int). + ''' + return libvlc_video_get_track_count(self) + + + def video_get_track(self): + '''Get current video track. + @return: the video track ID (int) or -1 if no active input. + ''' + return libvlc_video_get_track(self) + + + def video_set_track(self, i_track): + '''Set video track. + @param i_track: the track ID (i_id field from track description). + @return: 0 on success, -1 if out of range. + ''' + return libvlc_video_set_track(self, i_track) + + + def video_take_snapshot(self, num, psz_filepath, i_width, i_height): + '''Take a snapshot of the current video window. + If i_width AND i_height is 0, original size is used. + If i_width XOR i_height is 0, original aspect-ratio is preserved. + @param num: number of video output (typically 0 for the first/only one). + @param psz_filepath: the path of a file or a folder to save the screenshot into. + @param i_width: the snapshot's width. + @param i_height: the snapshot's height. + @return: 0 on success, -1 if the video was not found. + ''' + return libvlc_video_take_snapshot(self, num, str_to_bytes(psz_filepath), i_width, i_height) + + + def video_set_deinterlace(self, psz_mode): + '''Enable or disable deinterlace filter. + @param psz_mode: type of deinterlace filter, None to disable. + ''' + return libvlc_video_set_deinterlace(self, str_to_bytes(psz_mode)) + + + def video_get_marquee_int(self, option): + '''Get an integer marquee option value. + @param option: marq option to get See libvlc_video_marquee_int_option_t. + ''' + return libvlc_video_get_marquee_int(self, option) + + + def video_get_marquee_string(self, option): + '''Get a string marquee option value. + @param option: marq option to get See libvlc_video_marquee_string_option_t. + ''' + return libvlc_video_get_marquee_string(self, option) + + + def video_set_marquee_int(self, option, i_val): + '''Enable, disable or set an integer marquee option + Setting libvlc_marquee_Enable has the side effect of enabling (arg !0) + or disabling (arg 0) the marq filter. + @param option: marq option to set See libvlc_video_marquee_int_option_t. + @param i_val: marq option value. + ''' + return libvlc_video_set_marquee_int(self, option, i_val) + + + def video_set_marquee_string(self, option, psz_text): + '''Set a marquee string option. + @param option: marq option to set See libvlc_video_marquee_string_option_t. + @param psz_text: marq option value. + ''' + return libvlc_video_set_marquee_string(self, option, str_to_bytes(psz_text)) + + + def video_get_logo_int(self, option): + '''Get integer logo option. + @param option: logo option to get, values of libvlc_video_logo_option_t. + ''' + return libvlc_video_get_logo_int(self, option) + + + def video_set_logo_int(self, option, value): + '''Set logo option as integer. Options that take a different type value + are ignored. + Passing libvlc_logo_enable as option value has the side effect of + starting (arg !0) or stopping (arg 0) the logo filter. + @param option: logo option to set, values of libvlc_video_logo_option_t. + @param value: logo option value. + ''' + return libvlc_video_set_logo_int(self, option, value) + + + def video_set_logo_string(self, option, psz_value): + '''Set logo option as string. Options that take a different type value + are ignored. + @param option: logo option to set, values of libvlc_video_logo_option_t. + @param psz_value: logo option value. + ''' + return libvlc_video_set_logo_string(self, option, str_to_bytes(psz_value)) + + + def video_get_adjust_int(self, option): + '''Get integer adjust option. + @param option: adjust option to get, values of libvlc_video_adjust_option_t. + @version: LibVLC 1.1.1 and later. + ''' + return libvlc_video_get_adjust_int(self, option) + + + def video_set_adjust_int(self, option, value): + '''Set adjust option as integer. Options that take a different type value + are ignored. + Passing libvlc_adjust_enable as option value has the side effect of + starting (arg !0) or stopping (arg 0) the adjust filter. + @param option: adust option to set, values of libvlc_video_adjust_option_t. + @param value: adjust option value. + @version: LibVLC 1.1.1 and later. + ''' + return libvlc_video_set_adjust_int(self, option, value) + + + def video_get_adjust_float(self, option): + '''Get float adjust option. + @param option: adjust option to get, values of libvlc_video_adjust_option_t. + @version: LibVLC 1.1.1 and later. + ''' + return libvlc_video_get_adjust_float(self, option) + + + def video_set_adjust_float(self, option, value): + '''Set adjust option as float. Options that take a different type value + are ignored. + @param option: adust option to set, values of libvlc_video_adjust_option_t. + @param value: adjust option value. + @version: LibVLC 1.1.1 and later. + ''' + return libvlc_video_set_adjust_float(self, option, value) + + + def audio_output_set(self, psz_name): + '''Selects an audio output module. + @note: Any change will take be effect only after playback is stopped and + restarted. Audio output cannot be changed while playing. + @param psz_name: name of audio output, use psz_name of See L{AudioOutput}. + @return: 0 if function succeeded, -1 on error. + ''' + return libvlc_audio_output_set(self, str_to_bytes(psz_name)) + + + def audio_output_device_enum(self): + '''Gets a list of potential audio output devices, + See L{audio_output_device_set}(). + @note: Not all audio outputs support enumerating devices. + The audio output may be functional even if the list is empty (None). + @note: The list may not be exhaustive. + @warning: Some audio output devices in the list might not actually work in + some circumstances. By default, it is recommended to not specify any + explicit audio device. + @return: A None-terminated linked list of potential audio output devices. It must be freed with L{audio_output_device_list_release}(). + @version: LibVLC 2.2.0 or later. + ''' + return libvlc_audio_output_device_enum(self) + + + def audio_output_device_set(self, module, device_id): + '''Configures an explicit audio output device. + If the module paramater is None, audio output will be moved to the device + specified by the device identifier string immediately. This is the + recommended usage. + A list of adequate potential device strings can be obtained with + L{audio_output_device_enum}(). + However passing None is supported in LibVLC version 2.2.0 and later only; + in earlier versions, this function would have no effects when the module + parameter was None. + If the module parameter is not None, the device parameter of the + corresponding audio output, if it exists, will be set to the specified + string. Note that some audio output modules do not have such a parameter + (notably MMDevice and PulseAudio). + A list of adequate potential device strings can be obtained with + L{audio_output_device_list_get}(). + @note: This function does not select the specified audio output plugin. + L{audio_output_set}() is used for that purpose. + @warning: The syntax for the device parameter depends on the audio output. + Some audio output modules require further parameters (e.g. a channels map + in the case of ALSA). + @param module: If None, current audio output module. if non-None, name of audio output module. + @param device_id: device identifier string. + @return: Nothing. Errors are ignored (this is a design bug). + ''' + return libvlc_audio_output_device_set(self, str_to_bytes(module), str_to_bytes(device_id)) + + + def audio_output_device_get(self): + '''Get the current audio output device identifier. + This complements L{audio_output_device_set}(). + @warning: The initial value for the current audio output device identifier + may not be set or may be some unknown value. A LibVLC application should + compare this value against the known device identifiers (e.g. those that + were previously retrieved by a call to L{audio_output_device_enum} or + L{audio_output_device_list_get}) to find the current audio output device. + It is possible that the selected audio output device changes (an external + change) without a call to L{audio_output_device_set}. That may make this + method unsuitable to use if a LibVLC application is attempting to track + dynamic audio device changes as they happen. + @return: the current audio output device identifier None if no device is selected or in case of error (the result must be released with free() or L{free}()). + @version: LibVLC 3.0.0 or later. + ''' + return libvlc_audio_output_device_get(self) + + + def audio_toggle_mute(self): + '''Toggle mute status. + ''' + return libvlc_audio_toggle_mute(self) + + + def audio_get_mute(self): + '''Get current mute status. + @return: the mute status (boolean) if defined, -1 if undefined/unapplicable. + ''' + return libvlc_audio_get_mute(self) + + + def audio_set_mute(self, status): + '''Set mute status. + @param status: If status is true then mute, otherwise unmute @warning This function does not always work. If there are no active audio playback stream, the mute status might not be available. If digital pass-through (S/PDIF, HDMI...) is in use, muting may be unapplicable. Also some audio output plugins do not support muting at all. @note To force silent playback, disable all audio tracks. This is more efficient and reliable than mute. + ''' + return libvlc_audio_set_mute(self, status) + + + def audio_get_volume(self): + '''Get current software audio volume. + @return: the software volume in percents (0 = mute, 100 = nominal / 0dB). + ''' + return libvlc_audio_get_volume(self) + + + def audio_set_volume(self, i_volume): + '''Set current software audio volume. + @param i_volume: the volume in percents (0 = mute, 100 = 0dB). + @return: 0 if the volume was set, -1 if it was out of range. + ''' + return libvlc_audio_set_volume(self, i_volume) + + + def audio_get_track_count(self): + '''Get number of available audio tracks. + @return: the number of available audio tracks (int), or -1 if unavailable. + ''' + return libvlc_audio_get_track_count(self) + + + def audio_get_track(self): + '''Get current audio track. + @return: the audio track ID or -1 if no active input. + ''' + return libvlc_audio_get_track(self) + + + def audio_set_track(self, i_track): + '''Set current audio track. + @param i_track: the track ID (i_id field from track description). + @return: 0 on success, -1 on error. + ''' + return libvlc_audio_set_track(self, i_track) + + + def audio_get_channel(self): + '''Get current audio channel. + @return: the audio channel See libvlc_audio_output_channel_t. + ''' + return libvlc_audio_get_channel(self) + + + def audio_set_channel(self, channel): + '''Set current audio channel. + @param channel: the audio channel, See libvlc_audio_output_channel_t. + @return: 0 on success, -1 on error. + ''' + return libvlc_audio_set_channel(self, channel) + + + def audio_get_delay(self): + '''Get current audio delay. + @return: the audio delay (microseconds). + @version: LibVLC 1.1.1 or later. + ''' + return libvlc_audio_get_delay(self) + + + def audio_set_delay(self, i_delay): + '''Set current audio delay. The audio delay will be reset to zero each time the media changes. + @param i_delay: the audio delay (microseconds). + @return: 0 on success, -1 on error. + @version: LibVLC 1.1.1 or later. + ''' + return libvlc_audio_set_delay(self, i_delay) + + + def set_equalizer(self, p_equalizer): + '''Apply new equalizer settings to a media player. + The equalizer is first created by invoking L{audio_equalizer_new}() or + L{audio_equalizer_new_from_preset}(). + It is possible to apply new equalizer settings to a media player whether the media + player is currently playing media or not. + Invoking this method will immediately apply the new equalizer settings to the audio + output of the currently playing media if there is any. + If there is no currently playing media, the new equalizer settings will be applied + later if and when new media is played. + Equalizer settings will automatically be applied to subsequently played media. + To disable the equalizer for a media player invoke this method passing None for the + p_equalizer parameter. + The media player does not keep a reference to the supplied equalizer so it is safe + for an application to release the equalizer reference any time after this method + returns. + @param p_equalizer: opaque equalizer handle, or None to disable the equalizer for this media player. + @return: zero on success, -1 on error. + @version: LibVLC 2.2.0 or later. + ''' + return libvlc_media_player_set_equalizer(self, p_equalizer) + + + def get_role(self): + '''Gets the media role. + @return: the media player role (\ref libvlc_media_player_role_t). + @version: LibVLC 3.0.0 and later. + ''' + return libvlc_media_player_get_role(self) + + + def set_role(self, role): + '''Sets the media role. + @param role: the media player role (\ref libvlc_media_player_role_t). + @return: 0 on success, -1 on error. + ''' + return libvlc_media_player_set_role(self, role) + + + # LibVLC __version__ functions # + +def libvlc_clearerr(): + '''Clears the LibVLC error status for the current thread. This is optional. + By default, the error status is automatically overridden when a new error + occurs, and destroyed when the thread exits. + ''' + f = _Cfunctions.get('libvlc_clearerr', None) or \ + _Cfunction('libvlc_clearerr', (), None, + None) + return f() + +def libvlc_vprinterr(fmt, ap): + '''Sets the LibVLC error status and message for the current thread. + Any previous error is overridden. + @param fmt: the format string. + @param ap: the arguments. + @return: a nul terminated string in any case. + ''' + f = _Cfunctions.get('libvlc_vprinterr', None) or \ + _Cfunction('libvlc_vprinterr', ((1,), (1,),), None, + ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p) + return f(fmt, ap) + +def libvlc_new(argc, argv): + '''Create and initialize a libvlc instance. + This functions accept a list of "command line" arguments similar to the + main(). These arguments affect the LibVLC instance default configuration. + @note + LibVLC may create threads. Therefore, any thread-unsafe process + initialization must be performed before calling L{libvlc_new}(). In particular + and where applicable: + - setlocale() and textdomain(), + - setenv(), unsetenv() and putenv(), + - with the X11 display system, XInitThreads() + (see also L{libvlc_media_player_set_xwindow}()) and + - on Microsoft Windows, SetErrorMode(). + - sigprocmask() shall never be invoked; pthread_sigmask() can be used. + On POSIX systems, the SIGCHLD signal B{must not} be ignored, i.e. the + signal handler must set to SIG_DFL or a function pointer, not SIG_IGN. + Also while LibVLC is active, the wait() function shall not be called, and + any call to waitpid() shall use a strictly positive value for the first + parameter (i.e. the PID). Failure to follow those rules may lead to a + deadlock or a busy loop. + Also on POSIX systems, it is recommended that the SIGPIPE signal be blocked, + even if it is not, in principles, necessary, e.g.: + @code + @endcode + On Microsoft Windows Vista/2008, the process error mode + SEM_FAILCRITICALERRORS flag B{must} be set before using LibVLC. + On later versions, that is optional and unnecessary. + Also on Microsoft Windows (Vista and any later version), setting the default + DLL directories to SYSTEM32 exclusively is strongly recommended for + security reasons: + @code + @endcode. + @param argc: the number of arguments (should be 0). + @param argv: list of arguments (should be None). + @return: the libvlc instance or None in case of error. + @version Arguments are meant to be passed from the command line to LibVLC, just like VLC media player does. The list of valid arguments depends on the LibVLC version, the operating system and platform, and set of available LibVLC plugins. Invalid or unsupported arguments will cause the function to fail (i.e. return None). Also, some arguments may alter the behaviour or otherwise interfere with other LibVLC functions. @warning There is absolutely no warranty or promise of forward, backward and cross-platform compatibility with regards to L{libvlc_new}() arguments. We recommend that you do not use them, other than when debugging. + ''' + f = _Cfunctions.get('libvlc_new', None) or \ + _Cfunction('libvlc_new', ((1,), (1,),), class_result(Instance), + ctypes.c_void_p, ctypes.c_int, ListPOINTER(ctypes.c_char_p)) + return f(argc, argv) + +def libvlc_release(p_instance): + '''Decrement the reference count of a libvlc instance, and destroy it + if it reaches zero. + @param p_instance: the instance to destroy. + ''' + f = _Cfunctions.get('libvlc_release', None) or \ + _Cfunction('libvlc_release', ((1,),), None, + None, Instance) + return f(p_instance) + +def libvlc_retain(p_instance): + '''Increments the reference count of a libvlc instance. + The initial reference count is 1 after L{libvlc_new}() returns. + @param p_instance: the instance to reference. + ''' + f = _Cfunctions.get('libvlc_retain', None) or \ + _Cfunction('libvlc_retain', ((1,),), None, + None, Instance) + return f(p_instance) + +def libvlc_add_intf(p_instance, name): + '''Try to start a user interface for the libvlc instance. + @param p_instance: the instance. + @param name: interface name, or None for default. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_add_intf', None) or \ + _Cfunction('libvlc_add_intf', ((1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p) + return f(p_instance, name) + +def libvlc_set_user_agent(p_instance, name, http): + '''Sets the application name. LibVLC passes this as the user agent string + when a protocol requires it. + @param p_instance: LibVLC instance. + @param name: human-readable application name, e.g. "FooBar player 1.2.3". + @param http: HTTP User Agent, e.g. "FooBar/1.2.3 Python/2.6.0". + @version: LibVLC 1.1.1 or later. + ''' + f = _Cfunctions.get('libvlc_set_user_agent', None) or \ + _Cfunction('libvlc_set_user_agent', ((1,), (1,), (1,),), None, + None, Instance, ctypes.c_char_p, ctypes.c_char_p) + return f(p_instance, name, http) + +def libvlc_set_app_id(p_instance, id, version, icon): + '''Sets some meta-information about the application. + See also L{libvlc_set_user_agent}(). + @param p_instance: LibVLC instance. + @param id: Java-style application identifier, e.g. "com.acme.foobar". + @param version: application version numbers, e.g. "1.2.3". + @param icon: application icon name, e.g. "foobar". + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_set_app_id', None) or \ + _Cfunction('libvlc_set_app_id', ((1,), (1,), (1,), (1,),), None, + None, Instance, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p) + return f(p_instance, id, version, icon) + +def libvlc_get_version(): + '''Retrieve libvlc version. + Example: "1.1.0-git The Luggage". + @return: a string containing the libvlc version. + ''' + f = _Cfunctions.get('libvlc_get_version', None) or \ + _Cfunction('libvlc_get_version', (), None, + ctypes.c_char_p) + return f() + +def libvlc_get_compiler(): + '''Retrieve libvlc compiler version. + Example: "gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu6)". + @return: a string containing the libvlc compiler version. + ''' + f = _Cfunctions.get('libvlc_get_compiler', None) or \ + _Cfunction('libvlc_get_compiler', (), None, + ctypes.c_char_p) + return f() + +def libvlc_get_changeset(): + '''Retrieve libvlc changeset. + Example: "aa9bce0bc4". + @return: a string containing the libvlc changeset. + ''' + f = _Cfunctions.get('libvlc_get_changeset', None) or \ + _Cfunction('libvlc_get_changeset', (), None, + ctypes.c_char_p) + return f() + +def libvlc_free(ptr): + '''Frees an heap allocation returned by a LibVLC function. + If you know you're using the same underlying C run-time as the LibVLC + implementation, then you can call ANSI C free() directly instead. + @param ptr: the pointer. + ''' + f = _Cfunctions.get('libvlc_free', None) or \ + _Cfunction('libvlc_free', ((1,),), None, + None, ctypes.c_void_p) + return f(ptr) + +def libvlc_event_attach(p_event_manager, i_event_type, f_callback, user_data): + '''Register for an event notification. + @param p_event_manager: the event manager to which you want to attach to. Generally it is obtained by vlc_my_object_event_manager() where my_object is the object you want to listen to. + @param i_event_type: the desired event to which we want to listen. + @param f_callback: the function to call when i_event_type occurs. + @param user_data: user provided data to carry with the event. + @return: 0 on success, ENOMEM on error. + ''' + f = _Cfunctions.get('libvlc_event_attach', None) or \ + _Cfunction('libvlc_event_attach', ((1,), (1,), (1,), (1,),), None, + ctypes.c_int, EventManager, ctypes.c_uint, Callback, ctypes.c_void_p) + return f(p_event_manager, i_event_type, f_callback, user_data) + +def libvlc_event_detach(p_event_manager, i_event_type, f_callback, p_user_data): + '''Unregister an event notification. + @param p_event_manager: the event manager. + @param i_event_type: the desired event to which we want to unregister. + @param f_callback: the function to call when i_event_type occurs. + @param p_user_data: user provided data to carry with the event. + ''' + f = _Cfunctions.get('libvlc_event_detach', None) or \ + _Cfunction('libvlc_event_detach', ((1,), (1,), (1,), (1,),), None, + None, EventManager, ctypes.c_uint, Callback, ctypes.c_void_p) + return f(p_event_manager, i_event_type, f_callback, p_user_data) + +def libvlc_event_type_name(event_type): + '''Get an event's type name. + @param event_type: the desired event. + ''' + f = _Cfunctions.get('libvlc_event_type_name', None) or \ + _Cfunction('libvlc_event_type_name', ((1,),), None, + ctypes.c_char_p, ctypes.c_uint) + return f(event_type) + +def libvlc_log_get_context(ctx): + '''Gets log message debug infos. + This function retrieves self-debug information about a log message: + - the name of the VLC module emitting the message, + - the name of the source code module (i.e. file) and + - the line number within the source code module. + The returned module name and file name will be None if unknown. + The returned line number will similarly be zero if unknown. + @param ctx: message context (as passed to the @ref libvlc_log_cb callback). + @return: module module name storage (or None), file source code file name storage (or None), line source code file line number storage (or None). + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_get_context', None) or \ + _Cfunction('libvlc_log_get_context', ((1,), (2,), (2,), (2,),), None, + None, Log_ptr, ListPOINTER(ctypes.c_char_p), ListPOINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_uint)) + return f(ctx) + +def libvlc_log_get_object(ctx, id): + '''Gets log message info. + This function retrieves meta-information about a log message: + - the type name of the VLC object emitting the message, + - the object header if any, and + - a temporaly-unique object identifier. + This information is mainly meant for B{manual} troubleshooting. + The returned type name may be "generic" if unknown, but it cannot be None. + The returned header will be None if unset; in current versions, the header + is used to distinguish for VLM inputs. + The returned object ID will be zero if the message is not associated with + any VLC object. + @param ctx: message context (as passed to the @ref libvlc_log_cb callback). + @return: name object name storage (or None), header object header (or None), line source code file line number storage (or None). + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_get_object', None) or \ + _Cfunction('libvlc_log_get_object', ((1,), (2,), (2,), (1,),), None, + None, Log_ptr, ListPOINTER(ctypes.c_char_p), ListPOINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_uint)) + return f(ctx, id) + +def libvlc_log_unset(p_instance): + '''Unsets the logging callback. + This function deregisters the logging callback for a LibVLC instance. + This is rarely needed as the callback is implicitly unset when the instance + is destroyed. + @note: This function will wait for any pending callbacks invocation to + complete (causing a deadlock if called from within the callback). + @param p_instance: libvlc instance. + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_unset', None) or \ + _Cfunction('libvlc_log_unset', ((1,),), None, + None, Instance) + return f(p_instance) + +def libvlc_log_set(p_instance, cb, data): + '''Sets the logging callback for a LibVLC instance. + This function is thread-safe: it will wait for any pending callbacks + invocation to complete. + @param cb: callback function pointer. + @param data: opaque data pointer for the callback function @note Some log messages (especially debug) are emitted by LibVLC while is being initialized. These messages cannot be captured with this interface. @warning A deadlock may occur if this function is called from the callback. + @param p_instance: libvlc instance. + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_set', None) or \ + _Cfunction('libvlc_log_set', ((1,), (1,), (1,),), None, + None, Instance, LogCb, ctypes.c_void_p) + return f(p_instance, cb, data) + +def libvlc_log_set_file(p_instance, stream): + '''Sets up logging to a file. + @param p_instance: libvlc instance. + @param stream: FILE pointer opened for writing (the FILE pointer must remain valid until L{libvlc_log_unset}()). + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_set_file', None) or \ + _Cfunction('libvlc_log_set_file', ((1,), (1,),), None, + None, Instance, FILE_ptr) + return f(p_instance, stream) + +def libvlc_module_description_list_release(p_list): + '''Release a list of module descriptions. + @param p_list: the list to be released. + ''' + f = _Cfunctions.get('libvlc_module_description_list_release', None) or \ + _Cfunction('libvlc_module_description_list_release', ((1,),), None, + None, ctypes.POINTER(ModuleDescription)) + return f(p_list) + +def libvlc_audio_filter_list_get(p_instance): + '''Returns a list of audio filters that are available. + @param p_instance: libvlc instance. + @return: a list of module descriptions. It should be freed with L{libvlc_module_description_list_release}(). In case of an error, None is returned. See L{ModuleDescription} See L{libvlc_module_description_list_release}. + ''' + f = _Cfunctions.get('libvlc_audio_filter_list_get', None) or \ + _Cfunction('libvlc_audio_filter_list_get', ((1,),), None, + ctypes.POINTER(ModuleDescription), Instance) + return f(p_instance) + +def libvlc_video_filter_list_get(p_instance): + '''Returns a list of video filters that are available. + @param p_instance: libvlc instance. + @return: a list of module descriptions. It should be freed with L{libvlc_module_description_list_release}(). In case of an error, None is returned. See L{ModuleDescription} See L{libvlc_module_description_list_release}. + ''' + f = _Cfunctions.get('libvlc_video_filter_list_get', None) or \ + _Cfunction('libvlc_video_filter_list_get', ((1,),), None, + ctypes.POINTER(ModuleDescription), Instance) + return f(p_instance) + +def libvlc_clock(): + '''Return the current time as defined by LibVLC. The unit is the microsecond. + Time increases monotonically (regardless of time zone changes and RTC + adjustements). + The origin is arbitrary but consistent across the whole system + (e.g. the system uptim, the time since the system was booted). + @note: On systems that support it, the POSIX monotonic clock is used. + ''' + f = _Cfunctions.get('libvlc_clock', None) or \ + _Cfunction('libvlc_clock', (), None, + ctypes.c_int64) + return f() + +def libvlc_media_discoverer_new(p_inst, psz_name): + '''Create a media discoverer object by name. + After this object is created, you should attach to media_list events in + order to be notified of new items discovered. + You need to call L{libvlc_media_discoverer_start}() in order to start the + discovery. + See L{libvlc_media_discoverer_media_list} + See L{libvlc_media_discoverer_event_manager} + See L{libvlc_media_discoverer_start}. + @param p_inst: libvlc instance. + @param psz_name: service name; use L{libvlc_media_discoverer_list_get}() to get a list of the discoverer names available in this libVLC instance. + @return: media discover object or None in case of error. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_new', None) or \ + _Cfunction('libvlc_media_discoverer_new', ((1,), (1,),), class_result(MediaDiscoverer), + ctypes.c_void_p, Instance, ctypes.c_char_p) + return f(p_inst, psz_name) + +def libvlc_media_discoverer_start(p_mdis): + '''Start media discovery. + To stop it, call L{libvlc_media_discoverer_stop}() or + L{libvlc_media_discoverer_list_release}() directly. + See L{libvlc_media_discoverer_stop}. + @param p_mdis: media discover object. + @return: -1 in case of error, 0 otherwise. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_start', None) or \ + _Cfunction('libvlc_media_discoverer_start', ((1,),), None, + ctypes.c_int, MediaDiscoverer) + return f(p_mdis) + +def libvlc_media_discoverer_stop(p_mdis): + '''Stop media discovery. + See L{libvlc_media_discoverer_start}. + @param p_mdis: media discover object. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_stop', None) or \ + _Cfunction('libvlc_media_discoverer_stop', ((1,),), None, + None, MediaDiscoverer) + return f(p_mdis) + +def libvlc_media_discoverer_release(p_mdis): + '''Release media discover object. If the reference count reaches 0, then + the object will be released. + @param p_mdis: media service discover object. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_release', None) or \ + _Cfunction('libvlc_media_discoverer_release', ((1,),), None, + None, MediaDiscoverer) + return f(p_mdis) + +def libvlc_media_discoverer_media_list(p_mdis): + '''Get media service discover media list. + @param p_mdis: media service discover object. + @return: list of media items. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_media_list', None) or \ + _Cfunction('libvlc_media_discoverer_media_list', ((1,),), class_result(MediaList), + ctypes.c_void_p, MediaDiscoverer) + return f(p_mdis) + +def libvlc_media_discoverer_is_running(p_mdis): + '''Query if media service discover object is running. + @param p_mdis: media service discover object. + @return: true if running, false if not \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_is_running', None) or \ + _Cfunction('libvlc_media_discoverer_is_running', ((1,),), None, + ctypes.c_int, MediaDiscoverer) + return f(p_mdis) + +def libvlc_media_discoverer_list_get(p_inst, i_cat, ppp_services): + '''Get media discoverer services by category. + @param p_inst: libvlc instance. + @param i_cat: category of services to fetch. + @param ppp_services: address to store an allocated array of media discoverer services (must be freed with L{libvlc_media_discoverer_list_release}() by the caller) [OUT]. + @return: the number of media discoverer services (0 on error). + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_list_get', None) or \ + _Cfunction('libvlc_media_discoverer_list_get', ((1,), (1,), (1,),), None, + ctypes.c_size_t, Instance, MediaDiscovererCategory, ctypes.POINTER(ctypes.POINTER(MediaDiscovererDescription))) + return f(p_inst, i_cat, ppp_services) + +def libvlc_media_discoverer_list_release(pp_services, i_count): + '''Release an array of media discoverer services. + @param pp_services: array to release. + @param i_count: number of elements in the array. + @version: LibVLC 3.0.0 and later. See L{libvlc_media_discoverer_list_get}(). + ''' + f = _Cfunctions.get('libvlc_media_discoverer_list_release', None) or \ + _Cfunction('libvlc_media_discoverer_list_release', ((1,), (1,),), None, + None, ctypes.POINTER(MediaDiscovererDescription), ctypes.c_size_t) + return f(pp_services, i_count) + +def libvlc_dialog_set_context(p_id, p_context): + '''Associate an opaque pointer with the dialog id. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_dialog_set_context', None) or \ + _Cfunction('libvlc_dialog_set_context', ((1,), (1,),), None, + None, ctypes.c_void_p, ctypes.c_void_p) + return f(p_id, p_context) + +def libvlc_dialog_get_context(p_id): + '''Return the opaque pointer associated with the dialog id. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_dialog_get_context', None) or \ + _Cfunction('libvlc_dialog_get_context', ((1,),), None, + ctypes.c_void_p, ctypes.c_void_p) + return f(p_id) + +def libvlc_dialog_post_login(p_id, psz_username, psz_password, b_store): + '''Post a login answer + After this call, p_id won't be valid anymore + See libvlc_dialog_cbs.pf_display_login. + @param p_id: id of the dialog. + @param psz_username: valid and non empty string. + @param psz_password: valid string (can be empty). + @param b_store: if true, store the credentials. + @return: 0 on success, or -1 on error. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_dialog_post_login', None) or \ + _Cfunction('libvlc_dialog_post_login', ((1,), (1,), (1,), (1,),), None, + ctypes.c_int, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_bool) + return f(p_id, psz_username, psz_password, b_store) + +def libvlc_dialog_post_action(p_id, i_action): + '''Post a question answer + After this call, p_id won't be valid anymore + See libvlc_dialog_cbs.pf_display_question. + @param p_id: id of the dialog. + @param i_action: 1 for action1, 2 for action2. + @return: 0 on success, or -1 on error. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_dialog_post_action', None) or \ + _Cfunction('libvlc_dialog_post_action', ((1,), (1,),), None, + ctypes.c_int, ctypes.c_void_p, ctypes.c_int) + return f(p_id, i_action) + +def libvlc_dialog_dismiss(p_id): + '''Dismiss a dialog + After this call, p_id won't be valid anymore + See libvlc_dialog_cbs.pf_cancel. + @param p_id: id of the dialog. + @return: 0 on success, or -1 on error. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_dialog_dismiss', None) or \ + _Cfunction('libvlc_dialog_dismiss', ((1,),), None, + ctypes.c_int, ctypes.c_void_p) + return f(p_id) + +def libvlc_media_library_new(p_instance): + '''Create an new Media Library object. + @param p_instance: the libvlc instance. + @return: a new object or None on error. + ''' + f = _Cfunctions.get('libvlc_media_library_new', None) or \ + _Cfunction('libvlc_media_library_new', ((1,),), class_result(MediaLibrary), + ctypes.c_void_p, Instance) + return f(p_instance) + +def libvlc_media_library_release(p_mlib): + '''Release media library object. This functions decrements the + reference count of the media library object. If it reaches 0, + then the object will be released. + @param p_mlib: media library object. + ''' + f = _Cfunctions.get('libvlc_media_library_release', None) or \ + _Cfunction('libvlc_media_library_release', ((1,),), None, + None, MediaLibrary) + return f(p_mlib) + +def libvlc_media_library_retain(p_mlib): + '''Retain a reference to a media library object. This function will + increment the reference counting for this object. Use + L{libvlc_media_library_release}() to decrement the reference count. + @param p_mlib: media library object. + ''' + f = _Cfunctions.get('libvlc_media_library_retain', None) or \ + _Cfunction('libvlc_media_library_retain', ((1,),), None, + None, MediaLibrary) + return f(p_mlib) + +def libvlc_media_library_load(p_mlib): + '''Load media library. + @param p_mlib: media library object. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_media_library_load', None) or \ + _Cfunction('libvlc_media_library_load', ((1,),), None, + ctypes.c_int, MediaLibrary) + return f(p_mlib) + +def libvlc_media_library_media_list(p_mlib): + '''Get media library subitems. + @param p_mlib: media library object. + @return: media list subitems. + ''' + f = _Cfunctions.get('libvlc_media_library_media_list', None) or \ + _Cfunction('libvlc_media_library_media_list', ((1,),), class_result(MediaList), + ctypes.c_void_p, MediaLibrary) + return f(p_mlib) + +def libvlc_vlm_release(p_instance): + '''Release the vlm instance related to the given L{Instance}. + @param p_instance: the instance. + ''' + f = _Cfunctions.get('libvlc_vlm_release', None) or \ + _Cfunction('libvlc_vlm_release', ((1,),), None, + None, Instance) + return f(p_instance) + +def libvlc_vlm_add_broadcast(p_instance, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop): + '''Add a broadcast, with one input. + @param p_instance: the instance. + @param psz_name: the name of the new broadcast. + @param psz_input: the input MRL. + @param psz_output: the output MRL (the parameter to the "sout" variable). + @param i_options: number of additional options. + @param ppsz_options: additional options. + @param b_enabled: boolean for enabling the new broadcast. + @param b_loop: Should this broadcast be played in loop ? + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_add_broadcast', None) or \ + _Cfunction('libvlc_vlm_add_broadcast', ((1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ListPOINTER(ctypes.c_char_p), ctypes.c_int, ctypes.c_int) + return f(p_instance, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop) + +def libvlc_vlm_add_vod(p_instance, psz_name, psz_input, i_options, ppsz_options, b_enabled, psz_mux): + '''Add a vod, with one input. + @param p_instance: the instance. + @param psz_name: the name of the new vod media. + @param psz_input: the input MRL. + @param i_options: number of additional options. + @param ppsz_options: additional options. + @param b_enabled: boolean for enabling the new vod. + @param psz_mux: the muxer of the vod media. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_add_vod', None) or \ + _Cfunction('libvlc_vlm_add_vod', ((1,), (1,), (1,), (1,), (1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ListPOINTER(ctypes.c_char_p), ctypes.c_int, ctypes.c_char_p) + return f(p_instance, psz_name, psz_input, i_options, ppsz_options, b_enabled, psz_mux) + +def libvlc_vlm_del_media(p_instance, psz_name): + '''Delete a media (VOD or broadcast). + @param p_instance: the instance. + @param psz_name: the media to delete. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_del_media', None) or \ + _Cfunction('libvlc_vlm_del_media', ((1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p) + return f(p_instance, psz_name) + +def libvlc_vlm_set_enabled(p_instance, psz_name, b_enabled): + '''Enable or disable a media (VOD or broadcast). + @param p_instance: the instance. + @param psz_name: the media to work on. + @param b_enabled: the new status. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_set_enabled', None) or \ + _Cfunction('libvlc_vlm_set_enabled', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, b_enabled) + +def libvlc_vlm_set_output(p_instance, psz_name, psz_output): + '''Set the output for a media. + @param p_instance: the instance. + @param psz_name: the media to work on. + @param psz_output: the output MRL (the parameter to the "sout" variable). + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_set_output', None) or \ + _Cfunction('libvlc_vlm_set_output', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_char_p) + return f(p_instance, psz_name, psz_output) + +def libvlc_vlm_set_input(p_instance, psz_name, psz_input): + '''Set a media's input MRL. This will delete all existing inputs and + add the specified one. + @param p_instance: the instance. + @param psz_name: the media to work on. + @param psz_input: the input MRL. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_set_input', None) or \ + _Cfunction('libvlc_vlm_set_input', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_char_p) + return f(p_instance, psz_name, psz_input) + +def libvlc_vlm_add_input(p_instance, psz_name, psz_input): + '''Add a media's input MRL. This will add the specified one. + @param p_instance: the instance. + @param psz_name: the media to work on. + @param psz_input: the input MRL. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_add_input', None) or \ + _Cfunction('libvlc_vlm_add_input', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_char_p) + return f(p_instance, psz_name, psz_input) + +def libvlc_vlm_set_loop(p_instance, psz_name, b_loop): + '''Set a media's loop status. + @param p_instance: the instance. + @param psz_name: the media to work on. + @param b_loop: the new status. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_set_loop', None) or \ + _Cfunction('libvlc_vlm_set_loop', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, b_loop) + +def libvlc_vlm_set_mux(p_instance, psz_name, psz_mux): + '''Set a media's vod muxer. + @param p_instance: the instance. + @param psz_name: the media to work on. + @param psz_mux: the new muxer. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_set_mux', None) or \ + _Cfunction('libvlc_vlm_set_mux', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_char_p) + return f(p_instance, psz_name, psz_mux) + +def libvlc_vlm_change_media(p_instance, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop): + '''Edit the parameters of a media. This will delete all existing inputs and + add the specified one. + @param p_instance: the instance. + @param psz_name: the name of the new broadcast. + @param psz_input: the input MRL. + @param psz_output: the output MRL (the parameter to the "sout" variable). + @param i_options: number of additional options. + @param ppsz_options: additional options. + @param b_enabled: boolean for enabling the new broadcast. + @param b_loop: Should this broadcast be played in loop ? + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_change_media', None) or \ + _Cfunction('libvlc_vlm_change_media', ((1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ListPOINTER(ctypes.c_char_p), ctypes.c_int, ctypes.c_int) + return f(p_instance, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop) + +def libvlc_vlm_play_media(p_instance, psz_name): + '''Play the named broadcast. + @param p_instance: the instance. + @param psz_name: the name of the broadcast. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_play_media', None) or \ + _Cfunction('libvlc_vlm_play_media', ((1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p) + return f(p_instance, psz_name) + +def libvlc_vlm_stop_media(p_instance, psz_name): + '''Stop the named broadcast. + @param p_instance: the instance. + @param psz_name: the name of the broadcast. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_stop_media', None) or \ + _Cfunction('libvlc_vlm_stop_media', ((1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p) + return f(p_instance, psz_name) + +def libvlc_vlm_pause_media(p_instance, psz_name): + '''Pause the named broadcast. + @param p_instance: the instance. + @param psz_name: the name of the broadcast. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_pause_media', None) or \ + _Cfunction('libvlc_vlm_pause_media', ((1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p) + return f(p_instance, psz_name) + +def libvlc_vlm_seek_media(p_instance, psz_name, f_percentage): + '''Seek in the named broadcast. + @param p_instance: the instance. + @param psz_name: the name of the broadcast. + @param f_percentage: the percentage to seek to. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_seek_media', None) or \ + _Cfunction('libvlc_vlm_seek_media', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_float) + return f(p_instance, psz_name, f_percentage) + +def libvlc_vlm_show_media(p_instance, psz_name): + '''Return information about the named media as a JSON + string representation. + This function is mainly intended for debugging use, + if you want programmatic access to the state of + a vlm_media_instance_t, please use the corresponding + libvlc_vlm_get_media_instance_xxx -functions. + Currently there are no such functions available for + vlm_media_t though. + @param p_instance: the instance. + @param psz_name: the name of the media, if the name is an empty string, all media is described. + @return: string with information about named media, or None on error. + ''' + f = _Cfunctions.get('libvlc_vlm_show_media', None) or \ + _Cfunction('libvlc_vlm_show_media', ((1,), (1,),), string_result, + ctypes.c_void_p, Instance, ctypes.c_char_p) + return f(p_instance, psz_name) + +def libvlc_vlm_get_media_instance_position(p_instance, psz_name, i_instance): + '''Get vlm_media instance position by name or instance id. + @param p_instance: a libvlc instance. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: position as float or -1. on error. + ''' + f = _Cfunctions.get('libvlc_vlm_get_media_instance_position', None) or \ + _Cfunction('libvlc_vlm_get_media_instance_position', ((1,), (1,), (1,),), None, + ctypes.c_float, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, i_instance) + +def libvlc_vlm_get_media_instance_time(p_instance, psz_name, i_instance): + '''Get vlm_media instance time by name or instance id. + @param p_instance: a libvlc instance. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: time as integer or -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_get_media_instance_time', None) or \ + _Cfunction('libvlc_vlm_get_media_instance_time', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, i_instance) + +def libvlc_vlm_get_media_instance_length(p_instance, psz_name, i_instance): + '''Get vlm_media instance length by name or instance id. + @param p_instance: a libvlc instance. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: length of media item or -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_get_media_instance_length', None) or \ + _Cfunction('libvlc_vlm_get_media_instance_length', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, i_instance) + +def libvlc_vlm_get_media_instance_rate(p_instance, psz_name, i_instance): + '''Get vlm_media instance playback rate by name or instance id. + @param p_instance: a libvlc instance. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: playback rate or -1 on error. + ''' + f = _Cfunctions.get('libvlc_vlm_get_media_instance_rate', None) or \ + _Cfunction('libvlc_vlm_get_media_instance_rate', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, i_instance) + +def libvlc_vlm_get_media_instance_title(p_instance, psz_name, i_instance): + '''Get vlm_media instance title number by name or instance id. + @param p_instance: a libvlc instance. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: title as number or -1 on error. + @bug: will always return 0. + ''' + f = _Cfunctions.get('libvlc_vlm_get_media_instance_title', None) or \ + _Cfunction('libvlc_vlm_get_media_instance_title', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, i_instance) + +def libvlc_vlm_get_media_instance_chapter(p_instance, psz_name, i_instance): + '''Get vlm_media instance chapter number by name or instance id. + @param p_instance: a libvlc instance. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: chapter as number or -1 on error. + @bug: will always return 0. + ''' + f = _Cfunctions.get('libvlc_vlm_get_media_instance_chapter', None) or \ + _Cfunction('libvlc_vlm_get_media_instance_chapter', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, i_instance) + +def libvlc_vlm_get_media_instance_seekable(p_instance, psz_name, i_instance): + '''Is libvlc instance seekable ? + @param p_instance: a libvlc instance. + @param psz_name: name of vlm media instance. + @param i_instance: instance id. + @return: 1 if seekable, 0 if not, -1 if media does not exist. + @bug: will always return 0. + ''' + f = _Cfunctions.get('libvlc_vlm_get_media_instance_seekable', None) or \ + _Cfunction('libvlc_vlm_get_media_instance_seekable', ((1,), (1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_name, i_instance) + +def libvlc_vlm_get_event_manager(p_instance): + '''Get libvlc_event_manager from a vlm media. + The p_event_manager is immutable, so you don't have to hold the lock. + @param p_instance: a libvlc instance. + @return: libvlc_event_manager. + ''' + f = _Cfunctions.get('libvlc_vlm_get_event_manager', None) or \ + _Cfunction('libvlc_vlm_get_event_manager', ((1,),), class_result(EventManager), + ctypes.c_void_p, Instance) + return f(p_instance) + +def libvlc_media_new_location(p_instance, psz_mrl): + '''Create a media with a certain given media resource location, + for instance a valid URL. + @note: To refer to a local file with this function, + the file://... URI syntax B{must} be used (see IETF RFC3986). + We recommend using L{libvlc_media_new_path}() instead when dealing with + local files. + See L{libvlc_media_release}. + @param p_instance: the instance. + @param psz_mrl: the media location. + @return: the newly created media or None on error. + ''' + f = _Cfunctions.get('libvlc_media_new_location', None) or \ + _Cfunction('libvlc_media_new_location', ((1,), (1,),), class_result(Media), + ctypes.c_void_p, Instance, ctypes.c_char_p) + return f(p_instance, psz_mrl) + +def libvlc_media_new_path(p_instance, path): + '''Create a media for a certain file path. + See L{libvlc_media_release}. + @param p_instance: the instance. + @param path: local filesystem path. + @return: the newly created media or None on error. + ''' + f = _Cfunctions.get('libvlc_media_new_path', None) or \ + _Cfunction('libvlc_media_new_path', ((1,), (1,),), class_result(Media), + ctypes.c_void_p, Instance, ctypes.c_char_p) + return f(p_instance, path) + +def libvlc_media_new_fd(p_instance, fd): + '''Create a media for an already open file descriptor. + The file descriptor shall be open for reading (or reading and writing). + Regular file descriptors, pipe read descriptors and character device + descriptors (including TTYs) are supported on all platforms. + Block device descriptors are supported where available. + Directory descriptors are supported on systems that provide fdopendir(). + Sockets are supported on all platforms where they are file descriptors, + i.e. all except Windows. + @note: This library will B{not} automatically close the file descriptor + under any circumstance. Nevertheless, a file descriptor can usually only be + rendered once in a media player. To render it a second time, the file + descriptor should probably be rewound to the beginning with lseek(). + See L{libvlc_media_release}. + @param p_instance: the instance. + @param fd: open file descriptor. + @return: the newly created media or None on error. + @version: LibVLC 1.1.5 and later. + ''' + f = _Cfunctions.get('libvlc_media_new_fd', None) or \ + _Cfunction('libvlc_media_new_fd', ((1,), (1,),), class_result(Media), + ctypes.c_void_p, Instance, ctypes.c_int) + return f(p_instance, fd) + +def libvlc_media_new_callbacks(instance, open_cb, read_cb, seek_cb, close_cb, opaque): + '''Create a media with custom callbacks to read the data from. + @param instance: LibVLC instance. + @param open_cb: callback to open the custom bitstream input media. + @param read_cb: callback to read data (must not be None). + @param seek_cb: callback to seek, or None if seeking is not supported. + @param close_cb: callback to close the media, or None if unnecessary. + @param opaque: data pointer for the open callback. + @return: the newly created media or None on error @note If open_cb is None, the opaque pointer will be passed to read_cb, seek_cb and close_cb, and the stream size will be treated as unknown. @note The callbacks may be called asynchronously (from another thread). A single stream instance need not be reentrant. However the open_cb needs to be reentrant if the media is used by multiple player instances. @warning The callbacks may be used until all or any player instances that were supplied the media item are stopped. See L{libvlc_media_release}. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_new_callbacks', None) or \ + _Cfunction('libvlc_media_new_callbacks', ((1,), (1,), (1,), (1,), (1,), (1,),), class_result(Media), + ctypes.c_void_p, Instance, MediaOpenCb, MediaReadCb, MediaSeekCb, MediaCloseCb, ctypes.c_void_p) + return f(instance, open_cb, read_cb, seek_cb, close_cb, opaque) + +def libvlc_media_new_as_node(p_instance, psz_name): + '''Create a media as an empty node with a given name. + See L{libvlc_media_release}. + @param p_instance: the instance. + @param psz_name: the name of the node. + @return: the new empty media or None on error. + ''' + f = _Cfunctions.get('libvlc_media_new_as_node', None) or \ + _Cfunction('libvlc_media_new_as_node', ((1,), (1,),), class_result(Media), + ctypes.c_void_p, Instance, ctypes.c_char_p) + return f(p_instance, psz_name) + +def libvlc_media_add_option(p_md, psz_options): + '''Add an option to the media. + This option will be used to determine how the media_player will + read the media. This allows to use VLC's advanced + reading/streaming options on a per-media basis. + @note: The options are listed in 'vlc --long-help' from the command line, + e.g. "-sout-all". Keep in mind that available options and their semantics + vary across LibVLC versions and builds. + @warning: Not all options affects L{Media} objects: + Specifically, due to architectural issues most audio and video options, + such as text renderer options, have no effects on an individual media. + These options must be set through L{libvlc_new}() instead. + @param p_md: the media descriptor. + @param psz_options: the options (as a string). + ''' + f = _Cfunctions.get('libvlc_media_add_option', None) or \ + _Cfunction('libvlc_media_add_option', ((1,), (1,),), None, + None, Media, ctypes.c_char_p) + return f(p_md, psz_options) + +def libvlc_media_add_option_flag(p_md, psz_options, i_flags): + '''Add an option to the media with configurable flags. + This option will be used to determine how the media_player will + read the media. This allows to use VLC's advanced + reading/streaming options on a per-media basis. + The options are detailed in vlc --long-help, for instance + "--sout-all". Note that all options are not usable on medias: + specifically, due to architectural issues, video-related options + such as text renderer options cannot be set on a single media. They + must be set on the whole libvlc instance instead. + @param p_md: the media descriptor. + @param psz_options: the options (as a string). + @param i_flags: the flags for this option. + ''' + f = _Cfunctions.get('libvlc_media_add_option_flag', None) or \ + _Cfunction('libvlc_media_add_option_flag', ((1,), (1,), (1,),), None, + None, Media, ctypes.c_char_p, ctypes.c_uint) + return f(p_md, psz_options, i_flags) + +def libvlc_media_retain(p_md): + '''Retain a reference to a media descriptor object (libvlc_media_t). Use + L{libvlc_media_release}() to decrement the reference count of a + media descriptor object. + @param p_md: the media descriptor. + ''' + f = _Cfunctions.get('libvlc_media_retain', None) or \ + _Cfunction('libvlc_media_retain', ((1,),), None, + None, Media) + return f(p_md) + +def libvlc_media_release(p_md): + '''Decrement the reference count of a media descriptor object. If the + reference count is 0, then L{libvlc_media_release}() will release the + media descriptor object. It will send out an libvlc_MediaFreed event + to all listeners. If the media descriptor object has been released it + should not be used again. + @param p_md: the media descriptor. + ''' + f = _Cfunctions.get('libvlc_media_release', None) or \ + _Cfunction('libvlc_media_release', ((1,),), None, + None, Media) + return f(p_md) + +def libvlc_media_get_mrl(p_md): + '''Get the media resource locator (mrl) from a media descriptor object. + @param p_md: a media descriptor object. + @return: string with mrl of media descriptor object. + ''' + f = _Cfunctions.get('libvlc_media_get_mrl', None) or \ + _Cfunction('libvlc_media_get_mrl', ((1,),), string_result, + ctypes.c_void_p, Media) + return f(p_md) + +def libvlc_media_duplicate(p_md): + '''Duplicate a media descriptor object. + @param p_md: a media descriptor object. + ''' + f = _Cfunctions.get('libvlc_media_duplicate', None) or \ + _Cfunction('libvlc_media_duplicate', ((1,),), class_result(Media), + ctypes.c_void_p, Media) + return f(p_md) + +def libvlc_media_get_meta(p_md, e_meta): + '''Read the meta of the media. + If the media has not yet been parsed this will return None. + See L{libvlc_media_parse} + See L{libvlc_media_parse_with_options} + See libvlc_MediaMetaChanged. + @param p_md: the media descriptor. + @param e_meta: the meta to read. + @return: the media's meta. + ''' + f = _Cfunctions.get('libvlc_media_get_meta', None) or \ + _Cfunction('libvlc_media_get_meta', ((1,), (1,),), string_result, + ctypes.c_void_p, Media, Meta) + return f(p_md, e_meta) + +def libvlc_media_set_meta(p_md, e_meta, psz_value): + '''Set the meta of the media (this function will not save the meta, call + L{libvlc_media_save_meta} in order to save the meta). + @param p_md: the media descriptor. + @param e_meta: the meta to write. + @param psz_value: the media's meta. + ''' + f = _Cfunctions.get('libvlc_media_set_meta', None) or \ + _Cfunction('libvlc_media_set_meta', ((1,), (1,), (1,),), None, + None, Media, Meta, ctypes.c_char_p) + return f(p_md, e_meta, psz_value) + +def libvlc_media_save_meta(p_md): + '''Save the meta previously set. + @param p_md: the media desriptor. + @return: true if the write operation was successful. + ''' + f = _Cfunctions.get('libvlc_media_save_meta', None) or \ + _Cfunction('libvlc_media_save_meta', ((1,),), None, + ctypes.c_int, Media) + return f(p_md) + +def libvlc_media_get_state(p_md): + '''Get current state of media descriptor object. Possible media states are + libvlc_NothingSpecial=0, libvlc_Opening, libvlc_Playing, libvlc_Paused, + libvlc_Stopped, libvlc_Ended, libvlc_Error. + See libvlc_state_t. + @param p_md: a media descriptor object. + @return: state of media descriptor object. + ''' + f = _Cfunctions.get('libvlc_media_get_state', None) or \ + _Cfunction('libvlc_media_get_state', ((1,),), None, + State, Media) + return f(p_md) + +def libvlc_media_get_stats(p_md, p_stats): + '''Get the current statistics about the media. + @param p_md:: media descriptor object. + @param p_stats:: structure that contain the statistics about the media (this structure must be allocated by the caller). + @return: true if the statistics are available, false otherwise \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_get_stats', None) or \ + _Cfunction('libvlc_media_get_stats', ((1,), (1,),), None, + ctypes.c_int, Media, ctypes.POINTER(MediaStats)) + return f(p_md, p_stats) + +def libvlc_media_subitems(p_md): + '''Get subitems of media descriptor object. This will increment + the reference count of supplied media descriptor object. Use + L{libvlc_media_list_release}() to decrement the reference counting. + @param p_md: media descriptor object. + @return: list of media descriptor subitems or None. + ''' + f = _Cfunctions.get('libvlc_media_subitems', None) or \ + _Cfunction('libvlc_media_subitems', ((1,),), class_result(MediaList), + ctypes.c_void_p, Media) + return f(p_md) + +def libvlc_media_event_manager(p_md): + '''Get event manager from media descriptor object. + NOTE: this function doesn't increment reference counting. + @param p_md: a media descriptor object. + @return: event manager object. + ''' + f = _Cfunctions.get('libvlc_media_event_manager', None) or \ + _Cfunction('libvlc_media_event_manager', ((1,),), class_result(EventManager), + ctypes.c_void_p, Media) + return f(p_md) + +def libvlc_media_get_duration(p_md): + '''Get duration (in ms) of media descriptor object item. + @param p_md: media descriptor object. + @return: duration of media item or -1 on error. + ''' + f = _Cfunctions.get('libvlc_media_get_duration', None) or \ + _Cfunction('libvlc_media_get_duration', ((1,),), None, + ctypes.c_longlong, Media) + return f(p_md) + +def libvlc_media_parse_with_options(p_md, parse_flag, timeout): + '''Parse the media asynchronously with options. + This fetches (local or network) art, meta data and/or tracks information. + This method is the extended version of L{libvlc_media_parse_with_options}(). + To track when this is over you can listen to libvlc_MediaParsedChanged + event. However if this functions returns an error, you will not receive any + events. + It uses a flag to specify parse options (see libvlc_media_parse_flag_t). All + these flags can be combined. By default, media is parsed if it's a local + file. + @note: Parsing can be aborted with L{libvlc_media_parse_stop}(). + See libvlc_MediaParsedChanged + See L{libvlc_media_get_meta} + See L{libvlc_media_tracks_get} + See L{libvlc_media_get_parsed_status} + See libvlc_media_parse_flag_t. + @param p_md: media descriptor object. + @param parse_flag: parse options: + @param timeout: maximum time allowed to preparse the media. If -1, the default "preparse-timeout" option will be used as a timeout. If 0, it will wait indefinitely. If > 0, the timeout will be used (in milliseconds). + @return: -1 in case of error, 0 otherwise. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_parse_with_options', None) or \ + _Cfunction('libvlc_media_parse_with_options', ((1,), (1,), (1,),), None, + ctypes.c_int, Media, MediaParseFlag, ctypes.c_int) + return f(p_md, parse_flag, timeout) + +def libvlc_media_parse_stop(p_md): + '''Stop the parsing of the media + When the media parsing is stopped, the libvlc_MediaParsedChanged event will + be sent with the libvlc_media_parsed_status_timeout status. + See L{libvlc_media_parse_with_options}. + @param p_md: media descriptor object. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_parse_stop', None) or \ + _Cfunction('libvlc_media_parse_stop', ((1,),), None, + None, Media) + return f(p_md) + +def libvlc_media_get_parsed_status(p_md): + '''Get Parsed status for media descriptor object. + See libvlc_MediaParsedChanged + See libvlc_media_parsed_status_t. + @param p_md: media descriptor object. + @return: a value of the libvlc_media_parsed_status_t enum. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_get_parsed_status', None) or \ + _Cfunction('libvlc_media_get_parsed_status', ((1,),), None, + MediaParsedStatus, Media) + return f(p_md) + +def libvlc_media_set_user_data(p_md, p_new_user_data): + '''Sets media descriptor's user_data. user_data is specialized data + accessed by the host application, VLC.framework uses it as a pointer to + an native object that references a L{Media} pointer. + @param p_md: media descriptor object. + @param p_new_user_data: pointer to user data. + ''' + f = _Cfunctions.get('libvlc_media_set_user_data', None) or \ + _Cfunction('libvlc_media_set_user_data', ((1,), (1,),), None, + None, Media, ctypes.c_void_p) + return f(p_md, p_new_user_data) + +def libvlc_media_get_user_data(p_md): + '''Get media descriptor's user_data. user_data is specialized data + accessed by the host application, VLC.framework uses it as a pointer to + an native object that references a L{Media} pointer. + @param p_md: media descriptor object. + ''' + f = _Cfunctions.get('libvlc_media_get_user_data', None) or \ + _Cfunction('libvlc_media_get_user_data', ((1,),), None, + ctypes.c_void_p, Media) + return f(p_md) + +def libvlc_media_tracks_get(p_md, tracks): + '''Get media descriptor's elementary streams description + Note, you need to call L{libvlc_media_parse}() or play the media at least once + before calling this function. + Not doing this will result in an empty array. + @param p_md: media descriptor object. + @param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed with L{libvlc_media_tracks_release}. + @return: the number of Elementary Streams (zero on error). + @version: LibVLC 2.1.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_tracks_get', None) or \ + _Cfunction('libvlc_media_tracks_get', ((1,), (1,),), None, + ctypes.c_uint, Media, ctypes.POINTER(ctypes.POINTER(MediaTrack))) + return f(p_md, tracks) + +def libvlc_media_get_codec_description(i_type, i_codec): + '''Get codec description from media elementary stream. + @param i_type: i_type from L{MediaTrack}. + @param i_codec: i_codec or i_original_fourcc from L{MediaTrack}. + @return: codec description. + @version: LibVLC 3.0.0 and later. See L{MediaTrack}. + ''' + f = _Cfunctions.get('libvlc_media_get_codec_description', None) or \ + _Cfunction('libvlc_media_get_codec_description', ((1,), (1,),), None, + ctypes.c_char_p, TrackType, ctypes.c_uint32) + return f(i_type, i_codec) + +def libvlc_media_tracks_release(p_tracks, i_count): + '''Release media descriptor's elementary streams description array. + @param p_tracks: tracks info array to release. + @param i_count: number of elements in the array. + @version: LibVLC 2.1.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_tracks_release', None) or \ + _Cfunction('libvlc_media_tracks_release', ((1,), (1,),), None, + None, ctypes.POINTER(MediaTrack), ctypes.c_uint) + return f(p_tracks, i_count) + +def libvlc_media_get_type(p_md): + '''Get the media type of the media descriptor object. + @param p_md: media descriptor object. + @return: media type. + @version: LibVLC 3.0.0 and later. See libvlc_media_type_t. + ''' + f = _Cfunctions.get('libvlc_media_get_type', None) or \ + _Cfunction('libvlc_media_get_type', ((1,),), None, + MediaType, Media) + return f(p_md) + +def libvlc_media_slaves_add(p_md, i_type, i_priority, psz_uri): + '''Add a slave to the current media. + A slave is an external input source that may contains an additional subtitle + track (like a .srt) or an additional audio track (like a .ac3). + @note: This function must be called before the media is parsed (via + L{libvlc_media_parse_with_options}()) or before the media is played (via + L{libvlc_media_player_play}()). + @param p_md: media descriptor object. + @param i_type: subtitle or audio. + @param i_priority: from 0 (low priority) to 4 (high priority). + @param psz_uri: Uri of the slave (should contain a valid scheme). + @return: 0 on success, -1 on error. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_slaves_add', None) or \ + _Cfunction('libvlc_media_slaves_add', ((1,), (1,), (1,), (1,),), None, + ctypes.c_int, Media, MediaSlaveType, ctypes.c_int, ctypes.c_char_p) + return f(p_md, i_type, i_priority, psz_uri) + +def libvlc_media_slaves_clear(p_md): + '''Clear all slaves previously added by L{libvlc_media_slaves_add}() or + internally. + @param p_md: media descriptor object. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_slaves_clear', None) or \ + _Cfunction('libvlc_media_slaves_clear', ((1,),), None, + None, Media) + return f(p_md) + +def libvlc_media_slaves_get(p_md, ppp_slaves): + '''Get a media descriptor's slave list + The list will contain slaves parsed by VLC or previously added by + L{libvlc_media_slaves_add}(). The typical use case of this function is to save + a list of slave in a database for a later use. + @param p_md: media descriptor object. + @param ppp_slaves: address to store an allocated array of slaves (must be freed with L{libvlc_media_slaves_release}()) [OUT]. + @return: the number of slaves (zero on error). + @version: LibVLC 3.0.0 and later. See L{libvlc_media_slaves_add}. + ''' + f = _Cfunctions.get('libvlc_media_slaves_get', None) or \ + _Cfunction('libvlc_media_slaves_get', ((1,), (1,),), None, + ctypes.c_int, Media, ctypes.POINTER(ctypes.POINTER(MediaSlave))) + return f(p_md, ppp_slaves) + +def libvlc_media_slaves_release(pp_slaves, i_count): + '''Release a media descriptor's slave list. + @param pp_slaves: slave array to release. + @param i_count: number of elements in the array. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_slaves_release', None) or \ + _Cfunction('libvlc_media_slaves_release', ((1,), (1,),), None, + None, ctypes.POINTER(MediaSlave), ctypes.c_int) + return f(pp_slaves, i_count) + +def libvlc_renderer_item_hold(p_item): + '''Hold a renderer item, i.e. creates a new reference + This functions need to called from the libvlc_RendererDiscovererItemAdded + callback if the libvlc user wants to use this item after. (for display or + for passing it to the mediaplayer for example). + @return: the current item. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_item_hold', None) or \ + _Cfunction('libvlc_renderer_item_hold', ((1,),), None, + ctypes.c_void_p, ctypes.c_void_p) + return f(p_item) + +def libvlc_renderer_item_release(p_item): + '''Releases a renderer item, i.e. decrements its reference counter. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_item_release', None) or \ + _Cfunction('libvlc_renderer_item_release', ((1,),), None, + None, ctypes.c_void_p) + return f(p_item) + +def libvlc_renderer_item_name(p_item): + '''Get the human readable name of a renderer item. + @return: the name of the item (can't be None, must *not* be freed). + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_item_name', None) or \ + _Cfunction('libvlc_renderer_item_name', ((1,),), None, + ctypes.c_char_p, ctypes.c_void_p) + return f(p_item) + +def libvlc_renderer_item_type(p_item): + '''Get the type (not translated) of a renderer item. For now, the type can only + be "chromecast" ("upnp", "airplay" may come later). + @return: the type of the item (can't be None, must *not* be freed). + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_item_type', None) or \ + _Cfunction('libvlc_renderer_item_type', ((1,),), None, + ctypes.c_char_p, ctypes.c_void_p) + return f(p_item) + +def libvlc_renderer_item_icon_uri(p_item): + '''Get the icon uri of a renderer item. + @return: the uri of the item's icon (can be None, must *not* be freed). + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_item_icon_uri', None) or \ + _Cfunction('libvlc_renderer_item_icon_uri', ((1,),), None, + ctypes.c_char_p, ctypes.c_void_p) + return f(p_item) + +def libvlc_renderer_item_flags(p_item): + '''Get the flags of a renderer item + See LIBVLC_RENDERER_CAN_AUDIO + See LIBVLC_RENDERER_CAN_VIDEO. + @return: bitwise flag: capabilities of the renderer, see. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_item_flags', None) or \ + _Cfunction('libvlc_renderer_item_flags', ((1,),), None, + ctypes.c_int, ctypes.c_void_p) + return f(p_item) + +def libvlc_renderer_discoverer_new(p_inst, psz_name): + '''Create a renderer discoverer object by name + After this object is created, you should attach to events in order to be + notified of the discoverer events. + You need to call L{libvlc_renderer_discoverer_start}() in order to start the + discovery. + See L{libvlc_renderer_discoverer_event_manager}() + See L{libvlc_renderer_discoverer_start}(). + @param p_inst: libvlc instance. + @param psz_name: service name; use L{libvlc_renderer_discoverer_list_get}() to get a list of the discoverer names available in this libVLC instance. + @return: media discover object or None in case of error. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_discoverer_new', None) or \ + _Cfunction('libvlc_renderer_discoverer_new', ((1,), (1,),), None, + ctypes.c_void_p, Instance, ctypes.c_char_p) + return f(p_inst, psz_name) + +def libvlc_renderer_discoverer_release(p_rd): + '''Release a renderer discoverer object. + @param p_rd: renderer discoverer object. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_discoverer_release', None) or \ + _Cfunction('libvlc_renderer_discoverer_release', ((1,),), None, + None, ctypes.c_void_p) + return f(p_rd) + +def libvlc_renderer_discoverer_start(p_rd): + '''Start renderer discovery + To stop it, call L{libvlc_renderer_discoverer_stop}() or + L{libvlc_renderer_discoverer_release}() directly. + See L{libvlc_renderer_discoverer_stop}(). + @param p_rd: renderer discoverer object. + @return: -1 in case of error, 0 otherwise. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_discoverer_start', None) or \ + _Cfunction('libvlc_renderer_discoverer_start', ((1,),), None, + ctypes.c_int, ctypes.c_void_p) + return f(p_rd) + +def libvlc_renderer_discoverer_stop(p_rd): + '''Stop renderer discovery. + See L{libvlc_renderer_discoverer_start}(). + @param p_rd: renderer discoverer object. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_discoverer_stop', None) or \ + _Cfunction('libvlc_renderer_discoverer_stop', ((1,),), None, + None, ctypes.c_void_p) + return f(p_rd) + +def libvlc_renderer_discoverer_event_manager(p_rd): + '''Get the event manager of the renderer discoverer + The possible events to attach are @ref libvlc_RendererDiscovererItemAdded + and @ref libvlc_RendererDiscovererItemDeleted. + The @ref libvlc_renderer_item_t struct passed to event callbacks is owned by + VLC, users should take care of holding/releasing this struct for their + internal usage. + See libvlc_event_t.u.renderer_discoverer_item_added.item + See libvlc_event_t.u.renderer_discoverer_item_removed.item. + @return: a valid event manager (can't fail). + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_renderer_discoverer_event_manager', None) or \ + _Cfunction('libvlc_renderer_discoverer_event_manager', ((1,),), class_result(EventManager), + ctypes.c_void_p, ctypes.c_void_p) + return f(p_rd) + +def libvlc_renderer_discoverer_list_get(p_inst, ppp_services): + '''Get media discoverer services + See libvlc_renderer_list_release(). + @param p_inst: libvlc instance. + @param ppp_services: address to store an allocated array of renderer discoverer services (must be freed with libvlc_renderer_list_release() by the caller) [OUT]. + @return: the number of media discoverer services (0 on error). + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_renderer_discoverer_list_get', None) or \ + _Cfunction('libvlc_renderer_discoverer_list_get', ((1,), (1,),), None, + ctypes.c_size_t, Instance, ctypes.POINTER(ctypes.POINTER(RDDescription))) + return f(p_inst, ppp_services) + +def libvlc_renderer_discoverer_list_release(pp_services, i_count): + '''Release an array of media discoverer services + See L{libvlc_renderer_discoverer_list_get}(). + @param pp_services: array to release. + @param i_count: number of elements in the array. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_renderer_discoverer_list_release', None) or \ + _Cfunction('libvlc_renderer_discoverer_list_release', ((1,), (1,),), None, + None, ctypes.POINTER(RDDescription), ctypes.c_size_t) + return f(pp_services, i_count) + +def libvlc_media_list_new(p_instance): + '''Create an empty media list. + @param p_instance: libvlc instance. + @return: empty media list, or None on error. + ''' + f = _Cfunctions.get('libvlc_media_list_new', None) or \ + _Cfunction('libvlc_media_list_new', ((1,),), class_result(MediaList), + ctypes.c_void_p, Instance) + return f(p_instance) + +def libvlc_media_list_release(p_ml): + '''Release media list created with L{libvlc_media_list_new}(). + @param p_ml: a media list created with L{libvlc_media_list_new}(). + ''' + f = _Cfunctions.get('libvlc_media_list_release', None) or \ + _Cfunction('libvlc_media_list_release', ((1,),), None, + None, MediaList) + return f(p_ml) + +def libvlc_media_list_retain(p_ml): + '''Retain reference to a media list. + @param p_ml: a media list created with L{libvlc_media_list_new}(). + ''' + f = _Cfunctions.get('libvlc_media_list_retain', None) or \ + _Cfunction('libvlc_media_list_retain', ((1,),), None, + None, MediaList) + return f(p_ml) + +def libvlc_media_list_set_media(p_ml, p_md): + '''Associate media instance with this media list instance. + If another media instance was present it will be released. + The L{libvlc_media_list_lock} should NOT be held upon entering this function. + @param p_ml: a media list instance. + @param p_md: media instance to add. + ''' + f = _Cfunctions.get('libvlc_media_list_set_media', None) or \ + _Cfunction('libvlc_media_list_set_media', ((1,), (1,),), None, + None, MediaList, Media) + return f(p_ml, p_md) + +def libvlc_media_list_media(p_ml): + '''Get media instance from this media list instance. This action will increase + the refcount on the media instance. + The L{libvlc_media_list_lock} should NOT be held upon entering this function. + @param p_ml: a media list instance. + @return: media instance. + ''' + f = _Cfunctions.get('libvlc_media_list_media', None) or \ + _Cfunction('libvlc_media_list_media', ((1,),), class_result(Media), + ctypes.c_void_p, MediaList) + return f(p_ml) + +def libvlc_media_list_add_media(p_ml, p_md): + '''Add media instance to media list + The L{libvlc_media_list_lock} should be held upon entering this function. + @param p_ml: a media list instance. + @param p_md: a media instance. + @return: 0 on success, -1 if the media list is read-only. + ''' + f = _Cfunctions.get('libvlc_media_list_add_media', None) or \ + _Cfunction('libvlc_media_list_add_media', ((1,), (1,),), None, + ctypes.c_int, MediaList, Media) + return f(p_ml, p_md) + +def libvlc_media_list_insert_media(p_ml, p_md, i_pos): + '''Insert media instance in media list on a position + The L{libvlc_media_list_lock} should be held upon entering this function. + @param p_ml: a media list instance. + @param p_md: a media instance. + @param i_pos: position in array where to insert. + @return: 0 on success, -1 if the media list is read-only. + ''' + f = _Cfunctions.get('libvlc_media_list_insert_media', None) or \ + _Cfunction('libvlc_media_list_insert_media', ((1,), (1,), (1,),), None, + ctypes.c_int, MediaList, Media, ctypes.c_int) + return f(p_ml, p_md, i_pos) + +def libvlc_media_list_remove_index(p_ml, i_pos): + '''Remove media instance from media list on a position + The L{libvlc_media_list_lock} should be held upon entering this function. + @param p_ml: a media list instance. + @param i_pos: position in array where to insert. + @return: 0 on success, -1 if the list is read-only or the item was not found. + ''' + f = _Cfunctions.get('libvlc_media_list_remove_index', None) or \ + _Cfunction('libvlc_media_list_remove_index', ((1,), (1,),), None, + ctypes.c_int, MediaList, ctypes.c_int) + return f(p_ml, i_pos) + +def libvlc_media_list_count(p_ml): + '''Get count on media list items + The L{libvlc_media_list_lock} should be held upon entering this function. + @param p_ml: a media list instance. + @return: number of items in media list. + ''' + f = _Cfunctions.get('libvlc_media_list_count', None) or \ + _Cfunction('libvlc_media_list_count', ((1,),), None, + ctypes.c_int, MediaList) + return f(p_ml) + +def libvlc_media_list_item_at_index(p_ml, i_pos): + '''List media instance in media list at a position + The L{libvlc_media_list_lock} should be held upon entering this function. + @param p_ml: a media list instance. + @param i_pos: position in array where to insert. + @return: media instance at position i_pos, or None if not found. In case of success, L{libvlc_media_retain}() is called to increase the refcount on the media. + ''' + f = _Cfunctions.get('libvlc_media_list_item_at_index', None) or \ + _Cfunction('libvlc_media_list_item_at_index', ((1,), (1,),), class_result(Media), + ctypes.c_void_p, MediaList, ctypes.c_int) + return f(p_ml, i_pos) + +def libvlc_media_list_index_of_item(p_ml, p_md): + '''Find index position of List media instance in media list. + Warning: the function will return the first matched position. + The L{libvlc_media_list_lock} should be held upon entering this function. + @param p_ml: a media list instance. + @param p_md: media instance. + @return: position of media instance or -1 if media not found. + ''' + f = _Cfunctions.get('libvlc_media_list_index_of_item', None) or \ + _Cfunction('libvlc_media_list_index_of_item', ((1,), (1,),), None, + ctypes.c_int, MediaList, Media) + return f(p_ml, p_md) + +def libvlc_media_list_is_readonly(p_ml): + '''This indicates if this media list is read-only from a user point of view. + @param p_ml: media list instance. + @return: 1 on readonly, 0 on readwrite \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_list_is_readonly', None) or \ + _Cfunction('libvlc_media_list_is_readonly', ((1,),), None, + ctypes.c_int, MediaList) + return f(p_ml) + +def libvlc_media_list_lock(p_ml): + '''Get lock on media list items. + @param p_ml: a media list instance. + ''' + f = _Cfunctions.get('libvlc_media_list_lock', None) or \ + _Cfunction('libvlc_media_list_lock', ((1,),), None, + None, MediaList) + return f(p_ml) + +def libvlc_media_list_unlock(p_ml): + '''Release lock on media list items + The L{libvlc_media_list_lock} should be held upon entering this function. + @param p_ml: a media list instance. + ''' + f = _Cfunctions.get('libvlc_media_list_unlock', None) or \ + _Cfunction('libvlc_media_list_unlock', ((1,),), None, + None, MediaList) + return f(p_ml) + +def libvlc_media_list_event_manager(p_ml): + '''Get libvlc_event_manager from this media list instance. + The p_event_manager is immutable, so you don't have to hold the lock. + @param p_ml: a media list instance. + @return: libvlc_event_manager. + ''' + f = _Cfunctions.get('libvlc_media_list_event_manager', None) or \ + _Cfunction('libvlc_media_list_event_manager', ((1,),), class_result(EventManager), + ctypes.c_void_p, MediaList) + return f(p_ml) + +def libvlc_media_player_get_fps(p_mi): + '''Get movie fps rate + This function is provided for backward compatibility. It cannot deal with + multiple video tracks. In LibVLC versions prior to 3.0, it would also fail + if the file format did not convey the frame rate explicitly. + \deprecated Consider using L{libvlc_media_tracks_get}() instead. + @param p_mi: the Media Player. + @return: frames per second (fps) for this playing movie, or 0 if unspecified. + ''' + f = _Cfunctions.get('libvlc_media_player_get_fps', None) or \ + _Cfunction('libvlc_media_player_get_fps', ((1,),), None, + ctypes.c_float, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_agl(p_mi, drawable): + '''\deprecated Use L{libvlc_media_player_set_nsobject}() instead. + ''' + f = _Cfunctions.get('libvlc_media_player_set_agl', None) or \ + _Cfunction('libvlc_media_player_set_agl', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint32) + return f(p_mi, drawable) + +def libvlc_media_player_get_agl(p_mi): + '''\deprecated Use L{libvlc_media_player_get_nsobject}() instead. + ''' + f = _Cfunctions.get('libvlc_media_player_get_agl', None) or \ + _Cfunction('libvlc_media_player_get_agl', ((1,),), None, + ctypes.c_uint32, MediaPlayer) + return f(p_mi) + +def libvlc_track_description_release(p_track_description): + '''\deprecated Use L{libvlc_track_description_list_release}() instead. + ''' + f = _Cfunctions.get('libvlc_track_description_release', None) or \ + _Cfunction('libvlc_track_description_release', ((1,),), None, + None, ctypes.POINTER(TrackDescription)) + return f(p_track_description) + +def libvlc_video_get_height(p_mi): + '''Get current video height. + \deprecated Use L{libvlc_video_get_size}() instead. + @param p_mi: the media player. + @return: the video pixel height or 0 if not applicable. + ''' + f = _Cfunctions.get('libvlc_video_get_height', None) or \ + _Cfunction('libvlc_video_get_height', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_get_width(p_mi): + '''Get current video width. + \deprecated Use L{libvlc_video_get_size}() instead. + @param p_mi: the media player. + @return: the video pixel width or 0 if not applicable. + ''' + f = _Cfunctions.get('libvlc_video_get_width', None) or \ + _Cfunction('libvlc_video_get_width', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_get_title_description(p_mi): + '''Get the description of available titles. + @param p_mi: the media player. + @return: list containing description of available titles. It must be freed with L{libvlc_track_description_list_release}(). + ''' + f = _Cfunctions.get('libvlc_video_get_title_description', None) or \ + _Cfunction('libvlc_video_get_title_description', ((1,),), None, + ctypes.POINTER(TrackDescription), MediaPlayer) + return f(p_mi) + +def libvlc_video_get_chapter_description(p_mi, i_title): + '''Get the description of available chapters for specific title. + @param p_mi: the media player. + @param i_title: selected title. + @return: list containing description of available chapter for title i_title. It must be freed with L{libvlc_track_description_list_release}(). + ''' + f = _Cfunctions.get('libvlc_video_get_chapter_description', None) or \ + _Cfunction('libvlc_video_get_chapter_description', ((1,), (1,),), None, + ctypes.POINTER(TrackDescription), MediaPlayer, ctypes.c_int) + return f(p_mi, i_title) + +def libvlc_video_set_subtitle_file(p_mi, psz_subtitle): + '''Set new video subtitle file. + \deprecated Use L{libvlc_media_player_add_slave}() instead. + @param p_mi: the media player. + @param psz_subtitle: new video subtitle file. + @return: the success status (boolean). + ''' + f = _Cfunctions.get('libvlc_video_set_subtitle_file', None) or \ + _Cfunction('libvlc_video_set_subtitle_file', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_char_p) + return f(p_mi, psz_subtitle) + +def libvlc_toggle_teletext(p_mi): + '''Toggle teletext transparent status on video output. + \deprecated use L{libvlc_video_set_teletext}() instead. + @param p_mi: the media player. + ''' + f = _Cfunctions.get('libvlc_toggle_teletext', None) or \ + _Cfunction('libvlc_toggle_teletext', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_audio_output_device_count(p_instance, psz_audio_output): + '''Backward compatibility stub. Do not use in new code. + \deprecated Use L{libvlc_audio_output_device_list_get}() instead. + @return: always 0. + ''' + f = _Cfunctions.get('libvlc_audio_output_device_count', None) or \ + _Cfunction('libvlc_audio_output_device_count', ((1,), (1,),), None, + ctypes.c_int, Instance, ctypes.c_char_p) + return f(p_instance, psz_audio_output) + +def libvlc_audio_output_device_longname(p_instance, psz_output, i_device): + '''Backward compatibility stub. Do not use in new code. + \deprecated Use L{libvlc_audio_output_device_list_get}() instead. + @return: always None. + ''' + f = _Cfunctions.get('libvlc_audio_output_device_longname', None) or \ + _Cfunction('libvlc_audio_output_device_longname', ((1,), (1,), (1,),), string_result, + ctypes.c_void_p, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_output, i_device) + +def libvlc_audio_output_device_id(p_instance, psz_audio_output, i_device): + '''Backward compatibility stub. Do not use in new code. + \deprecated Use L{libvlc_audio_output_device_list_get}() instead. + @return: always None. + ''' + f = _Cfunctions.get('libvlc_audio_output_device_id', None) or \ + _Cfunction('libvlc_audio_output_device_id', ((1,), (1,), (1,),), string_result, + ctypes.c_void_p, Instance, ctypes.c_char_p, ctypes.c_int) + return f(p_instance, psz_audio_output, i_device) + +def libvlc_media_parse(p_md): + '''Parse a media. + This fetches (local) art, meta data and tracks information. + The method is synchronous. + \deprecated This function could block indefinitely. + Use L{libvlc_media_parse_with_options}() instead + See L{libvlc_media_parse_with_options} + See L{libvlc_media_get_meta} + See L{libvlc_media_get_tracks_info}. + @param p_md: media descriptor object. + ''' + f = _Cfunctions.get('libvlc_media_parse', None) or \ + _Cfunction('libvlc_media_parse', ((1,),), None, + None, Media) + return f(p_md) + +def libvlc_media_parse_async(p_md): + '''Parse a media. + This fetches (local) art, meta data and tracks information. + The method is the asynchronous of L{libvlc_media_parse}(). + To track when this is over you can listen to libvlc_MediaParsedChanged + event. However if the media was already parsed you will not receive this + event. + \deprecated You can't be sure to receive the libvlc_MediaParsedChanged + event (you can wait indefinitely for this event). + Use L{libvlc_media_parse_with_options}() instead + See L{libvlc_media_parse} + See libvlc_MediaParsedChanged + See L{libvlc_media_get_meta} + See L{libvlc_media_get_tracks_info}. + @param p_md: media descriptor object. + ''' + f = _Cfunctions.get('libvlc_media_parse_async', None) or \ + _Cfunction('libvlc_media_parse_async', ((1,),), None, + None, Media) + return f(p_md) + +def libvlc_media_is_parsed(p_md): + '''Return true is the media descriptor object is parsed + \deprecated This can return true in case of failure. + Use L{libvlc_media_get_parsed_status}() instead + See libvlc_MediaParsedChanged. + @param p_md: media descriptor object. + @return: true if media object has been parsed otherwise it returns false \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_is_parsed', None) or \ + _Cfunction('libvlc_media_is_parsed', ((1,),), None, + ctypes.c_int, Media) + return f(p_md) + +def libvlc_media_get_tracks_info(p_md): + '''Get media descriptor's elementary streams description + Note, you need to call L{libvlc_media_parse}() or play the media at least once + before calling this function. + Not doing this will result in an empty array. + \deprecated Use L{libvlc_media_tracks_get}() instead. + @param p_md: media descriptor object. + @param tracks: address to store an allocated array of Elementary Streams descriptions (must be freed by the caller) [OUT]. + @return: the number of Elementary Streams. + ''' + f = _Cfunctions.get('libvlc_media_get_tracks_info', None) or \ + _Cfunction('libvlc_media_get_tracks_info', ((1,), (2,),), None, + ctypes.c_int, Media, ctypes.POINTER(ctypes.c_void_p)) + return f(p_md) + +def libvlc_media_discoverer_new_from_name(p_inst, psz_name): + '''\deprecated Use L{libvlc_media_discoverer_new}() and L{libvlc_media_discoverer_start}(). + ''' + f = _Cfunctions.get('libvlc_media_discoverer_new_from_name', None) or \ + _Cfunction('libvlc_media_discoverer_new_from_name', ((1,), (1,),), class_result(MediaDiscoverer), + ctypes.c_void_p, Instance, ctypes.c_char_p) + return f(p_inst, psz_name) + +def libvlc_media_discoverer_localized_name(p_mdis): + '''Get media service discover object its localized name. + \deprecated Useless, use L{libvlc_media_discoverer_list_get}() to get the + longname of the service discovery. + @param p_mdis: media discover object. + @return: localized name or None if the media_discoverer is not started. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_localized_name', None) or \ + _Cfunction('libvlc_media_discoverer_localized_name', ((1,),), string_result, + ctypes.c_void_p, MediaDiscoverer) + return f(p_mdis) + +def libvlc_media_discoverer_event_manager(p_mdis): + '''Get event manager from media service discover object. + \deprecated Useless, media_discoverer events are only triggered when calling + L{libvlc_media_discoverer_start}() and L{libvlc_media_discoverer_stop}(). + @param p_mdis: media service discover object. + @return: event manager object. + ''' + f = _Cfunctions.get('libvlc_media_discoverer_event_manager', None) or \ + _Cfunction('libvlc_media_discoverer_event_manager', ((1,),), class_result(EventManager), + ctypes.c_void_p, MediaDiscoverer) + return f(p_mdis) + +def libvlc_wait(p_instance): + '''Waits until an interface causes the instance to exit. + You should start at least one interface first, using L{libvlc_add_intf}(). + @param p_instance: the instance @warning This function wastes one thread doing basically nothing. libvlc_set_exit_handler() should be used instead. + ''' + f = _Cfunctions.get('libvlc_wait', None) or \ + _Cfunction('libvlc_wait', ((1,),), None, + None, Instance) + return f(p_instance) + +def libvlc_get_log_verbosity(p_instance): + '''Always returns minus one. + This function is only provided for backward compatibility. + @param p_instance: ignored. + @return: always -1. + ''' + f = _Cfunctions.get('libvlc_get_log_verbosity', None) or \ + _Cfunction('libvlc_get_log_verbosity', ((1,),), None, + ctypes.c_uint, Instance) + return f(p_instance) + +def libvlc_set_log_verbosity(p_instance, level): + '''This function does nothing. + It is only provided for backward compatibility. + @param p_instance: ignored. + @param level: ignored. + ''' + f = _Cfunctions.get('libvlc_set_log_verbosity', None) or \ + _Cfunction('libvlc_set_log_verbosity', ((1,), (1,),), None, + None, Instance, ctypes.c_uint) + return f(p_instance, level) + +def libvlc_log_open(p_instance): + '''This function does nothing useful. + It is only provided for backward compatibility. + @param p_instance: libvlc instance. + @return: an unique pointer or None on error. + ''' + f = _Cfunctions.get('libvlc_log_open', None) or \ + _Cfunction('libvlc_log_open', ((1,),), None, + Log_ptr, Instance) + return f(p_instance) + +def libvlc_log_close(p_log): + '''Frees memory allocated by L{libvlc_log_open}(). + @param p_log: libvlc log instance or None. + ''' + f = _Cfunctions.get('libvlc_log_close', None) or \ + _Cfunction('libvlc_log_close', ((1,),), None, + None, Log_ptr) + return f(p_log) + +def libvlc_log_count(p_log): + '''Always returns zero. + This function is only provided for backward compatibility. + @param p_log: ignored. + @return: always zero. + ''' + f = _Cfunctions.get('libvlc_log_count', None) or \ + _Cfunction('libvlc_log_count', ((1,),), None, + ctypes.c_uint, Log_ptr) + return f(p_log) + +def libvlc_log_clear(p_log): + '''This function does nothing. + It is only provided for backward compatibility. + @param p_log: ignored. + ''' + f = _Cfunctions.get('libvlc_log_clear', None) or \ + _Cfunction('libvlc_log_clear', ((1,),), None, + None, Log_ptr) + return f(p_log) + +def libvlc_log_get_iterator(p_log): + '''This function does nothing useful. + It is only provided for backward compatibility. + @param p_log: ignored. + @return: an unique pointer or None on error or if the parameter was None. + ''' + f = _Cfunctions.get('libvlc_log_get_iterator', None) or \ + _Cfunction('libvlc_log_get_iterator', ((1,),), class_result(LogIterator), + ctypes.c_void_p, Log_ptr) + return f(p_log) + +def libvlc_log_iterator_free(p_iter): + '''Frees memory allocated by L{libvlc_log_get_iterator}(). + @param p_iter: libvlc log iterator or None. + ''' + f = _Cfunctions.get('libvlc_log_iterator_free', None) or \ + _Cfunction('libvlc_log_iterator_free', ((1,),), None, + None, LogIterator) + return f(p_iter) + +def libvlc_log_iterator_has_next(p_iter): + '''Always returns zero. + This function is only provided for backward compatibility. + @param p_iter: ignored. + @return: always zero. + ''' + f = _Cfunctions.get('libvlc_log_iterator_has_next', None) or \ + _Cfunction('libvlc_log_iterator_has_next', ((1,),), None, + ctypes.c_int, LogIterator) + return f(p_iter) + +def libvlc_log_iterator_next(p_iter, p_buf): + '''Always returns None. + This function is only provided for backward compatibility. + @param p_iter: libvlc log iterator or None. + @param p_buf: ignored. + @return: always None. + ''' + f = _Cfunctions.get('libvlc_log_iterator_next', None) or \ + _Cfunction('libvlc_log_iterator_next', ((1,), (1,),), None, + ctypes.POINTER(LogMessage), LogIterator, ctypes.POINTER(LogMessage)) + return f(p_iter, p_buf) + +def libvlc_playlist_play(p_instance, i_id, i_options, ppsz_options): + '''Start playing (if there is any item in the playlist). + Additionnal playlist item options can be specified for addition to the + item before it is played. + @param p_instance: the playlist instance. + @param i_id: the item to play. If this is a negative number, the next item will be selected. Otherwise, the item with the given ID will be played. + @param i_options: the number of options to add to the item. + @param ppsz_options: the options to add to the item. + ''' + f = _Cfunctions.get('libvlc_playlist_play', None) or \ + _Cfunction('libvlc_playlist_play', ((1,), (1,), (1,), (1,),), None, + None, Instance, ctypes.c_int, ctypes.c_int, ListPOINTER(ctypes.c_char_p)) + return f(p_instance, i_id, i_options, ppsz_options) + +def libvlc_media_player_new(p_libvlc_instance): + '''Create an empty Media Player object. + @param p_libvlc_instance: the libvlc instance in which the Media Player should be created. + @return: a new media player object, or None on error. + ''' + f = _Cfunctions.get('libvlc_media_player_new', None) or \ + _Cfunction('libvlc_media_player_new', ((1,),), class_result(MediaPlayer), + ctypes.c_void_p, Instance) + return f(p_libvlc_instance) + +def libvlc_media_player_new_from_media(p_md): + '''Create a Media Player object from a Media. + @param p_md: the media. Afterwards the p_md can be safely destroyed. + @return: a new media player object, or None on error. + ''' + f = _Cfunctions.get('libvlc_media_player_new_from_media', None) or \ + _Cfunction('libvlc_media_player_new_from_media', ((1,),), class_result(MediaPlayer), + ctypes.c_void_p, Media) + return f(p_md) + +def libvlc_media_player_release(p_mi): + '''Release a media_player after use + Decrement the reference count of a media player object. If the + reference count is 0, then L{libvlc_media_player_release}() will + release the media player object. If the media player object + has been released, then it should not be used again. + @param p_mi: the Media Player to free. + ''' + f = _Cfunctions.get('libvlc_media_player_release', None) or \ + _Cfunction('libvlc_media_player_release', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_retain(p_mi): + '''Retain a reference to a media player object. Use + L{libvlc_media_player_release}() to decrement reference count. + @param p_mi: media player object. + ''' + f = _Cfunctions.get('libvlc_media_player_retain', None) or \ + _Cfunction('libvlc_media_player_retain', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_media(p_mi, p_md): + '''Set the media that will be used by the media_player. If any, + previous md will be released. + @param p_mi: the Media Player. + @param p_md: the Media. Afterwards the p_md can be safely destroyed. + ''' + f = _Cfunctions.get('libvlc_media_player_set_media', None) or \ + _Cfunction('libvlc_media_player_set_media', ((1,), (1,),), None, + None, MediaPlayer, Media) + return f(p_mi, p_md) + +def libvlc_media_player_get_media(p_mi): + '''Get the media used by the media_player. + @param p_mi: the Media Player. + @return: the media associated with p_mi, or None if no media is associated. + ''' + f = _Cfunctions.get('libvlc_media_player_get_media', None) or \ + _Cfunction('libvlc_media_player_get_media', ((1,),), class_result(Media), + ctypes.c_void_p, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_event_manager(p_mi): + '''Get the Event Manager from which the media player send event. + @param p_mi: the Media Player. + @return: the event manager associated with p_mi. + ''' + f = _Cfunctions.get('libvlc_media_player_event_manager', None) or \ + _Cfunction('libvlc_media_player_event_manager', ((1,),), class_result(EventManager), + ctypes.c_void_p, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_is_playing(p_mi): + '''is_playing. + @param p_mi: the Media Player. + @return: 1 if the media player is playing, 0 otherwise \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_player_is_playing', None) or \ + _Cfunction('libvlc_media_player_is_playing', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_play(p_mi): + '''Play. + @param p_mi: the Media Player. + @return: 0 if playback started (and was already started), or -1 on error. + ''' + f = _Cfunctions.get('libvlc_media_player_play', None) or \ + _Cfunction('libvlc_media_player_play', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_pause(mp, do_pause): + '''Pause or resume (no effect if there is no media). + @param mp: the Media Player. + @param do_pause: play/resume if zero, pause if non-zero. + @version: LibVLC 1.1.1 or later. + ''' + f = _Cfunctions.get('libvlc_media_player_set_pause', None) or \ + _Cfunction('libvlc_media_player_set_pause', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_int) + return f(mp, do_pause) + +def libvlc_media_player_pause(p_mi): + '''Toggle pause (no effect if there is no media). + @param p_mi: the Media Player. + ''' + f = _Cfunctions.get('libvlc_media_player_pause', None) or \ + _Cfunction('libvlc_media_player_pause', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_stop(p_mi): + '''Stop (no effect if there is no media). + @param p_mi: the Media Player. + ''' + f = _Cfunctions.get('libvlc_media_player_stop', None) or \ + _Cfunction('libvlc_media_player_stop', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_renderer(p_mi, p_item): + '''Set a renderer to the media player + @note: must be called before the first call of L{libvlc_media_player_play}() to + take effect. + See L{libvlc_renderer_discoverer_new}. + @param p_mi: the Media Player. + @param p_item: an item discovered by L{libvlc_renderer_discoverer_start}(). + @return: 0 on success, -1 on error. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_player_set_renderer', None) or \ + _Cfunction('libvlc_media_player_set_renderer', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_void_p) + return f(p_mi, p_item) + +def libvlc_video_set_callbacks(mp, lock, unlock, display, opaque): + '''Set callbacks and private data to render decoded video to a custom area + in memory. + Use L{libvlc_video_set_format}() or L{libvlc_video_set_format_callbacks}() + to configure the decoded format. + @warning: Rendering video into custom memory buffers is considerably less + efficient than rendering in a custom window as normal. + For optimal perfomances, VLC media player renders into a custom window, and + does not use this function and associated callbacks. It is B{highly + recommended} that other LibVLC-based application do likewise. + To embed video in a window, use libvlc_media_player_set_xid() or equivalent + depending on the operating system. + If window embedding does not fit the application use case, then a custom + LibVLC video output display plugin is required to maintain optimal video + rendering performances. + The following limitations affect performance: + - Hardware video decoding acceleration will either be disabled completely, + or require (relatively slow) copy from video/DSP memory to main memory. + - Sub-pictures (subtitles, on-screen display, etc.) must be blent into the + main picture by the CPU instead of the GPU. + - Depending on the video format, pixel format conversion, picture scaling, + cropping and/or picture re-orientation, must be performed by the CPU + instead of the GPU. + - Memory copying is required between LibVLC reference picture buffers and + application buffers (between lock and unlock callbacks). + @param mp: the media player. + @param lock: callback to lock video memory (must not be None). + @param unlock: callback to unlock video memory (or None if not needed). + @param display: callback to display video (or None if not needed). + @param opaque: private pointer for the three callbacks (as first parameter). + @version: LibVLC 1.1.1 or later. + ''' + f = _Cfunctions.get('libvlc_video_set_callbacks', None) or \ + _Cfunction('libvlc_video_set_callbacks', ((1,), (1,), (1,), (1,), (1,),), None, + None, MediaPlayer, VideoLockCb, VideoUnlockCb, VideoDisplayCb, ctypes.c_void_p) + return f(mp, lock, unlock, display, opaque) + +def libvlc_video_set_format(mp, chroma, width, height, pitch): + '''Set decoded video chroma and dimensions. + This only works in combination with L{libvlc_video_set_callbacks}(), + and is mutually exclusive with L{libvlc_video_set_format_callbacks}(). + @param mp: the media player. + @param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV"). + @param width: pixel width. + @param height: pixel height. + @param pitch: line pitch (in bytes). + @version: LibVLC 1.1.1 or later. + @bug: All pixel planes are expected to have the same pitch. To use the YCbCr color space with chrominance subsampling, consider using L{libvlc_video_set_format_callbacks}() instead. + ''' + f = _Cfunctions.get('libvlc_video_set_format', None) or \ + _Cfunction('libvlc_video_set_format', ((1,), (1,), (1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_char_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint) + return f(mp, chroma, width, height, pitch) + +def libvlc_video_set_format_callbacks(mp, setup, cleanup): + '''Set decoded video chroma and dimensions. This only works in combination with + L{libvlc_video_set_callbacks}(). + @param mp: the media player. + @param setup: callback to select the video format (cannot be None). + @param cleanup: callback to release any allocated resources (or None). + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_video_set_format_callbacks', None) or \ + _Cfunction('libvlc_video_set_format_callbacks', ((1,), (1,), (1,),), None, + None, MediaPlayer, VideoFormatCb, VideoCleanupCb) + return f(mp, setup, cleanup) + +def libvlc_media_player_set_nsobject(p_mi, drawable): + '''Set the NSView handler where the media player should render its video output. + Use the vout called "macosx". + The drawable is an NSObject that follow the VLCOpenGLVideoViewEmbedding + protocol: + @code.m + \@protocol VLCOpenGLVideoViewEmbedding + - (void)addVoutSubview:(NSView *)view; + - (void)removeVoutSubview:(NSView *)view; + \@end + @endcode + Or it can be an NSView object. + If you want to use it along with Qt see the QMacCocoaViewContainer. Then + the following code should work: + @code.mm + + NSView *video = [[NSView alloc] init]; + QMacCocoaViewContainer *container = new QMacCocoaViewContainer(video, parent); + L{libvlc_media_player_set_nsobject}(mp, video); + [video release]; + + @endcode + You can find a live example in VLCVideoView in VLCKit.framework. + @param p_mi: the Media Player. + @param drawable: the drawable that is either an NSView or an object following the VLCOpenGLVideoViewEmbedding protocol. + ''' + f = _Cfunctions.get('libvlc_media_player_set_nsobject', None) or \ + _Cfunction('libvlc_media_player_set_nsobject', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_void_p) + return f(p_mi, drawable) + +def libvlc_media_player_get_nsobject(p_mi): + '''Get the NSView handler previously set with L{libvlc_media_player_set_nsobject}(). + @param p_mi: the Media Player. + @return: the NSView handler or 0 if none where set. + ''' + f = _Cfunctions.get('libvlc_media_player_get_nsobject', None) or \ + _Cfunction('libvlc_media_player_get_nsobject', ((1,),), None, + ctypes.c_void_p, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_xwindow(p_mi, drawable): + '''Set an X Window System drawable where the media player should render its + video output. The call takes effect when the playback starts. If it is + already started, it might need to be stopped before changes apply. + If LibVLC was built without X11 output support, then this function has no + effects. + By default, LibVLC will capture input events on the video rendering area. + Use L{libvlc_video_set_mouse_input}() and L{libvlc_video_set_key_input}() to + disable that and deliver events to the parent window / to the application + instead. By design, the X11 protocol delivers input events to only one + recipient. + @warning + The application must call the XInitThreads() function from Xlib before + L{libvlc_new}(), and before any call to XOpenDisplay() directly or via any + other library. Failure to call XInitThreads() will seriously impede LibVLC + performance. Calling XOpenDisplay() before XInitThreads() will eventually + crash the process. That is a limitation of Xlib. + @param p_mi: media player. + @param drawable: X11 window ID @note The specified identifier must correspond to an existing Input/Output class X11 window. Pixmaps are B{not} currently supported. The default X11 server is assumed, i.e. that specified in the DISPLAY environment variable. @warning LibVLC can deal with invalid X11 handle errors, however some display drivers (EGL, GLX, VA and/or VDPAU) can unfortunately not. Thus the window handle must remain valid until playback is stopped, otherwise the process may abort or crash. + @bug No more than one window handle per media player instance can be specified. If the media has multiple simultaneously active video tracks, extra tracks will be rendered into external windows beyond the control of the application. + ''' + f = _Cfunctions.get('libvlc_media_player_set_xwindow', None) or \ + _Cfunction('libvlc_media_player_set_xwindow', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint32) + return f(p_mi, drawable) + +def libvlc_media_player_get_xwindow(p_mi): + '''Get the X Window System window identifier previously set with + L{libvlc_media_player_set_xwindow}(). Note that this will return the identifier + even if VLC is not currently using it (for instance if it is playing an + audio-only input). + @param p_mi: the Media Player. + @return: an X window ID, or 0 if none where set. + ''' + f = _Cfunctions.get('libvlc_media_player_get_xwindow', None) or \ + _Cfunction('libvlc_media_player_get_xwindow', ((1,),), None, + ctypes.c_uint32, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_hwnd(p_mi, drawable): + '''Set a Win32/Win64 API window handle (HWND) where the media player should + render its video output. If LibVLC was built without Win32/Win64 API output + support, then this has no effects. + @param p_mi: the Media Player. + @param drawable: windows handle of the drawable. + ''' + f = _Cfunctions.get('libvlc_media_player_set_hwnd', None) or \ + _Cfunction('libvlc_media_player_set_hwnd', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_void_p) + return f(p_mi, drawable) + +def libvlc_media_player_get_hwnd(p_mi): + '''Get the Windows API window handle (HWND) previously set with + L{libvlc_media_player_set_hwnd}(). The handle will be returned even if LibVLC + is not currently outputting any video to it. + @param p_mi: the Media Player. + @return: a window handle or None if there are none. + ''' + f = _Cfunctions.get('libvlc_media_player_get_hwnd', None) or \ + _Cfunction('libvlc_media_player_get_hwnd', ((1,),), None, + ctypes.c_void_p, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_android_context(p_mi, p_awindow_handler): + '''Set the android context. + @param p_mi: the media player. + @param p_awindow_handler: org.videolan.libvlc.AWindow jobject owned by the org.videolan.libvlc.MediaPlayer class from the libvlc-android project. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_player_set_android_context', None) or \ + _Cfunction('libvlc_media_player_set_android_context', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_void_p) + return f(p_mi, p_awindow_handler) + +def libvlc_media_player_set_evas_object(p_mi, p_evas_object): + '''Set the EFL Evas Object. + @param p_mi: the media player. + @param p_evas_object: a valid EFL Evas Object (Evas_Object). + @return: -1 if an error was detected, 0 otherwise. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_player_set_evas_object', None) or \ + _Cfunction('libvlc_media_player_set_evas_object', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_void_p) + return f(p_mi, p_evas_object) + +def libvlc_audio_set_callbacks(mp, play, pause, resume, flush, drain, opaque): + '''Sets callbacks and private data for decoded audio. + Use L{libvlc_audio_set_format}() or L{libvlc_audio_set_format_callbacks}() + to configure the decoded audio format. + @note: The audio callbacks override any other audio output mechanism. + If the callbacks are set, LibVLC will B{not} output audio in any way. + @param mp: the media player. + @param play: callback to play audio samples (must not be None). + @param pause: callback to pause playback (or None to ignore). + @param resume: callback to resume playback (or None to ignore). + @param flush: callback to flush audio buffers (or None to ignore). + @param drain: callback to drain audio buffers (or None to ignore). + @param opaque: private pointer for the audio callbacks (as first parameter). + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_callbacks', None) or \ + _Cfunction('libvlc_audio_set_callbacks', ((1,), (1,), (1,), (1,), (1,), (1,), (1,),), None, + None, MediaPlayer, AudioPlayCb, AudioPauseCb, AudioResumeCb, AudioFlushCb, AudioDrainCb, ctypes.c_void_p) + return f(mp, play, pause, resume, flush, drain, opaque) + +def libvlc_audio_set_volume_callback(mp, set_volume): + '''Set callbacks and private data for decoded audio. This only works in + combination with L{libvlc_audio_set_callbacks}(). + Use L{libvlc_audio_set_format}() or L{libvlc_audio_set_format_callbacks}() + to configure the decoded audio format. + @param mp: the media player. + @param set_volume: callback to apply audio volume, or None to apply volume in software. + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_volume_callback', None) or \ + _Cfunction('libvlc_audio_set_volume_callback', ((1,), (1,),), None, + None, MediaPlayer, AudioSetVolumeCb) + return f(mp, set_volume) + +def libvlc_audio_set_format_callbacks(mp, setup, cleanup): + '''Sets decoded audio format via callbacks. + This only works in combination with L{libvlc_audio_set_callbacks}(). + @param mp: the media player. + @param setup: callback to select the audio format (cannot be None). + @param cleanup: callback to release any allocated resources (or None). + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_format_callbacks', None) or \ + _Cfunction('libvlc_audio_set_format_callbacks', ((1,), (1,), (1,),), None, + None, MediaPlayer, AudioSetupCb, AudioCleanupCb) + return f(mp, setup, cleanup) + +def libvlc_audio_set_format(mp, format, rate, channels): + '''Sets a fixed decoded audio format. + This only works in combination with L{libvlc_audio_set_callbacks}(), + and is mutually exclusive with L{libvlc_audio_set_format_callbacks}(). + @param mp: the media player. + @param format: a four-characters string identifying the sample format (e.g. "S16N" or "FL32"). + @param rate: sample rate (expressed in Hz). + @param channels: channels count. + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_format', None) or \ + _Cfunction('libvlc_audio_set_format', ((1,), (1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_char_p, ctypes.c_uint, ctypes.c_uint) + return f(mp, format, rate, channels) + +def libvlc_media_player_get_length(p_mi): + '''Get the current movie length (in ms). + @param p_mi: the Media Player. + @return: the movie length (in ms), or -1 if there is no media. + ''' + f = _Cfunctions.get('libvlc_media_player_get_length', None) or \ + _Cfunction('libvlc_media_player_get_length', ((1,),), None, + ctypes.c_longlong, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_get_time(p_mi): + '''Get the current movie time (in ms). + @param p_mi: the Media Player. + @return: the movie time (in ms), or -1 if there is no media. + ''' + f = _Cfunctions.get('libvlc_media_player_get_time', None) or \ + _Cfunction('libvlc_media_player_get_time', ((1,),), None, + ctypes.c_longlong, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_time(p_mi, i_time): + '''Set the movie time (in ms). This has no effect if no media is being played. + Not all formats and protocols support this. + @param p_mi: the Media Player. + @param i_time: the movie time (in ms). + ''' + f = _Cfunctions.get('libvlc_media_player_set_time', None) or \ + _Cfunction('libvlc_media_player_set_time', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_longlong) + return f(p_mi, i_time) + +def libvlc_media_player_get_position(p_mi): + '''Get movie position as percentage between 0.0 and 1.0. + @param p_mi: the Media Player. + @return: movie position, or -1. in case of error. + ''' + f = _Cfunctions.get('libvlc_media_player_get_position', None) or \ + _Cfunction('libvlc_media_player_get_position', ((1,),), None, + ctypes.c_float, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_position(p_mi, f_pos): + '''Set movie position as percentage between 0.0 and 1.0. + This has no effect if playback is not enabled. + This might not work depending on the underlying input format and protocol. + @param p_mi: the Media Player. + @param f_pos: the position. + ''' + f = _Cfunctions.get('libvlc_media_player_set_position', None) or \ + _Cfunction('libvlc_media_player_set_position', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_float) + return f(p_mi, f_pos) + +def libvlc_media_player_set_chapter(p_mi, i_chapter): + '''Set movie chapter (if applicable). + @param p_mi: the Media Player. + @param i_chapter: chapter number to play. + ''' + f = _Cfunctions.get('libvlc_media_player_set_chapter', None) or \ + _Cfunction('libvlc_media_player_set_chapter', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_int) + return f(p_mi, i_chapter) + +def libvlc_media_player_get_chapter(p_mi): + '''Get movie chapter. + @param p_mi: the Media Player. + @return: chapter number currently playing, or -1 if there is no media. + ''' + f = _Cfunctions.get('libvlc_media_player_get_chapter', None) or \ + _Cfunction('libvlc_media_player_get_chapter', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_get_chapter_count(p_mi): + '''Get movie chapter count. + @param p_mi: the Media Player. + @return: number of chapters in movie, or -1. + ''' + f = _Cfunctions.get('libvlc_media_player_get_chapter_count', None) or \ + _Cfunction('libvlc_media_player_get_chapter_count', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_will_play(p_mi): + '''Is the player able to play. + @param p_mi: the Media Player. + @return: boolean \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_player_will_play', None) or \ + _Cfunction('libvlc_media_player_will_play', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_get_chapter_count_for_title(p_mi, i_title): + '''Get title chapter count. + @param p_mi: the Media Player. + @param i_title: title. + @return: number of chapters in title, or -1. + ''' + f = _Cfunctions.get('libvlc_media_player_get_chapter_count_for_title', None) or \ + _Cfunction('libvlc_media_player_get_chapter_count_for_title', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int) + return f(p_mi, i_title) + +def libvlc_media_player_set_title(p_mi, i_title): + '''Set movie title. + @param p_mi: the Media Player. + @param i_title: title number to play. + ''' + f = _Cfunctions.get('libvlc_media_player_set_title', None) or \ + _Cfunction('libvlc_media_player_set_title', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_int) + return f(p_mi, i_title) + +def libvlc_media_player_get_title(p_mi): + '''Get movie title. + @param p_mi: the Media Player. + @return: title number currently playing, or -1. + ''' + f = _Cfunctions.get('libvlc_media_player_get_title', None) or \ + _Cfunction('libvlc_media_player_get_title', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_get_title_count(p_mi): + '''Get movie title count. + @param p_mi: the Media Player. + @return: title number count, or -1. + ''' + f = _Cfunctions.get('libvlc_media_player_get_title_count', None) or \ + _Cfunction('libvlc_media_player_get_title_count', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_previous_chapter(p_mi): + '''Set previous chapter (if applicable). + @param p_mi: the Media Player. + ''' + f = _Cfunctions.get('libvlc_media_player_previous_chapter', None) or \ + _Cfunction('libvlc_media_player_previous_chapter', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_next_chapter(p_mi): + '''Set next chapter (if applicable). + @param p_mi: the Media Player. + ''' + f = _Cfunctions.get('libvlc_media_player_next_chapter', None) or \ + _Cfunction('libvlc_media_player_next_chapter', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_get_rate(p_mi): + '''Get the requested movie play rate. + @warning: Depending on the underlying media, the requested rate may be + different from the real playback rate. + @param p_mi: the Media Player. + @return: movie play rate. + ''' + f = _Cfunctions.get('libvlc_media_player_get_rate', None) or \ + _Cfunction('libvlc_media_player_get_rate', ((1,),), None, + ctypes.c_float, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_rate(p_mi, rate): + '''Set movie play rate. + @param p_mi: the Media Player. + @param rate: movie play rate to set. + @return: -1 if an error was detected, 0 otherwise (but even then, it might not actually work depending on the underlying media protocol). + ''' + f = _Cfunctions.get('libvlc_media_player_set_rate', None) or \ + _Cfunction('libvlc_media_player_set_rate', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_float) + return f(p_mi, rate) + +def libvlc_media_player_get_state(p_mi): + '''Get current movie state. + @param p_mi: the Media Player. + @return: the current state of the media player (playing, paused, ...) See libvlc_state_t. + ''' + f = _Cfunctions.get('libvlc_media_player_get_state', None) or \ + _Cfunction('libvlc_media_player_get_state', ((1,),), None, + State, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_has_vout(p_mi): + '''How many video outputs does this media player have? + @param p_mi: the media player. + @return: the number of video outputs. + ''' + f = _Cfunctions.get('libvlc_media_player_has_vout', None) or \ + _Cfunction('libvlc_media_player_has_vout', ((1,),), None, + ctypes.c_uint, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_is_seekable(p_mi): + '''Is this media player seekable? + @param p_mi: the media player. + @return: true if the media player can seek \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_player_is_seekable', None) or \ + _Cfunction('libvlc_media_player_is_seekable', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_can_pause(p_mi): + '''Can this media player be paused? + @param p_mi: the media player. + @return: true if the media player can pause \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_player_can_pause', None) or \ + _Cfunction('libvlc_media_player_can_pause', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_program_scrambled(p_mi): + '''Check if the current program is scrambled. + @param p_mi: the media player. + @return: true if the current program is scrambled \libvlc_return_bool. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_player_program_scrambled', None) or \ + _Cfunction('libvlc_media_player_program_scrambled', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_next_frame(p_mi): + '''Display the next frame (if supported). + @param p_mi: the media player. + ''' + f = _Cfunctions.get('libvlc_media_player_next_frame', None) or \ + _Cfunction('libvlc_media_player_next_frame', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_navigate(p_mi, navigate): + '''Navigate through DVD Menu. + @param p_mi: the Media Player. + @param navigate: the Navigation mode. + @version: libVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_player_navigate', None) or \ + _Cfunction('libvlc_media_player_navigate', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint) + return f(p_mi, navigate) + +def libvlc_media_player_set_video_title_display(p_mi, position, timeout): + '''Set if, and how, the video title will be shown when media is played. + @param p_mi: the media player. + @param position: position at which to display the title, or libvlc_position_disable to prevent the title from being displayed. + @param timeout: title display timeout in milliseconds (ignored if libvlc_position_disable). + @version: libVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_player_set_video_title_display', None) or \ + _Cfunction('libvlc_media_player_set_video_title_display', ((1,), (1,), (1,),), None, + None, MediaPlayer, Position, ctypes.c_int) + return f(p_mi, position, timeout) + +def libvlc_media_player_add_slave(p_mi, i_type, psz_uri, b_select): + '''Add a slave to the current media player. + @note: If the player is playing, the slave will be added directly. This call + will also update the slave list of the attached L{Media}. + @param p_mi: the media player. + @param i_type: subtitle or audio. + @param psz_uri: Uri of the slave (should contain a valid scheme). + @param b_select: True if this slave should be selected when it's loaded. + @return: 0 on success, -1 on error. + @version: LibVLC 3.0.0 and later. See L{libvlc_media_slaves_add}. + ''' + f = _Cfunctions.get('libvlc_media_player_add_slave', None) or \ + _Cfunction('libvlc_media_player_add_slave', ((1,), (1,), (1,), (1,),), None, + ctypes.c_int, MediaPlayer, MediaSlaveType, ctypes.c_char_p, ctypes.c_bool) + return f(p_mi, i_type, psz_uri, b_select) + +def libvlc_track_description_list_release(p_track_description): + '''Release (free) L{TrackDescription}. + @param p_track_description: the structure to release. + ''' + f = _Cfunctions.get('libvlc_track_description_list_release', None) or \ + _Cfunction('libvlc_track_description_list_release', ((1,),), None, + None, ctypes.POINTER(TrackDescription)) + return f(p_track_description) + +def libvlc_toggle_fullscreen(p_mi): + '''Toggle fullscreen status on non-embedded video outputs. + @warning: The same limitations applies to this function + as to L{libvlc_set_fullscreen}(). + @param p_mi: the media player. + ''' + f = _Cfunctions.get('libvlc_toggle_fullscreen', None) or \ + _Cfunction('libvlc_toggle_fullscreen', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_set_fullscreen(p_mi, b_fullscreen): + '''Enable or disable fullscreen. + @warning: With most window managers, only a top-level windows can be in + full-screen mode. Hence, this function will not operate properly if + L{libvlc_media_player_set_xwindow}() was used to embed the video in a + non-top-level window. In that case, the embedding window must be reparented + to the root window B{before} fullscreen mode is enabled. You will want + to reparent it back to its normal parent when disabling fullscreen. + @param p_mi: the media player. + @param b_fullscreen: boolean for fullscreen status. + ''' + f = _Cfunctions.get('libvlc_set_fullscreen', None) or \ + _Cfunction('libvlc_set_fullscreen', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_int) + return f(p_mi, b_fullscreen) + +def libvlc_get_fullscreen(p_mi): + '''Get current fullscreen status. + @param p_mi: the media player. + @return: the fullscreen status (boolean) \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_get_fullscreen', None) or \ + _Cfunction('libvlc_get_fullscreen', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_set_key_input(p_mi, on): + '''Enable or disable key press events handling, according to the LibVLC hotkeys + configuration. By default and for historical reasons, keyboard events are + handled by the LibVLC video widget. + @note: On X11, there can be only one subscriber for key press and mouse + click events per window. If your application has subscribed to those events + for the X window ID of the video widget, then LibVLC will not be able to + handle key presses and mouse clicks in any case. + @warning: This function is only implemented for X11 and Win32 at the moment. + @param p_mi: the media player. + @param on: true to handle key press events, false to ignore them. + ''' + f = _Cfunctions.get('libvlc_video_set_key_input', None) or \ + _Cfunction('libvlc_video_set_key_input', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint) + return f(p_mi, on) + +def libvlc_video_set_mouse_input(p_mi, on): + '''Enable or disable mouse click events handling. By default, those events are + handled. This is needed for DVD menus to work, as well as a few video + filters such as "puzzle". + See L{libvlc_video_set_key_input}(). + @warning: This function is only implemented for X11 and Win32 at the moment. + @param p_mi: the media player. + @param on: true to handle mouse click events, false to ignore them. + ''' + f = _Cfunctions.get('libvlc_video_set_mouse_input', None) or \ + _Cfunction('libvlc_video_set_mouse_input', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint) + return f(p_mi, on) + +def libvlc_video_get_size(p_mi, num): + '''Get the pixel dimensions of a video. + @param p_mi: media player. + @param num: number of the video (starting from, and most commonly 0). + @return: px pixel width, py pixel height. + ''' + f = _Cfunctions.get('libvlc_video_get_size', None) or \ + _Cfunction('libvlc_video_get_size', ((1,), (1,), (2,), (2,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) + return f(p_mi, num) + +def libvlc_video_get_cursor(p_mi, num): + '''Get the mouse pointer coordinates over a video. + Coordinates are expressed in terms of the decoded video resolution, + B{not} in terms of pixels on the screen/viewport (to get the latter, + you can query your windowing system directly). + Either of the coordinates may be negative or larger than the corresponding + dimension of the video, if the cursor is outside the rendering area. + @warning: The coordinates may be out-of-date if the pointer is not located + on the video rendering area. LibVLC does not track the pointer if it is + outside of the video widget. + @note: LibVLC does not support multiple pointers (it does of course support + multiple input devices sharing the same pointer) at the moment. + @param p_mi: media player. + @param num: number of the video (starting from, and most commonly 0). + @return: px abscissa, py ordinate. + ''' + f = _Cfunctions.get('libvlc_video_get_cursor', None) or \ + _Cfunction('libvlc_video_get_cursor', ((1,), (1,), (2,), (2,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_uint, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) + return f(p_mi, num) + +def libvlc_video_get_scale(p_mi): + '''Get the current video scaling factor. + See also L{libvlc_video_set_scale}(). + @param p_mi: the media player. + @return: the currently configured zoom factor, or 0. if the video is set to fit to the output window/drawable automatically. + ''' + f = _Cfunctions.get('libvlc_video_get_scale', None) or \ + _Cfunction('libvlc_video_get_scale', ((1,),), None, + ctypes.c_float, MediaPlayer) + return f(p_mi) + +def libvlc_video_set_scale(p_mi, f_factor): + '''Set the video scaling factor. That is the ratio of the number of pixels on + screen to the number of pixels in the original decoded video in each + dimension. Zero is a special value; it will adjust the video to the output + window/drawable (in windowed mode) or the entire screen. + Note that not all video outputs support scaling. + @param p_mi: the media player. + @param f_factor: the scaling factor, or zero. + ''' + f = _Cfunctions.get('libvlc_video_set_scale', None) or \ + _Cfunction('libvlc_video_set_scale', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_float) + return f(p_mi, f_factor) + +def libvlc_video_get_aspect_ratio(p_mi): + '''Get current video aspect ratio. + @param p_mi: the media player. + @return: the video aspect ratio or None if unspecified (the result must be released with free() or L{libvlc_free}()). + ''' + f = _Cfunctions.get('libvlc_video_get_aspect_ratio', None) or \ + _Cfunction('libvlc_video_get_aspect_ratio', ((1,),), string_result, + ctypes.c_void_p, MediaPlayer) + return f(p_mi) + +def libvlc_video_set_aspect_ratio(p_mi, psz_aspect): + '''Set new video aspect ratio. + @param p_mi: the media player. + @param psz_aspect: new video aspect-ratio or None to reset to default @note Invalid aspect ratios are ignored. + ''' + f = _Cfunctions.get('libvlc_video_set_aspect_ratio', None) or \ + _Cfunction('libvlc_video_set_aspect_ratio', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_char_p) + return f(p_mi, psz_aspect) + +def libvlc_video_new_viewpoint(): + '''Create a video viewpoint structure. + @return: video viewpoint or None (the result must be released with free() or L{libvlc_free}()). + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_video_new_viewpoint', None) or \ + _Cfunction('libvlc_video_new_viewpoint', (), None, + VideoViewpoint) + return f() + +def libvlc_video_update_viewpoint(p_mi, p_viewpoint, b_absolute): + '''Update the video viewpoint information. + @note: It is safe to call this function before the media player is started. + @param p_mi: the media player. + @param p_viewpoint: video viewpoint allocated via L{libvlc_video_new_viewpoint}(). + @param b_absolute: if true replace the old viewpoint with the new one. If false, increase/decrease it. + @return: -1 in case of error, 0 otherwise @note the values are set asynchronously, it will be used by the next frame displayed. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_video_update_viewpoint', None) or \ + _Cfunction('libvlc_video_update_viewpoint', ((1,), (1,), (1,),), None, + ctypes.c_int, MediaPlayer, VideoViewpoint, ctypes.c_bool) + return f(p_mi, p_viewpoint, b_absolute) + +def libvlc_video_get_spu(p_mi): + '''Get current video subtitle. + @param p_mi: the media player. + @return: the video subtitle selected, or -1 if none. + ''' + f = _Cfunctions.get('libvlc_video_get_spu', None) or \ + _Cfunction('libvlc_video_get_spu', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_get_spu_count(p_mi): + '''Get the number of available video subtitles. + @param p_mi: the media player. + @return: the number of available video subtitles. + ''' + f = _Cfunctions.get('libvlc_video_get_spu_count', None) or \ + _Cfunction('libvlc_video_get_spu_count', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_get_spu_description(p_mi): + '''Get the description of available video subtitles. + @param p_mi: the media player. + @return: list containing description of available video subtitles. It must be freed with L{libvlc_track_description_list_release}(). + ''' + f = _Cfunctions.get('libvlc_video_get_spu_description', None) or \ + _Cfunction('libvlc_video_get_spu_description', ((1,),), None, + ctypes.POINTER(TrackDescription), MediaPlayer) + return f(p_mi) + +def libvlc_video_set_spu(p_mi, i_spu): + '''Set new video subtitle. + @param p_mi: the media player. + @param i_spu: video subtitle track to select (i_id from track description). + @return: 0 on success, -1 if out of range. + ''' + f = _Cfunctions.get('libvlc_video_set_spu', None) or \ + _Cfunction('libvlc_video_set_spu', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int) + return f(p_mi, i_spu) + +def libvlc_video_get_spu_delay(p_mi): + '''Get the current subtitle delay. Positive values means subtitles are being + displayed later, negative values earlier. + @param p_mi: media player. + @return: time (in microseconds) the display of subtitles is being delayed. + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_video_get_spu_delay', None) or \ + _Cfunction('libvlc_video_get_spu_delay', ((1,),), None, + ctypes.c_int64, MediaPlayer) + return f(p_mi) + +def libvlc_video_set_spu_delay(p_mi, i_delay): + '''Set the subtitle delay. This affects the timing of when the subtitle will + be displayed. Positive values result in subtitles being displayed later, + while negative values will result in subtitles being displayed earlier. + The subtitle delay will be reset to zero each time the media changes. + @param p_mi: media player. + @param i_delay: time (in microseconds) the display of subtitles should be delayed. + @return: 0 on success, -1 on error. + @version: LibVLC 2.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_video_set_spu_delay', None) or \ + _Cfunction('libvlc_video_set_spu_delay', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int64) + return f(p_mi, i_delay) + +def libvlc_media_player_get_full_title_descriptions(p_mi, titles): + '''Get the full description of available titles. + @param p_mi: the media player. + @param titles: address to store an allocated array of title descriptions descriptions (must be freed with L{libvlc_title_descriptions_release}() by the caller) [OUT]. + @return: the number of titles (-1 on error). + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_player_get_full_title_descriptions', None) or \ + _Cfunction('libvlc_media_player_get_full_title_descriptions', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.POINTER(ctypes.POINTER(TitleDescription))) + return f(p_mi, titles) + +def libvlc_title_descriptions_release(p_titles, i_count): + '''Release a title description. + @param p_titles: title description array to release. + @param i_count: number of title descriptions to release. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_title_descriptions_release', None) or \ + _Cfunction('libvlc_title_descriptions_release', ((1,), (1,),), None, + None, ctypes.POINTER(TitleDescription), ctypes.c_uint) + return f(p_titles, i_count) + +def libvlc_media_player_get_full_chapter_descriptions(p_mi, i_chapters_of_title, pp_chapters): + '''Get the full description of available chapters. + @param p_mi: the media player. + @param i_chapters_of_title: index of the title to query for chapters (uses current title if set to -1). + @param pp_chapters: address to store an allocated array of chapter descriptions descriptions (must be freed with L{libvlc_chapter_descriptions_release}() by the caller) [OUT]. + @return: the number of chapters (-1 on error). + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_player_get_full_chapter_descriptions', None) or \ + _Cfunction('libvlc_media_player_get_full_chapter_descriptions', ((1,), (1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int, ctypes.POINTER(ctypes.POINTER(ChapterDescription))) + return f(p_mi, i_chapters_of_title, pp_chapters) + +def libvlc_chapter_descriptions_release(p_chapters, i_count): + '''Release a chapter description. + @param p_chapters: chapter description array to release. + @param i_count: number of chapter descriptions to release. + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_chapter_descriptions_release', None) or \ + _Cfunction('libvlc_chapter_descriptions_release', ((1,), (1,),), None, + None, ctypes.POINTER(ChapterDescription), ctypes.c_uint) + return f(p_chapters, i_count) + +def libvlc_video_get_crop_geometry(p_mi): + '''Get current crop filter geometry. + @param p_mi: the media player. + @return: the crop filter geometry or None if unset. + ''' + f = _Cfunctions.get('libvlc_video_get_crop_geometry', None) or \ + _Cfunction('libvlc_video_get_crop_geometry', ((1,),), string_result, + ctypes.c_void_p, MediaPlayer) + return f(p_mi) + +def libvlc_video_set_crop_geometry(p_mi, psz_geometry): + '''Set new crop filter geometry. + @param p_mi: the media player. + @param psz_geometry: new crop filter geometry (None to unset). + ''' + f = _Cfunctions.get('libvlc_video_set_crop_geometry', None) or \ + _Cfunction('libvlc_video_set_crop_geometry', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_char_p) + return f(p_mi, psz_geometry) + +def libvlc_video_get_teletext(p_mi): + '''Get current teletext page requested or 0 if it's disabled. + Teletext is disabled by default, call L{libvlc_video_set_teletext}() to enable + it. + @param p_mi: the media player. + @return: the current teletext page requested. + ''' + f = _Cfunctions.get('libvlc_video_get_teletext', None) or \ + _Cfunction('libvlc_video_get_teletext', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_set_teletext(p_mi, i_page): + '''Set new teletext page to retrieve. + This function can also be used to send a teletext key. + @param p_mi: the media player. + @param i_page: teletex page number requested. This value can be 0 to disable teletext, a number in the range ]0;1000[ to show the requested page, or a \ref libvlc_teletext_key_t. 100 is the default teletext page. + ''' + f = _Cfunctions.get('libvlc_video_set_teletext', None) or \ + _Cfunction('libvlc_video_set_teletext', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_int) + return f(p_mi, i_page) + +def libvlc_video_get_track_count(p_mi): + '''Get number of available video tracks. + @param p_mi: media player. + @return: the number of available video tracks (int). + ''' + f = _Cfunctions.get('libvlc_video_get_track_count', None) or \ + _Cfunction('libvlc_video_get_track_count', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_get_track_description(p_mi): + '''Get the description of available video tracks. + @param p_mi: media player. + @return: list with description of available video tracks, or None on error. It must be freed with L{libvlc_track_description_list_release}(). + ''' + f = _Cfunctions.get('libvlc_video_get_track_description', None) or \ + _Cfunction('libvlc_video_get_track_description', ((1,),), None, + ctypes.POINTER(TrackDescription), MediaPlayer) + return f(p_mi) + +def libvlc_video_get_track(p_mi): + '''Get current video track. + @param p_mi: media player. + @return: the video track ID (int) or -1 if no active input. + ''' + f = _Cfunctions.get('libvlc_video_get_track', None) or \ + _Cfunction('libvlc_video_get_track', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_video_set_track(p_mi, i_track): + '''Set video track. + @param p_mi: media player. + @param i_track: the track ID (i_id field from track description). + @return: 0 on success, -1 if out of range. + ''' + f = _Cfunctions.get('libvlc_video_set_track', None) or \ + _Cfunction('libvlc_video_set_track', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int) + return f(p_mi, i_track) + +def libvlc_video_take_snapshot(p_mi, num, psz_filepath, i_width, i_height): + '''Take a snapshot of the current video window. + If i_width AND i_height is 0, original size is used. + If i_width XOR i_height is 0, original aspect-ratio is preserved. + @param p_mi: media player instance. + @param num: number of video output (typically 0 for the first/only one). + @param psz_filepath: the path of a file or a folder to save the screenshot into. + @param i_width: the snapshot's width. + @param i_height: the snapshot's height. + @return: 0 on success, -1 if the video was not found. + ''' + f = _Cfunctions.get('libvlc_video_take_snapshot', None) or \ + _Cfunction('libvlc_video_take_snapshot', ((1,), (1,), (1,), (1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_uint, ctypes.c_char_p, ctypes.c_int, ctypes.c_int) + return f(p_mi, num, psz_filepath, i_width, i_height) + +def libvlc_video_set_deinterlace(p_mi, psz_mode): + '''Enable or disable deinterlace filter. + @param p_mi: libvlc media player. + @param psz_mode: type of deinterlace filter, None to disable. + ''' + f = _Cfunctions.get('libvlc_video_set_deinterlace', None) or \ + _Cfunction('libvlc_video_set_deinterlace', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_char_p) + return f(p_mi, psz_mode) + +def libvlc_video_get_marquee_int(p_mi, option): + '''Get an integer marquee option value. + @param p_mi: libvlc media player. + @param option: marq option to get See libvlc_video_marquee_int_option_t. + ''' + f = _Cfunctions.get('libvlc_video_get_marquee_int', None) or \ + _Cfunction('libvlc_video_get_marquee_int', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_uint) + return f(p_mi, option) + +def libvlc_video_get_marquee_string(p_mi, option): + '''Get a string marquee option value. + @param p_mi: libvlc media player. + @param option: marq option to get See libvlc_video_marquee_string_option_t. + ''' + f = _Cfunctions.get('libvlc_video_get_marquee_string', None) or \ + _Cfunction('libvlc_video_get_marquee_string', ((1,), (1,),), string_result, + ctypes.c_void_p, MediaPlayer, ctypes.c_uint) + return f(p_mi, option) + +def libvlc_video_set_marquee_int(p_mi, option, i_val): + '''Enable, disable or set an integer marquee option + Setting libvlc_marquee_Enable has the side effect of enabling (arg !0) + or disabling (arg 0) the marq filter. + @param p_mi: libvlc media player. + @param option: marq option to set See libvlc_video_marquee_int_option_t. + @param i_val: marq option value. + ''' + f = _Cfunctions.get('libvlc_video_set_marquee_int', None) or \ + _Cfunction('libvlc_video_set_marquee_int', ((1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint, ctypes.c_int) + return f(p_mi, option, i_val) + +def libvlc_video_set_marquee_string(p_mi, option, psz_text): + '''Set a marquee string option. + @param p_mi: libvlc media player. + @param option: marq option to set See libvlc_video_marquee_string_option_t. + @param psz_text: marq option value. + ''' + f = _Cfunctions.get('libvlc_video_set_marquee_string', None) or \ + _Cfunction('libvlc_video_set_marquee_string', ((1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint, ctypes.c_char_p) + return f(p_mi, option, psz_text) + +def libvlc_video_get_logo_int(p_mi, option): + '''Get integer logo option. + @param p_mi: libvlc media player instance. + @param option: logo option to get, values of libvlc_video_logo_option_t. + ''' + f = _Cfunctions.get('libvlc_video_get_logo_int', None) or \ + _Cfunction('libvlc_video_get_logo_int', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_uint) + return f(p_mi, option) + +def libvlc_video_set_logo_int(p_mi, option, value): + '''Set logo option as integer. Options that take a different type value + are ignored. + Passing libvlc_logo_enable as option value has the side effect of + starting (arg !0) or stopping (arg 0) the logo filter. + @param p_mi: libvlc media player instance. + @param option: logo option to set, values of libvlc_video_logo_option_t. + @param value: logo option value. + ''' + f = _Cfunctions.get('libvlc_video_set_logo_int', None) or \ + _Cfunction('libvlc_video_set_logo_int', ((1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint, ctypes.c_int) + return f(p_mi, option, value) + +def libvlc_video_set_logo_string(p_mi, option, psz_value): + '''Set logo option as string. Options that take a different type value + are ignored. + @param p_mi: libvlc media player instance. + @param option: logo option to set, values of libvlc_video_logo_option_t. + @param psz_value: logo option value. + ''' + f = _Cfunctions.get('libvlc_video_set_logo_string', None) or \ + _Cfunction('libvlc_video_set_logo_string', ((1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint, ctypes.c_char_p) + return f(p_mi, option, psz_value) + +def libvlc_video_get_adjust_int(p_mi, option): + '''Get integer adjust option. + @param p_mi: libvlc media player instance. + @param option: adjust option to get, values of libvlc_video_adjust_option_t. + @version: LibVLC 1.1.1 and later. + ''' + f = _Cfunctions.get('libvlc_video_get_adjust_int', None) or \ + _Cfunction('libvlc_video_get_adjust_int', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_uint) + return f(p_mi, option) + +def libvlc_video_set_adjust_int(p_mi, option, value): + '''Set adjust option as integer. Options that take a different type value + are ignored. + Passing libvlc_adjust_enable as option value has the side effect of + starting (arg !0) or stopping (arg 0) the adjust filter. + @param p_mi: libvlc media player instance. + @param option: adust option to set, values of libvlc_video_adjust_option_t. + @param value: adjust option value. + @version: LibVLC 1.1.1 and later. + ''' + f = _Cfunctions.get('libvlc_video_set_adjust_int', None) or \ + _Cfunction('libvlc_video_set_adjust_int', ((1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint, ctypes.c_int) + return f(p_mi, option, value) + +def libvlc_video_get_adjust_float(p_mi, option): + '''Get float adjust option. + @param p_mi: libvlc media player instance. + @param option: adjust option to get, values of libvlc_video_adjust_option_t. + @version: LibVLC 1.1.1 and later. + ''' + f = _Cfunctions.get('libvlc_video_get_adjust_float', None) or \ + _Cfunction('libvlc_video_get_adjust_float', ((1,), (1,),), None, + ctypes.c_float, MediaPlayer, ctypes.c_uint) + return f(p_mi, option) + +def libvlc_video_set_adjust_float(p_mi, option, value): + '''Set adjust option as float. Options that take a different type value + are ignored. + @param p_mi: libvlc media player instance. + @param option: adust option to set, values of libvlc_video_adjust_option_t. + @param value: adjust option value. + @version: LibVLC 1.1.1 and later. + ''' + f = _Cfunctions.get('libvlc_video_set_adjust_float', None) or \ + _Cfunction('libvlc_video_set_adjust_float', ((1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_uint, ctypes.c_float) + return f(p_mi, option, value) + +def libvlc_audio_output_list_get(p_instance): + '''Gets the list of available audio output modules. + @param p_instance: libvlc instance. + @return: list of available audio outputs. It must be freed with In case of error, None is returned. + ''' + f = _Cfunctions.get('libvlc_audio_output_list_get', None) or \ + _Cfunction('libvlc_audio_output_list_get', ((1,),), None, + ctypes.POINTER(AudioOutput), Instance) + return f(p_instance) + +def libvlc_audio_output_list_release(p_list): + '''Frees the list of available audio output modules. + @param p_list: list with audio outputs for release. + ''' + f = _Cfunctions.get('libvlc_audio_output_list_release', None) or \ + _Cfunction('libvlc_audio_output_list_release', ((1,),), None, + None, ctypes.POINTER(AudioOutput)) + return f(p_list) + +def libvlc_audio_output_set(p_mi, psz_name): + '''Selects an audio output module. + @note: Any change will take be effect only after playback is stopped and + restarted. Audio output cannot be changed while playing. + @param p_mi: media player. + @param psz_name: name of audio output, use psz_name of See L{AudioOutput}. + @return: 0 if function succeeded, -1 on error. + ''' + f = _Cfunctions.get('libvlc_audio_output_set', None) or \ + _Cfunction('libvlc_audio_output_set', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_char_p) + return f(p_mi, psz_name) + +def libvlc_audio_output_device_enum(mp): + '''Gets a list of potential audio output devices, + See L{libvlc_audio_output_device_set}(). + @note: Not all audio outputs support enumerating devices. + The audio output may be functional even if the list is empty (None). + @note: The list may not be exhaustive. + @warning: Some audio output devices in the list might not actually work in + some circumstances. By default, it is recommended to not specify any + explicit audio device. + @param mp: media player. + @return: A None-terminated linked list of potential audio output devices. It must be freed with L{libvlc_audio_output_device_list_release}(). + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_output_device_enum', None) or \ + _Cfunction('libvlc_audio_output_device_enum', ((1,),), None, + ctypes.POINTER(AudioOutputDevice), MediaPlayer) + return f(mp) + +def libvlc_audio_output_device_list_get(p_instance, aout): + '''Gets a list of audio output devices for a given audio output module, + See L{libvlc_audio_output_device_set}(). + @note: Not all audio outputs support this. In particular, an empty (None) + list of devices does B{not} imply that the specified audio output does + not work. + @note: The list might not be exhaustive. + @warning: Some audio output devices in the list might not actually work in + some circumstances. By default, it is recommended to not specify any + explicit audio device. + @param p_instance: libvlc instance. + @param aout: audio output name (as returned by L{libvlc_audio_output_list_get}()). + @return: A None-terminated linked list of potential audio output devices. It must be freed with L{libvlc_audio_output_device_list_release}(). + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_output_device_list_get', None) or \ + _Cfunction('libvlc_audio_output_device_list_get', ((1,), (1,),), None, + ctypes.POINTER(AudioOutputDevice), Instance, ctypes.c_char_p) + return f(p_instance, aout) + +def libvlc_audio_output_device_list_release(p_list): + '''Frees a list of available audio output devices. + @param p_list: list with audio outputs for release. + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_output_device_list_release', None) or \ + _Cfunction('libvlc_audio_output_device_list_release', ((1,),), None, + None, ctypes.POINTER(AudioOutputDevice)) + return f(p_list) + +def libvlc_audio_output_device_set(mp, module, device_id): + '''Configures an explicit audio output device. + If the module paramater is None, audio output will be moved to the device + specified by the device identifier string immediately. This is the + recommended usage. + A list of adequate potential device strings can be obtained with + L{libvlc_audio_output_device_enum}(). + However passing None is supported in LibVLC version 2.2.0 and later only; + in earlier versions, this function would have no effects when the module + parameter was None. + If the module parameter is not None, the device parameter of the + corresponding audio output, if it exists, will be set to the specified + string. Note that some audio output modules do not have such a parameter + (notably MMDevice and PulseAudio). + A list of adequate potential device strings can be obtained with + L{libvlc_audio_output_device_list_get}(). + @note: This function does not select the specified audio output plugin. + L{libvlc_audio_output_set}() is used for that purpose. + @warning: The syntax for the device parameter depends on the audio output. + Some audio output modules require further parameters (e.g. a channels map + in the case of ALSA). + @param mp: media player. + @param module: If None, current audio output module. if non-None, name of audio output module. + @param device_id: device identifier string. + @return: Nothing. Errors are ignored (this is a design bug). + ''' + f = _Cfunctions.get('libvlc_audio_output_device_set', None) or \ + _Cfunction('libvlc_audio_output_device_set', ((1,), (1,), (1,),), None, + None, MediaPlayer, ctypes.c_char_p, ctypes.c_char_p) + return f(mp, module, device_id) + +def libvlc_audio_output_device_get(mp): + '''Get the current audio output device identifier. + This complements L{libvlc_audio_output_device_set}(). + @warning: The initial value for the current audio output device identifier + may not be set or may be some unknown value. A LibVLC application should + compare this value against the known device identifiers (e.g. those that + were previously retrieved by a call to L{libvlc_audio_output_device_enum} or + L{libvlc_audio_output_device_list_get}) to find the current audio output device. + It is possible that the selected audio output device changes (an external + change) without a call to L{libvlc_audio_output_device_set}. That may make this + method unsuitable to use if a LibVLC application is attempting to track + dynamic audio device changes as they happen. + @param mp: media player. + @return: the current audio output device identifier None if no device is selected or in case of error (the result must be released with free() or L{libvlc_free}()). + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_output_device_get', None) or \ + _Cfunction('libvlc_audio_output_device_get', ((1,),), string_result, + ctypes.c_void_p, MediaPlayer) + return f(mp) + +def libvlc_audio_toggle_mute(p_mi): + '''Toggle mute status. + @param p_mi: media player @warning Toggling mute atomically is not always possible: On some platforms, other processes can mute the VLC audio playback stream asynchronously. Thus, there is a small race condition where toggling will not work. See also the limitations of L{libvlc_audio_set_mute}(). + ''' + f = _Cfunctions.get('libvlc_audio_toggle_mute', None) or \ + _Cfunction('libvlc_audio_toggle_mute', ((1,),), None, + None, MediaPlayer) + return f(p_mi) + +def libvlc_audio_get_mute(p_mi): + '''Get current mute status. + @param p_mi: media player. + @return: the mute status (boolean) if defined, -1 if undefined/unapplicable. + ''' + f = _Cfunctions.get('libvlc_audio_get_mute', None) or \ + _Cfunction('libvlc_audio_get_mute', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_audio_set_mute(p_mi, status): + '''Set mute status. + @param p_mi: media player. + @param status: If status is true then mute, otherwise unmute @warning This function does not always work. If there are no active audio playback stream, the mute status might not be available. If digital pass-through (S/PDIF, HDMI...) is in use, muting may be unapplicable. Also some audio output plugins do not support muting at all. @note To force silent playback, disable all audio tracks. This is more efficient and reliable than mute. + ''' + f = _Cfunctions.get('libvlc_audio_set_mute', None) or \ + _Cfunction('libvlc_audio_set_mute', ((1,), (1,),), None, + None, MediaPlayer, ctypes.c_int) + return f(p_mi, status) + +def libvlc_audio_get_volume(p_mi): + '''Get current software audio volume. + @param p_mi: media player. + @return: the software volume in percents (0 = mute, 100 = nominal / 0dB). + ''' + f = _Cfunctions.get('libvlc_audio_get_volume', None) or \ + _Cfunction('libvlc_audio_get_volume', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_audio_set_volume(p_mi, i_volume): + '''Set current software audio volume. + @param p_mi: media player. + @param i_volume: the volume in percents (0 = mute, 100 = 0dB). + @return: 0 if the volume was set, -1 if it was out of range. + ''' + f = _Cfunctions.get('libvlc_audio_set_volume', None) or \ + _Cfunction('libvlc_audio_set_volume', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int) + return f(p_mi, i_volume) + +def libvlc_audio_get_track_count(p_mi): + '''Get number of available audio tracks. + @param p_mi: media player. + @return: the number of available audio tracks (int), or -1 if unavailable. + ''' + f = _Cfunctions.get('libvlc_audio_get_track_count', None) or \ + _Cfunction('libvlc_audio_get_track_count', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_audio_get_track_description(p_mi): + '''Get the description of available audio tracks. + @param p_mi: media player. + @return: list with description of available audio tracks, or None. It must be freed with L{libvlc_track_description_list_release}(). + ''' + f = _Cfunctions.get('libvlc_audio_get_track_description', None) or \ + _Cfunction('libvlc_audio_get_track_description', ((1,),), None, + ctypes.POINTER(TrackDescription), MediaPlayer) + return f(p_mi) + +def libvlc_audio_get_track(p_mi): + '''Get current audio track. + @param p_mi: media player. + @return: the audio track ID or -1 if no active input. + ''' + f = _Cfunctions.get('libvlc_audio_get_track', None) or \ + _Cfunction('libvlc_audio_get_track', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_audio_set_track(p_mi, i_track): + '''Set current audio track. + @param p_mi: media player. + @param i_track: the track ID (i_id field from track description). + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_audio_set_track', None) or \ + _Cfunction('libvlc_audio_set_track', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int) + return f(p_mi, i_track) + +def libvlc_audio_get_channel(p_mi): + '''Get current audio channel. + @param p_mi: media player. + @return: the audio channel See libvlc_audio_output_channel_t. + ''' + f = _Cfunctions.get('libvlc_audio_get_channel', None) or \ + _Cfunction('libvlc_audio_get_channel', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_audio_set_channel(p_mi, channel): + '''Set current audio channel. + @param p_mi: media player. + @param channel: the audio channel, See libvlc_audio_output_channel_t. + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_audio_set_channel', None) or \ + _Cfunction('libvlc_audio_set_channel', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int) + return f(p_mi, channel) + +def libvlc_audio_get_delay(p_mi): + '''Get current audio delay. + @param p_mi: media player. + @return: the audio delay (microseconds). + @version: LibVLC 1.1.1 or later. + ''' + f = _Cfunctions.get('libvlc_audio_get_delay', None) or \ + _Cfunction('libvlc_audio_get_delay', ((1,),), None, + ctypes.c_int64, MediaPlayer) + return f(p_mi) + +def libvlc_audio_set_delay(p_mi, i_delay): + '''Set current audio delay. The audio delay will be reset to zero each time the media changes. + @param p_mi: media player. + @param i_delay: the audio delay (microseconds). + @return: 0 on success, -1 on error. + @version: LibVLC 1.1.1 or later. + ''' + f = _Cfunctions.get('libvlc_audio_set_delay', None) or \ + _Cfunction('libvlc_audio_set_delay', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_int64) + return f(p_mi, i_delay) + +def libvlc_audio_equalizer_get_preset_count(): + '''Get the number of equalizer presets. + @return: number of presets. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_get_preset_count', None) or \ + _Cfunction('libvlc_audio_equalizer_get_preset_count', (), None, + ctypes.c_uint) + return f() + +def libvlc_audio_equalizer_get_preset_name(u_index): + '''Get the name of a particular equalizer preset. + This name can be used, for example, to prepare a preset label or menu in a user + interface. + @param u_index: index of the preset, counting from zero. + @return: preset name, or None if there is no such preset. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_get_preset_name', None) or \ + _Cfunction('libvlc_audio_equalizer_get_preset_name', ((1,),), None, + ctypes.c_char_p, ctypes.c_uint) + return f(u_index) + +def libvlc_audio_equalizer_get_band_count(): + '''Get the number of distinct frequency bands for an equalizer. + @return: number of frequency bands. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_get_band_count', None) or \ + _Cfunction('libvlc_audio_equalizer_get_band_count', (), None, + ctypes.c_uint) + return f() + +def libvlc_audio_equalizer_get_band_frequency(u_index): + '''Get a particular equalizer band frequency. + This value can be used, for example, to create a label for an equalizer band control + in a user interface. + @param u_index: index of the band, counting from zero. + @return: equalizer band frequency (Hz), or -1 if there is no such band. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_get_band_frequency', None) or \ + _Cfunction('libvlc_audio_equalizer_get_band_frequency', ((1,),), None, + ctypes.c_float, ctypes.c_uint) + return f(u_index) + +def libvlc_audio_equalizer_new(): + '''Create a new default equalizer, with all frequency values zeroed. + The new equalizer can subsequently be applied to a media player by invoking + L{libvlc_media_player_set_equalizer}(). + The returned handle should be freed via L{libvlc_audio_equalizer_release}() when + it is no longer needed. + @return: opaque equalizer handle, or None on error. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_new', None) or \ + _Cfunction('libvlc_audio_equalizer_new', (), None, + ctypes.c_void_p) + return f() + +def libvlc_audio_equalizer_new_from_preset(u_index): + '''Create a new equalizer, with initial frequency values copied from an existing + preset. + The new equalizer can subsequently be applied to a media player by invoking + L{libvlc_media_player_set_equalizer}(). + The returned handle should be freed via L{libvlc_audio_equalizer_release}() when + it is no longer needed. + @param u_index: index of the preset, counting from zero. + @return: opaque equalizer handle, or None on error. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_new_from_preset', None) or \ + _Cfunction('libvlc_audio_equalizer_new_from_preset', ((1,),), None, + ctypes.c_void_p, ctypes.c_uint) + return f(u_index) + +def libvlc_audio_equalizer_release(p_equalizer): + '''Release a previously created equalizer instance. + The equalizer was previously created by using L{libvlc_audio_equalizer_new}() or + L{libvlc_audio_equalizer_new_from_preset}(). + It is safe to invoke this method with a None p_equalizer parameter for no effect. + @param p_equalizer: opaque equalizer handle, or None. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_release', None) or \ + _Cfunction('libvlc_audio_equalizer_release', ((1,),), None, + None, ctypes.c_void_p) + return f(p_equalizer) + +def libvlc_audio_equalizer_set_preamp(p_equalizer, f_preamp): + '''Set a new pre-amplification value for an equalizer. + The new equalizer settings are subsequently applied to a media player by invoking + L{libvlc_media_player_set_equalizer}(). + The supplied amplification value will be clamped to the -20.0 to +20.0 range. + @param p_equalizer: valid equalizer handle, must not be None. + @param f_preamp: preamp value (-20.0 to 20.0 Hz). + @return: zero on success, -1 on error. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_set_preamp', None) or \ + _Cfunction('libvlc_audio_equalizer_set_preamp', ((1,), (1,),), None, + ctypes.c_int, ctypes.c_void_p, ctypes.c_float) + return f(p_equalizer, f_preamp) + +def libvlc_audio_equalizer_get_preamp(p_equalizer): + '''Get the current pre-amplification value from an equalizer. + @param p_equalizer: valid equalizer handle, must not be None. + @return: preamp value (Hz). + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_get_preamp', None) or \ + _Cfunction('libvlc_audio_equalizer_get_preamp', ((1,),), None, + ctypes.c_float, ctypes.c_void_p) + return f(p_equalizer) + +def libvlc_audio_equalizer_set_amp_at_index(p_equalizer, f_amp, u_band): + '''Set a new amplification value for a particular equalizer frequency band. + The new equalizer settings are subsequently applied to a media player by invoking + L{libvlc_media_player_set_equalizer}(). + The supplied amplification value will be clamped to the -20.0 to +20.0 range. + @param p_equalizer: valid equalizer handle, must not be None. + @param f_amp: amplification value (-20.0 to 20.0 Hz). + @param u_band: index, counting from zero, of the frequency band to set. + @return: zero on success, -1 on error. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_set_amp_at_index', None) or \ + _Cfunction('libvlc_audio_equalizer_set_amp_at_index', ((1,), (1,), (1,),), None, + ctypes.c_int, ctypes.c_void_p, ctypes.c_float, ctypes.c_uint) + return f(p_equalizer, f_amp, u_band) + +def libvlc_audio_equalizer_get_amp_at_index(p_equalizer, u_band): + '''Get the amplification value for a particular equalizer frequency band. + @param p_equalizer: valid equalizer handle, must not be None. + @param u_band: index, counting from zero, of the frequency band to get. + @return: amplification value (Hz); NaN if there is no such frequency band. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_audio_equalizer_get_amp_at_index', None) or \ + _Cfunction('libvlc_audio_equalizer_get_amp_at_index', ((1,), (1,),), None, + ctypes.c_float, ctypes.c_void_p, ctypes.c_uint) + return f(p_equalizer, u_band) + +def libvlc_media_player_set_equalizer(p_mi, p_equalizer): + '''Apply new equalizer settings to a media player. + The equalizer is first created by invoking L{libvlc_audio_equalizer_new}() or + L{libvlc_audio_equalizer_new_from_preset}(). + It is possible to apply new equalizer settings to a media player whether the media + player is currently playing media or not. + Invoking this method will immediately apply the new equalizer settings to the audio + output of the currently playing media if there is any. + If there is no currently playing media, the new equalizer settings will be applied + later if and when new media is played. + Equalizer settings will automatically be applied to subsequently played media. + To disable the equalizer for a media player invoke this method passing None for the + p_equalizer parameter. + The media player does not keep a reference to the supplied equalizer so it is safe + for an application to release the equalizer reference any time after this method + returns. + @param p_mi: opaque media player handle. + @param p_equalizer: opaque equalizer handle, or None to disable the equalizer for this media player. + @return: zero on success, -1 on error. + @version: LibVLC 2.2.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_player_set_equalizer', None) or \ + _Cfunction('libvlc_media_player_set_equalizer', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_void_p) + return f(p_mi, p_equalizer) + +def libvlc_media_player_get_role(p_mi): + '''Gets the media role. + @param p_mi: media player. + @return: the media player role (\ref libvlc_media_player_role_t). + @version: LibVLC 3.0.0 and later. + ''' + f = _Cfunctions.get('libvlc_media_player_get_role', None) or \ + _Cfunction('libvlc_media_player_get_role', ((1,),), None, + ctypes.c_int, MediaPlayer) + return f(p_mi) + +def libvlc_media_player_set_role(p_mi, role): + '''Sets the media role. + @param p_mi: media player. + @param role: the media player role (\ref libvlc_media_player_role_t). + @return: 0 on success, -1 on error. + ''' + f = _Cfunctions.get('libvlc_media_player_set_role', None) or \ + _Cfunction('libvlc_media_player_set_role', ((1,), (1,),), None, + ctypes.c_int, MediaPlayer, ctypes.c_uint) + return f(p_mi, role) + +def libvlc_media_list_player_new(p_instance): + '''Create new media_list_player. + @param p_instance: libvlc instance. + @return: media list player instance or None on error. + ''' + f = _Cfunctions.get('libvlc_media_list_player_new', None) or \ + _Cfunction('libvlc_media_list_player_new', ((1,),), class_result(MediaListPlayer), + ctypes.c_void_p, Instance) + return f(p_instance) + +def libvlc_media_list_player_release(p_mlp): + '''Release a media_list_player after use + Decrement the reference count of a media player object. If the + reference count is 0, then L{libvlc_media_list_player_release}() will + release the media player object. If the media player object + has been released, then it should not be used again. + @param p_mlp: media list player instance. + ''' + f = _Cfunctions.get('libvlc_media_list_player_release', None) or \ + _Cfunction('libvlc_media_list_player_release', ((1,),), None, + None, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_retain(p_mlp): + '''Retain a reference to a media player list object. Use + L{libvlc_media_list_player_release}() to decrement reference count. + @param p_mlp: media player list object. + ''' + f = _Cfunctions.get('libvlc_media_list_player_retain', None) or \ + _Cfunction('libvlc_media_list_player_retain', ((1,),), None, + None, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_event_manager(p_mlp): + '''Return the event manager of this media_list_player. + @param p_mlp: media list player instance. + @return: the event manager. + ''' + f = _Cfunctions.get('libvlc_media_list_player_event_manager', None) or \ + _Cfunction('libvlc_media_list_player_event_manager', ((1,),), class_result(EventManager), + ctypes.c_void_p, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_set_media_player(p_mlp, p_mi): + '''Replace media player in media_list_player with this instance. + @param p_mlp: media list player instance. + @param p_mi: media player instance. + ''' + f = _Cfunctions.get('libvlc_media_list_player_set_media_player', None) or \ + _Cfunction('libvlc_media_list_player_set_media_player', ((1,), (1,),), None, + None, MediaListPlayer, MediaPlayer) + return f(p_mlp, p_mi) + +def libvlc_media_list_player_get_media_player(p_mlp): + '''Get media player of the media_list_player instance. + @param p_mlp: media list player instance. + @return: media player instance @note the caller is responsible for releasing the returned instance. + ''' + f = _Cfunctions.get('libvlc_media_list_player_get_media_player', None) or \ + _Cfunction('libvlc_media_list_player_get_media_player', ((1,),), class_result(MediaPlayer), + ctypes.c_void_p, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_set_media_list(p_mlp, p_mlist): + '''Set the media list associated with the player. + @param p_mlp: media list player instance. + @param p_mlist: list of media. + ''' + f = _Cfunctions.get('libvlc_media_list_player_set_media_list', None) or \ + _Cfunction('libvlc_media_list_player_set_media_list', ((1,), (1,),), None, + None, MediaListPlayer, MediaList) + return f(p_mlp, p_mlist) + +def libvlc_media_list_player_play(p_mlp): + '''Play media list. + @param p_mlp: media list player instance. + ''' + f = _Cfunctions.get('libvlc_media_list_player_play', None) or \ + _Cfunction('libvlc_media_list_player_play', ((1,),), None, + None, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_pause(p_mlp): + '''Toggle pause (or resume) media list. + @param p_mlp: media list player instance. + ''' + f = _Cfunctions.get('libvlc_media_list_player_pause', None) or \ + _Cfunction('libvlc_media_list_player_pause', ((1,),), None, + None, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_set_pause(p_mlp, do_pause): + '''Pause or resume media list. + @param p_mlp: media list player instance. + @param do_pause: play/resume if zero, pause if non-zero. + @version: LibVLC 3.0.0 or later. + ''' + f = _Cfunctions.get('libvlc_media_list_player_set_pause', None) or \ + _Cfunction('libvlc_media_list_player_set_pause', ((1,), (1,),), None, + None, MediaListPlayer, ctypes.c_int) + return f(p_mlp, do_pause) + +def libvlc_media_list_player_is_playing(p_mlp): + '''Is media list playing? + @param p_mlp: media list player instance. + @return: true for playing and false for not playing \libvlc_return_bool. + ''' + f = _Cfunctions.get('libvlc_media_list_player_is_playing', None) or \ + _Cfunction('libvlc_media_list_player_is_playing', ((1,),), None, + ctypes.c_int, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_get_state(p_mlp): + '''Get current libvlc_state of media list player. + @param p_mlp: media list player instance. + @return: libvlc_state_t for media list player. + ''' + f = _Cfunctions.get('libvlc_media_list_player_get_state', None) or \ + _Cfunction('libvlc_media_list_player_get_state', ((1,),), None, + State, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_play_item_at_index(p_mlp, i_index): + '''Play media list item at position index. + @param p_mlp: media list player instance. + @param i_index: index in media list to play. + @return: 0 upon success -1 if the item wasn't found. + ''' + f = _Cfunctions.get('libvlc_media_list_player_play_item_at_index', None) or \ + _Cfunction('libvlc_media_list_player_play_item_at_index', ((1,), (1,),), None, + ctypes.c_int, MediaListPlayer, ctypes.c_int) + return f(p_mlp, i_index) + +def libvlc_media_list_player_play_item(p_mlp, p_md): + '''Play the given media item. + @param p_mlp: media list player instance. + @param p_md: the media instance. + @return: 0 upon success, -1 if the media is not part of the media list. + ''' + f = _Cfunctions.get('libvlc_media_list_player_play_item', None) or \ + _Cfunction('libvlc_media_list_player_play_item', ((1,), (1,),), None, + ctypes.c_int, MediaListPlayer, Media) + return f(p_mlp, p_md) + +def libvlc_media_list_player_stop(p_mlp): + '''Stop playing media list. + @param p_mlp: media list player instance. + ''' + f = _Cfunctions.get('libvlc_media_list_player_stop', None) or \ + _Cfunction('libvlc_media_list_player_stop', ((1,),), None, + None, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_next(p_mlp): + '''Play next item from media list. + @param p_mlp: media list player instance. + @return: 0 upon success -1 if there is no next item. + ''' + f = _Cfunctions.get('libvlc_media_list_player_next', None) or \ + _Cfunction('libvlc_media_list_player_next', ((1,),), None, + ctypes.c_int, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_previous(p_mlp): + '''Play previous item from media list. + @param p_mlp: media list player instance. + @return: 0 upon success -1 if there is no previous item. + ''' + f = _Cfunctions.get('libvlc_media_list_player_previous', None) or \ + _Cfunction('libvlc_media_list_player_previous', ((1,),), None, + ctypes.c_int, MediaListPlayer) + return f(p_mlp) + +def libvlc_media_list_player_set_playback_mode(p_mlp, e_mode): + '''Sets the playback mode for the playlist. + @param p_mlp: media list player instance. + @param e_mode: playback mode specification. + ''' + f = _Cfunctions.get('libvlc_media_list_player_set_playback_mode', None) or \ + _Cfunction('libvlc_media_list_player_set_playback_mode', ((1,), (1,),), None, + None, MediaListPlayer, PlaybackMode) + return f(p_mlp, e_mode) + + +# 5 function(s) blacklisted: +# libvlc_audio_output_get_device_type +# libvlc_audio_output_set_device_type +# libvlc_dialog_set_callbacks +# libvlc_printerr +# libvlc_set_exit_handler + +# 54 function(s) not wrapped as methods: +# libvlc_audio_equalizer_get_amp_at_index +# libvlc_audio_equalizer_get_band_count +# libvlc_audio_equalizer_get_band_frequency +# libvlc_audio_equalizer_get_preamp +# libvlc_audio_equalizer_get_preset_count +# libvlc_audio_equalizer_get_preset_name +# libvlc_audio_equalizer_new +# libvlc_audio_equalizer_new_from_preset +# libvlc_audio_equalizer_release +# libvlc_audio_equalizer_set_amp_at_index +# libvlc_audio_equalizer_set_preamp +# libvlc_audio_output_device_list_release +# libvlc_audio_output_list_release +# libvlc_chapter_descriptions_release +# libvlc_clearerr +# libvlc_clock +# libvlc_dialog_dismiss +# libvlc_dialog_get_context +# libvlc_dialog_post_action +# libvlc_dialog_post_login +# libvlc_dialog_set_context +# libvlc_event_type_name +# libvlc_free +# libvlc_get_changeset +# libvlc_get_compiler +# libvlc_get_version +# libvlc_log_clear +# libvlc_log_close +# libvlc_log_count +# libvlc_log_get_context +# libvlc_log_get_iterator +# libvlc_log_get_object +# libvlc_media_discoverer_list_release +# libvlc_media_get_codec_description +# libvlc_media_slaves_release +# libvlc_media_tracks_release +# libvlc_module_description_list_release +# libvlc_new +# libvlc_renderer_discoverer_event_manager +# libvlc_renderer_discoverer_list_release +# libvlc_renderer_discoverer_release +# libvlc_renderer_discoverer_start +# libvlc_renderer_discoverer_stop +# libvlc_renderer_item_flags +# libvlc_renderer_item_hold +# libvlc_renderer_item_icon_uri +# libvlc_renderer_item_name +# libvlc_renderer_item_release +# libvlc_renderer_item_type +# libvlc_title_descriptions_release +# libvlc_track_description_list_release +# libvlc_track_description_release +# libvlc_video_new_viewpoint +# libvlc_vprinterr + +# Start of footer.py # + +# Backward compatibility +def callbackmethod(callback): + """Now obsolete @callbackmethod decorator.""" + return callback + +# libvlc_free is not present in some versions of libvlc. If it is not +# in the library, then emulate it by calling libc.free +if not hasattr(dll, 'libvlc_free'): + # need to find the free function in the C runtime. This is + # platform specific. + # For Linux and MacOSX + libc_path = find_library('c') + if libc_path: + libc = ctypes.CDLL(libc_path) + libvlc_free = libc.free + else: + # On win32, it is impossible to guess the proper lib to call + # (msvcrt, mingw...). Just ignore the call: it will memleak, + # but not prevent to run the application. + def libvlc_free(p): + pass + + # ensure argtypes is right, because default type of int won't + # work on 64-bit systems + libvlc_free.argtypes = [ ctypes.c_void_p ] + +# Version functions +def _dot2int(v): + '''(INTERNAL) Convert 'i.i.i[.i]' str to int. + ''' + t = [int(i) for i in v.split('.')] + if len(t) == 3: + t.append(0) + elif len(t) != 4: + raise ValueError('"i.i.i[.i]": %r' % (v,)) + if min(t) < 0 or max(t) > 255: + raise ValueError('[0..255]: %r' % (v,)) + i = t.pop(0) + while t: + i = (i << 8) + t.pop(0) + return i + +def hex_version(): + """Return the version of these bindings in hex or 0 if unavailable. + """ + try: + return _dot2int(__version__) + except (NameError, ValueError): + return 0 + +def libvlc_hex_version(): + """Return the libvlc version in hex or 0 if unavailable. + """ + try: + return _dot2int(bytes_to_str(libvlc_get_version()).split()[0]) + except ValueError: + return 0 + +def debug_callback(event, *args, **kwds): + '''Example callback, useful for debugging. + ''' + l = ['event %s' % (event.type,)] + if args: + l.extend(map(str, args)) + if kwds: + l.extend(sorted('%s=%s' % t for t in kwds.items())) + print('Debug callback (%s)' % ', '.join(l)) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + try: + from msvcrt import getch + except ImportError: + import termios + import tty + + def getch(): # getchar(), getc(stdin) #PYCHOK flake + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + return ch + + def end_callback(event): + print('End of media stream (event %s)' % event.type) + sys.exit(0) + + echo_position = False + def pos_callback(event, player): + if echo_position: + sys.stdout.write('\r%s to %.2f%% (%.2f%%)' % (event.type, + event.u.new_position * 100, + player.get_position() * 100)) + sys.stdout.flush() + + def print_version(): + """Print version of this vlc.py and of the libvlc""" + try: + print('Build date: %s (%#x)' % (build_date, hex_version())) + print('LibVLC version: %s (%#x)' % (bytes_to_str(libvlc_get_version()), libvlc_hex_version())) + print('LibVLC compiler: %s' % bytes_to_str(libvlc_get_compiler())) + if plugin_path: + print('Plugin path: %s' % plugin_path) + except: + print('Error: %s' % sys.exc_info()[1]) + + if sys.argv[1:] and '-h' not in sys.argv[1:] and '--help' not in sys.argv[1:]: + + movie = os.path.expanduser(sys.argv.pop()) + if not os.access(movie, os.R_OK): + print('Error: %s file not readable' % movie) + sys.exit(1) + + # Need --sub-source=marq in order to use marquee below + instance = Instance(["--sub-source=marq"] + sys.argv[1:]) + try: + media = instance.media_new(movie) + except (AttributeError, NameError) as e: + print('%s: %s (%s %s vs LibVLC %s)' % (e.__class__.__name__, e, + sys.argv[0], __version__, + libvlc_get_version())) + sys.exit(1) + player = instance.media_player_new() + player.set_media(media) + player.play() + + # Some marquee examples. Marquee requires '--sub-source marq' in the + # Instance() call above, see + player.video_set_marquee_int(VideoMarqueeOption.Enable, 1) + player.video_set_marquee_int(VideoMarqueeOption.Size, 24) # pixels + player.video_set_marquee_int(VideoMarqueeOption.Position, Position.Bottom) + if False: # only one marquee can be specified + player.video_set_marquee_int(VideoMarqueeOption.Timeout, 5000) # millisec, 0==forever + t = media.get_mrl() # movie + else: # update marquee text periodically + player.video_set_marquee_int(VideoMarqueeOption.Timeout, 0) # millisec, 0==forever + player.video_set_marquee_int(VideoMarqueeOption.Refresh, 1000) # millisec (or sec?) + ##t = '$L / $D or $P at $T' + t = '%Y-%m-%d %H:%M:%S' + player.video_set_marquee_string(VideoMarqueeOption.Text, str_to_bytes(t)) + + # Some event manager examples. Note, the callback can be any Python + # callable and does not need to be decorated. Optionally, specify + # any number of positional and/or keyword arguments to be passed + # to the callback (in addition to the first one, an Event instance). + event_manager = player.event_manager() + event_manager.event_attach(EventType.MediaPlayerEndReached, end_callback) + event_manager.event_attach(EventType.MediaPlayerPositionChanged, pos_callback, player) + + def mspf(): + """Milliseconds per frame""" + return int(1000 // (player.get_fps() or 25)) + + def print_info(): + """Print information about the media""" + try: + print_version() + media = player.get_media() + print('State: %s' % player.get_state()) + print('Media: %s' % bytes_to_str(media.get_mrl())) + print('Track: %s/%s' % (player.video_get_track(), player.video_get_track_count())) + print('Current time: %s/%s' % (player.get_time(), media.get_duration())) + print('Position: %s' % player.get_position()) + print('FPS: %s (%d ms)' % (player.get_fps(), mspf())) + print('Rate: %s' % player.get_rate()) + print('Video size: %s' % str(player.video_get_size(0))) # num=0 + print('Scale: %s' % player.video_get_scale()) + print('Aspect ratio: %s' % player.video_get_aspect_ratio()) + #print('Window:' % player.get_hwnd() + except Exception: + print('Error: %s' % sys.exc_info()[1]) + + def sec_forward(): + """Go forward one sec""" + player.set_time(player.get_time() + 1000) + + def sec_backward(): + """Go backward one sec""" + player.set_time(player.get_time() - 1000) + + def frame_forward(): + """Go forward one frame""" + player.set_time(player.get_time() + mspf()) + + def frame_backward(): + """Go backward one frame""" + player.set_time(player.get_time() - mspf()) + + def print_help(): + """Print help""" + print('Single-character commands:') + for k, m in sorted(keybindings.items()): + m = (m.__doc__ or m.__name__).splitlines()[0] + print(' %s: %s.' % (k, m.rstrip('.'))) + print('0-9: go to that fraction of the movie') + + def quit_app(): + """Stop and exit""" + sys.exit(0) + + def toggle_echo_position(): + """Toggle echoing of media position""" + global echo_position + echo_position = not echo_position + + keybindings = { + ' ': player.pause, + '+': sec_forward, + '-': sec_backward, + '.': frame_forward, + ',': frame_backward, + 'f': player.toggle_fullscreen, + 'i': print_info, + 'p': toggle_echo_position, + 'q': quit_app, + '?': print_help, + } + + print('Press q to quit, ? to get help.%s' % os.linesep) + while True: + k = getch() + print('> %s' % k) + if k in keybindings: + keybindings[k]() + elif k.isdigit(): + # jump to fraction of the movie. + player.set_position(float('0.'+k)) + + else: + print('Usage: %s [options] ' % sys.argv[0]) + print('Once launched, type ? for help.') + print('') + print_version() diff --git a/RPI Code/pyparrot_/pyparrot/readthedocs.yml b/RPI Code/pyparrot_/pyparrot/readthedocs.yml new file mode 100644 index 0000000..75db847 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/readthedocs.yml @@ -0,0 +1,8 @@ +# .readthedocs.yml + +build: + image: latest + +python: + version: 3.6 + setup_py_install: true diff --git a/RPI Code/pyparrot_/pyparrot/setup.py b/RPI Code/pyparrot_/pyparrot/setup.py new file mode 100644 index 0000000..7010d61 --- /dev/null +++ b/RPI Code/pyparrot_/pyparrot/setup.py @@ -0,0 +1,203 @@ +""" +Setup for pyparrot based on the sample one found below: + +A setuptools based setup module. +See: +https://packaging.python.org/en/latest/distributing.html +https://github.com/pypa/sampleproject +""" + +# Always prefer setuptools over distutils +from setuptools import setup, find_packages +# To use a consistent encoding +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +# Arguments marked as "Required" below must be included for upload to PyPI. +# Fields marked as "Optional" may be commented out. + +setup( + # This is the name of your project. The first time you publish this + # package, this name will be registered for you. It will determine how + # users can install this project, e.g.: + # + # $ pip install sampleproject + # + # And where it will live on PyPI: https://pypi.org/project/sampleproject/ + # + # There are some restrictions on what makes a valid project name + # specification here: + # https://packaging.python.org/specifications/core-metadata/#name + name='pyparrot', # Required + + # Versions should comply with PEP 440: + # https://www.python.org/dev/peps/pep-0440/ + # + # For a discussion on single-sourcing the version across setup.py and the + # project code, see + # https://packaging.python.org/en/latest/single_source_version.html + version='1.5.14', # Required + + # This is a one-line description or tagline of what your project does. This + # corresponds to the "Summary" metadata field: + # https://packaging.python.org/specifications/core-metadata/#summary + description='Python interface to control Parrot drones', # Required + + # This is an optional longer description of your project that represents + # the body of text which users will see when they visit PyPI. + # + # Often, this is the same as your README, so you can just read it in from + # that file directly (as we have already done above) + # + # This field corresponds to the "Description" metadata field: + # https://packaging.python.org/specifications/core-metadata/#description-optional + long_description=long_description, # Optional + + # Denotes that our long_description is in Markdown; valid values are + # text/plain, text/x-rst, and text/markdown + # + # Optional if long_description is written in reStructuredText (rst) but + # required for plain-text or Markdown; if unspecified, "applications should + # attempt to render [the long_description] as text/x-rst; charset=UTF-8 and + # fall back to text/plain if it is not valid rst" (see link below) + # + # This field corresponds to the "Description-Content-Type" metadata field: + # https://packaging.python.org/specifications/core-metadata/#description-content-type-optional + long_description_content_type='text/markdown; charset=UTF-8; variant=GFM', # Optional (see note above) + + # This should be a valid link to your project's main homepage. + # + # This field corresponds to the "Home-Page" metadata field: + # https://packaging.python.org/specifications/core-metadata/#home-page-optional + url='https://github.com/amymcgovern/pyparrot', # Optional + + # This should be your name or the name of the organization which owns the + # project. + author='Amy McGovern', # Optional + + # This should be a valid email address corresponding to the author listed + # above. + author_email='dramymcgovern@gmail.com', # Optional + + # Classifiers help users find your project by categorizing it. + # + # For a list of valid classifiers, see https://pypi.org/classifiers/ + classifiers=[ # Optional + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 4 - Beta', + + # Indicate who your project is intended for + 'Intended Audience :: Developers ', + 'Intended Audience :: Education', + 'Topic :: Education ', + 'Topic :: Software Development', + + # Pick your license as you wish + 'License :: OSI Approved :: MIT License', + + # Specify the Python versions you support here. In particular, ensure + # that you indicate whether you support Python 2, Python 3 or both. + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + + # This field adds keywords for your project which will appear on the + # project page. What does your project relate to? + # + # Note that this is a string of words separated by whitespace, not a list. + keywords='python parrot drone education programming', # Optional + + # You can just specify package directories manually here if your project is + # simple. Or you can use find_packages(). + # + # Alternatively, if you just want to distribute a single Python file, use + # the `py_modules` argument instead as follows, which will expect a file + # called `my_module.py` to exist: + # + # py_modules=["my_module"], + # + packages=find_packages(exclude=['contrib', 'docs', 'tests']), # Required + + include_package_data=True, + + # This field lists other packages that your project depends on to run. + # Any package you put here will be installed by pip when your project is + # installed, so they must be valid existing projects. + # + # For an analysis of "install_requires" vs pip's requirements files see: + # https://packaging.python.org/en/latest/requirements.html + install_requires=['untangle'], # Optional + + # this only runs on python 3 + python_requires='>=3', + + + # List additional groups of dependencies here (e.g. development + # dependencies). Users will be able to install these using the "extras" + # syntax, for example: + # + # $ pip install sampleproject[dev] + # + # Similar to `install_requires` above, these must be valid existing + # projects. + #extras_require={ # Optional + # 'dev': ['check-manifest'], + # 'test': ['coverage'], + #}, + + # If there are data files included in your packages that need to be + # installed, specify them here. + # + # If using Python 2.6 or earlier, then these have to be included in + # MANIFEST.in as well. + #package_data={ # Optional + # 'sample': ['package_data.dat'], + #}, + + # Although 'package_data' is the preferred approach, in some case you may + # need to place data files outside of your packages. See: + # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files + # + # In this case, 'data_file' will be installed into '/my_data' + # data_files=[('my_data', ['data/data_file'])], # Optional + + # To provide executable scripts, use entry points in preference to the + # "scripts" keyword. Entry points provide cross-platform support and allow + # `pip` to create the appropriate form of executable for the target + # platform. + # + # For example, the following would provide a command called `sample` which + # executes the function `main` from this package when invoked: + entry_points={ # Optional + 'console_scripts': [ + 'find_mambo=pyparrot.scripts.findMambo:main', + ], + }, + + # List additional URLs that are relevant to your project as a dict. + # + # This field corresponds to the "Project-URL" metadata fields: + # https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use + # + # Examples listed include a pattern for specifying where the package tracks + # issues, where the source is hosted, where to say thanks to the package + # maintainers, and where to support the project financially. The key is + # what's used to render the link text on PyPI. + project_urls={ # Optional + 'Bug Reports': 'https://github.com/amymcgovern/pyparrot/issues', + 'Funding': 'https://www.oufoundation.org/portal', + 'Say Thanks!': 'https://saythanks.io/to/amymcgovern', + 'Source': 'https://github.com/amymcgovern/pyparrot/', + }, +)