trainman419 / python-cec

Other
170 stars 42 forks source link

OSError: Power status not found #42

Closed Mariusmssj closed 4 years ago

Mariusmssj commented 4 years ago

When extracting details about found devices the code below:

import cec

cec.init()

devices = cec.list_devices()

for i in range(len(devices)):
    d = cec.Device(i)
    print('{0} : {1} | Active: {2} | ON: {3}'.format(i, d.osd_string, d.is_active(), d.is_on()))

gives the following output:

0 : TV | Active: False | ON: False 1 : python-cec | Active: False | ON: True Traceback (most recent call last): File "/home/pi/subwoofer_switch/cec_script.py", line 9, in print('{0} : {1} | Active: {2} | ON: {3}'.format(i, d.osd_string, d.is_active(), d.is_on())) OSError: Power status not found

However, just checking if they are active works fine:

import cec

cec.init()

devices = cec.list_devices()

for i in range(len(devices)):
    d = cec.Device(i)
    print('{0} : {1} | Active: {2}'.format(i, d.osd_string, d.is_active()))

Output:

0 : TV | Active: False 1 : python-cec | Active: False 2 : Recorder 2 | Active: False 3 : Tuner 1 | Active: False 4 : PlayStation 4 | Active: False

I got a script that checks for TV being turned on or off every 2 seconds and it seems to work for for few hours or so but then always crashes with an error OSError: Power status not found any idea what could be causing this? Thank you

nforro commented 4 years ago

any idea what could be causing this?

You would have to provide full logs, but the exception is caused by GetDevicePowerStatus() returning unexpected value, so the power status is not known at that particular moment. Can't you simply handle the exception?

nforro commented 4 years ago

That being said, I'd suggest taking advantage of CEC_OPCODE_GIVE_DEVICE_POWER_STATUS (if your TV responds to it), that way the TV will send you back its power status when it's ready, even if it's busy at the moment of the request.

Mariusmssj commented 4 years ago

Thank you, will do that and will wrap it with an exception handling as well

Mariusmssj commented 4 years ago

That being said, I'd suggest taking advantage of CEC_OPCODE_GIVE_DEVICE_POWER_STATUS (if your TV responds to it), that way the TV will send you back its power status when it's ready, even if it's busy at the moment of the request.

Sorry to be asking more questions.

Using CEC_OPCODE_GIVE_DEVICE_POWER_STATUS gives a wrong reading. When I run this with the TV off:

import cec

cec.init()

status = cec.Device(cec.CECDEVICE_TV).transmit(cec.CEC_OPCODE_GIVE_DEVICE_POWER_STATUS)
print("Transmit Status: {0}".format(status))

d = cec.Device(5)
print("Is on Status: {0}".format(d.is_on()))

I get the following output:

Transmit Status: True Is on Status: False

Am I doing something wrong?

nforro commented 4 years ago

First of all, you have a device mismatch. cec.CECDEVICE_TV is equal to zero, if your TV has a different logical address, you have to change that. But in the other issue you indicated that logical address 5 is your AV receiver, so, what device are you actually trying to monitor?

Second, transmit() returns True because it was successful, you successfully sent the request. But that doesn't tell you anything about the power status of the device, you have to wait for its response (in the command handler).

Mariusmssj commented 4 years ago

ahh I see, thank you and sorry about this, I am new to python and I am just trying to figure things out. I got a smart switch that I want to turn on/off based on if the receiver has been turned on or off. And I came across this library which has helped a lot, but I did make some mistakes. It does work but I wanted to know why I was getting the error and if can be made more efficient by using callback calls. Got my code below if interested:

import cec
import time
import configparser
from tuya.devices import TuyaSmartSwitch

cec.init()

#get config from the file
CONFIG = configparser.ConfigParser()
CONFIG.read("config.ini")

try: #connect to the smart switch
    device = TuyaSmartSwitch(
                username=CONFIG["TUYA"]["username"],
                password=CONFIG["TUYA"]["password"],
                location=CONFIG["TUYA"]["location"],
                device=CONFIG["TUYA"]["device"],
            )
except:
    print("Could not connect to the switch")
    quit()

current_status = True
receiver_state = True

print("Script started successfully")

def main():
    while True:
        if check_status_change():
            if receiver_state: #if the receiver is on turn on smart switch
                device.turn_on()
            else: #if the receiver is off turn off smart switch
                device.turn_off()
        time.sleep(5) #delay the checks to every 2 seconds

#Check if receiver has been turned on or off
def check_status_change():
    global current_status
    global receiver_state

    try:
        receiver_state = cec.Device(5).is_on() #check if the receiver is on
    except:
        print ("Failed power state check for receiver @:{0}".format(datetime.datetime.now()))

    if current_status is not receiver_state: #if the state has changed
        current_status = receiver_state
        return True #something has changed
    else:
        return False

if __name__ == "__main__":
    main()

Thank you for your help @nforro :)

nforro commented 4 years ago

Ok, I'd do something like this:

import sys
import time

import cec

class Receiver:
    def __init__(self, address, callback, log=False):
        self.address = address
        self.callback = callback
        self.device = cec.Device(self.address)
        cec.add_callback(self._command_event, cec.EVENT_COMMAND)
        if log:
            cec.add_callback(self._log_event, cec.EVENT_LOG)
        cec.init()

    def loop(self, interval):
        while True:
            self.device.transmit(cec.CEC_OPCODE_GIVE_DEVICE_POWER_STATUS)
            time.sleep(interval)

    def _command_event(self, event, command):
        if command['initiator'] == self.address and \
           command['opcode'] == cec.CEC_OPCODE_REPORT_POWER_STATUS:
            status = command['parameters'][0]
            if status == 0x00:
                self.callback('STANDBY')
            elif status == 0x01:
                self.callback('RUNNING')

    def _log_event(self, event, level, time, message):
        sys.stderr.write('CEC [{0}]: {1}\n'.format(time, message))
        sys.stderr.flush()

def power_status_callback(status):
    if status == 'STANDBY':
        # turn off the switch
        pass
    elif status == 'RUNNING':
        # turn on the switch
        pass

def main():
    receiver = Receiver(5, power_status_callback, log=True)
    try:
        receiver.loop(2)
    except KeyboardInterrupt:
        return

if __name__ == '__main__':
    main()

I suggest that you keep logging active until you figure out exactly what's going on. It's possible your AV receiver doesn't support CEC_OPCODE_GIVE_DEVICE_POWER_STATUS, but you should see that in the logs. It's also possible it does send something when it turns off and on, so no polling might be necessary. Again, you would see that in the logs.

Mariusmssj commented 4 years ago

That's brilliant thank you. Seems to work really well :)