iloveicedgreentea / jvc_homeassistant

JVC Integration for Home Assistant
20 stars 4 forks source link

NZ800 not working (old N5 worked) #49

Closed ftobi closed 1 day ago

ftobi commented 4 days ago

Hi,

after I changed from my old N5 to the new NZ800, the integration doesn't work with the new one. HA says: authentication failed. Network password is set correctly, IP-communication also works, as the JVC Calibration software connects sucessfully. The wireshark-snip from the successfull connection test of the JVC Calibration software is attached (hope it helps). Calibration connection Test.zip

Anyhing I can do or help (even if I'm not in python)?

Tobias

iloveicedgreentea commented 3 days ago

Please enable debug logging and send me the logs of it trying to connect

iloveicedgreentea commented 3 days ago

try upgrading to 5.0.0 as well first

ftobi commented 2 days ago

updated to 5.0.0. Unfortunately I cannot set the log to debug, as the integration only appears in GUI after successfull connect. This is what HA says after trying to set debug via configuration.yaml fpr projectors:

Dieser Fehler wurde von einer benutzerdefinierten Integration verursacht

Logger: custom_components.jvc_projectors.remote
Quelle: custom_components/jvc_projectors/remote.py:50
Integration: jvc_projectors
Erstmals aufgetreten: 08:46:48 (1 Vorkommnisse)
Zuletzt protokolliert: 08:46:48

Exception with PJACK: b'PJNAK'

and then:

Dieser Fehler wurde von einer benutzerdefinierten Integration verursacht

Logger: custom_components.jvc_projectors.remote
Quelle: custom_components/jvc_projectors/remote.py:52
Integration: jvc_projectors
Erstmals aufgetreten: 08:46:48 (1 Vorkommnisse)
Zuletzt protokolliert: 08:46:48

Failed to connect to the projector

I also tried some test with dirty testcode:

#!/usr/bin/python
#http://www.us.jvc.com/projectors/pdf/2018_ILA-FPJ_Ext_Command_List_v1.2.pdf
import socket
import time
import sys

IP = ""
command = ""
password = ""

if (len(sys.argv) < 3):
    print ("Usage: python jvc.py [IP] [command] [optional:password]")
    exit();
else:
    IP = sys.argv[1]
    command = sys.argv[2]
    print ("Parameter:")
    print ("IP:      " + IP)
    print ("Command: "+ command)
if (len(sys.argv) == 4):
    password = sys.argv[3]
    print ("Password: " + password)

print ()

print ("connect: ")
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((IP, 20554))
data = client_socket.recv(1024)
print (" received: " + str(data))
if len(password) == 10:
    data = b"PJREQ_" + bytes(password, "ascii") + b""
elif len(password) == 9:
    data = b"PJREQ_" + bytes(password, "ascii") + b"\x00"
elif len(password) == 8:
    data = b"PJREQ_" + bytes(password, "ascii") + b"\x00\x00"
else:
    raise JVCConfigError(
        "Specified network password invalid (too long/short). Please check your configuration."
    )

#data = f"PJREQ_{password}\x00\x00".encode()
#data = b"PJREQ_"+bytes(password, "ascii")
print ("handshake: "+ str(data))
client_socket.send(data)
time.sleep(0.25)
data = client_socket.recv(1024)
print (" received: " + str(data))

if(command=="ON"):
    print ("send: ON")
    #on
    data = b"\x21\x89\x01\x50\x57\x31\x0A"
    client_socket.send(data)
    data = client_socket.recv(1024)
    print (" received: " + str(data))

elif(command=="STANDBY"):
    print ("send: STANDBY")
    #Standby
    data = b"\x21\x89\x01\x50\x57\x30\x0A"
    client_socket.send(data)

elif(command=="HDMI1"):
    print ("send: HDMI1")
    #hdmi1
    data = b"\x21\x89\x01\x49\x50\x36\x0A"
    client_socket.send(data)

elif(command=="HDMI2"):
    print ("send: HDMI2")
    #hdmi2
    data = b"\x21\x89\x01\x49\x50\x37\x0A"
    client_socket.send(data)
else:
    print ("No valid command found!")
    print ("Valid commands are")
    print ("    jvc-py <IP>     ON")
    print ("                    STANDBY")
    print ("                    HDMI1")
    print ("                    HDMI2")
    print ("")

print ("Good Bye")
time.sleep(0.25)
client_socket.close()

And the output:

C:\Users\tobia\Desktop\JvcControl\python script>python jvc.py 192.168.10.42 ON 1234567890
Parameter:
IP:      192.168.10.42
Command: ON
Password: 1234567890

connect:
 received: b'PJ_OK'
handshake: b'PJREQ_1234567890'
 received: b'PJNAK'
send: ON
 received: b'PJNAK'
Good Bye 

I assumed that JVC changed something with the password handling (encrypted someway?) and therefore contacted my dealer to ask for the information at JVC what changed and how to deal with the PJREQ_PASSWORD with the actual projectors. I will report if I get information on that.

ftobi commented 2 days ago

Digged a little deeper: JVC changed how the password has to be handled: you have to append JVCKWPJ to the password and encode the resulting string with SHA256. then send PJREQ{encoded password} I only tested with a 10-charcter-password. So I can'T saay whether short passwords have to be paaded to 10. example: password is: "1234567890" with appended JVCKWPJ: "1234567890JVCKWPJ" encode SHA256("1234567890JVCKWPJ"): CC3054CF7C701A32A883BF45A7B19CBF7F62DB778208B3C058E0C6DDE886996F command to send to JVC: PJREQ_CC3054CF7C701A32A883BF45A7B19CBF7F62DB778208B3C058E0C6DDE886996F The result was PJACK and I was able to switch on the projector......

ftobi commented 2 days ago

did tests with 8 and 9 character passwords, with the SHA256 encoding: there is no need to fill othe characters of the password with \x00 until its 10 charcters long.

I tested with the following code (again: dirty code, just for testing):

#!/usr/bin/python
import socket
import time
import sys
import hashlib

IP = ""
command = ""
password = ""

def encrypt_string(hash_string):
    sha_signature = \
        hashlib.sha256(hash_string.encode()).hexdigest()
    return sha_signature

if (len(sys.argv) < 3):
    print ("Usage: python jvc.py [IP] [command] [optional:password]")
    exit();
else:
    IP = sys.argv[1]
    command = sys.argv[2]
    print ("Parameter:")
    print ("IP:        " + IP)
    print ("Command:   "+ command)
if (len(sys.argv) == 4):
    password = sys.argv[3]
    print ("Password:  " + password)

print ()

########### Connect
print ("connect: ")
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((IP, 20554))
data = client_socket.recv(1024)
print (" received: " + str(data))
pwSHA = ""

########### Handshake
if len(password) >= 8 and len(password) <=10:
    password = password + "JVCKWPJ"
else:
    print ("Specified network password invalid (too long/short). Trying to connect without password. Please check your configuration.")

print (b" password with added suffix: " + bytes(password, "ascii"))    
print (b" password SHA256 encoded   : " + bytes(encrypt_string(password), "ascii"))
data = b"PJREQ_"+bytes(encrypt_string(password), 'ascii').upper()
print ("handshake                    : "+ str(data))
client_socket.send(data)
time.sleep(0.25)
data = client_socket.recv(1024)
print (" received                    : " + str(data))

########### Commands
if(command=="ON"):
    print ("send: ON")
    #on
    data = b"\x21\x89\x01\x50\x57\x31\x0A"
    client_socket.send(data)
    data = client_socket.recv(1024)
    print (" received                   : " + str(data))

elif(command=="STANDBY"):
    print ("send: STANDBY")
    #Standby
    data = b"\x21\x89\x01\x50\x57\x30\x0A"
    client_socket.send(data)
    data = client_socket.recv(1024)
    print (" received                   : " + str(data))

elif(command=="HDMI1"):
    print ("send: HDMI1")
    #hdmi1
    data = b"\x21\x89\x01\x49\x50\x36\x0A"
    client_socket.send(data)

elif(command=="HDMI2"):
    print ("send: HDMI2")
    #hdmi2
    data = b"\x21\x89\x01\x49\x50\x37\x0A"
    client_socket.send(data)
else:
    print ("No valid command found!")
    print ("Valid commands are")
    print ("    jvc-py <IP> ON")
    print ("            STANDBY")
    print ("            HDMI1")
    print ("            HDMI2")
    print ("")

print ("Good Bye")
time.sleep(0.25)
client_socket.close()

with the following output:

C:\Users\tobia\Desktop\JvcControl\python script>python jvc.py 192.168.10.42 STANDBY 123456789
Parameter:
IP:        192.168.10.42
Command:   STANDBY
Password:  123456789

connect:
 received: b'PJ_OK'
b' password with added suffix: 123456789JVCKWPJ'
b' password SHA256 encoded   : d81ca9d85a62e6fe681f06ea4987c5614836a4fa4438291458813e5ef410d52f'
handshake                    : b'PJREQ_D81CA9D85A62E6FE681F06EA4987C5614836A4FA4438291458813E5EF410D52F'
 received                    : b'PJACK'
send: STANDBY
 received                   : b'\x06\x89\x01PW\n'
Good Bye

I suggest a checkbox in visual config or a additinal optional parameter configuration.yaml for the new projectors (NZ900 should be the same) that splits the handsahke_password behaviour old_Styl / new_style. Other idea for a quick shot: accept passwords longer than 10 characters. Then the user could SHA256 encode himself and copy the encoded string in the config (if you take the output as example, the user could generate the SHA256 with his password and the following JVCKWPJ here https://www.convertthisfile.com/conversions/hash-generator and then copy the SHA256 (in the example D81CA9D85A62E6FE681F06EA4987C5614836A4FA4438291458813E5EF410D52F) in the configuration.yaml). But that would have a much higher user-error-probalility ;)

To contribute those who found it: I found the information here https://www.c4forums.com/forums/topic/45561-jvc-nz900-rs4200-driver/

iloveicedgreentea commented 2 days ago

Thanks for doing the investigation I will make sure to add this to the next release. In the meantime you should be able to use this integration if you supply the sha256 encoded password with the right suffix and stuff as you did

It will probably be less error prone to add a NZ800/900+ flag, but users can also do the above if they know what they are doing

iloveicedgreentea commented 2 days ago

Please try v5.0.1

Make sure to read the readme to try the new option. If you use the new_model flag make sure your password is the original unencoded one

You will need to show beta versions in HACS

ftobi commented 1 day ago

wow, that was fast. Thank you. The workaround with placing the encoded password for the "old" version worked. And the beta 5.0.1 wsith the plain password and the flag new_model in config also works (I testet with a 9 character password). And many thanks for the fast implementation :)