Last active
December 31, 2022 20:53
-
-
Save evadne/4a1ec282c661178ac8158744b4260e00 to your computer and use it in GitHub Desktop.
NEC X651UHD External Control
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import select | |
import socket | |
import sys | |
import functools | |
import datetime | |
import time | |
class Display: | |
def __init__(self, host, port): | |
self.host = host | |
self.port = port | |
self.timeout = 5 | |
def send(self, buffer): | |
display_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
display_socket.settimeout(self.timeout) | |
display_socket.connect((self.host, self.port)) | |
display_socket.setblocking(self.timeout) | |
display_socket.sendall(buffer) | |
if select.select([display_socket], [], [], self.timeout)[0]: | |
data = display_socket.recv(512) | |
display_socket.close() | |
return data | |
else: | |
raise Exception("timeout") | |
def parse_response(self, buffer): | |
length = int(buffer[5:7].decode(), 16) | |
envelope = buffer[7:-2] | |
if len(envelope) == length: | |
if envelope[:1] == bytes([0x02]): | |
if envelope[-1:] == bytes([0x03]): | |
if self.compute_checksum(buffer[1:(7+length)]) == buffer[-2:-1]: | |
return envelope[1:-1] | |
raise Exception("incomplete_response") | |
def encode_message(self, type, message): | |
content = bytes([0x01, 0x30]) + b'*0' + type + "{:02x}".format(2 + int(len(message))).upper().encode() + bytes([0x02]) + message + bytes([0x03]) | |
checksum = self.compute_checksum(content[1:]) | |
return content + checksum + bytes([0x0D]) | |
def compute_checksum(self, buffer): | |
return bytes([functools.reduce(lambda a, b: a ^ b, list(buffer))]) | |
def send_command(self, command): | |
response_envelope = self.send(self.encode_message(b'A', command)) | |
return self.parse_response(response_envelope) | |
def get_parameter(self, opcode_page, opcode): | |
response_envelope = self.send(self.encode_message(b'C', opcode_page + opcode)) | |
response = self.parse_response(response_envelope) | |
assert 16 == len(response) | |
assert opcode_page == response[2:4] | |
assert opcode == response[4:6] | |
if b'00' == response[0:2]: | |
max = int(response[8:12], 16) | |
current = int(response[12:16], 16) | |
if b'00' == response[6:8]: | |
return dict(success = True, type = 'fixed', max = max, current = current) | |
if b'01' == response[6:8]: | |
return dict(success = True, type = 'automatic', max = max, current = current) | |
if b'01' == response[0:2]: | |
return dict(success = False) | |
raise Exception('invalid_response') | |
def set_parameter(self, opcode_page, opcode, value): | |
request_value = "{:04x}".format(int(value)).upper().encode() | |
request_message = opcode_page + opcode + request_value | |
request = self.encode_message(b'E', request_message) | |
response_envelope = self.send(request) | |
response = self.parse_response(response_envelope) | |
assert 16 == len(response) | |
assert opcode_page == response[2:4] | |
assert opcode == response[4:6] | |
if b'00' == response[0:2]: | |
max = int(response[8:12], 16) | |
requested = int(response[12:16], 16) | |
if b'00' == response[6:8]: | |
return dict(success = True, type = 'fixed', max = max, requested = requested) | |
if b'01' == response[6:8]: | |
return dict(success = True, type = 'automatic', max = max, requested = requested) | |
if b'01' == response[0:2]: | |
return dict(success = False) | |
raise Exception('invalid_response') | |
class Handler: | |
def __init__(self, display): | |
self.display = display | |
def get_power_status(self): | |
result = self.display.send_command(b'01D6') | |
if result == b'0200D60000040001': | |
print('on') | |
elif result == b'0200D60000040002': | |
print('power_save_standby') | |
elif result == b'0200D60000040003': | |
print('power_save_suspend') | |
elif result == b'0200D60000040004': | |
print('off') | |
else: | |
raise Exception('bad_response') | |
def get_image_contrast(self): | |
result = self.display.get_parameter(b'00', b'12') | |
assert result['success'] | |
print(result['current']) | |
def get_image_brightness(self): | |
result = self.display.get_parameter(b'00', b'92') | |
assert result['success'] | |
print(result['current']) | |
def get_image_sharpness(self): | |
result = self.display.get_parameter(b'00', b'8C') | |
assert result['success'] | |
print(result['current']) | |
def get_colour_temperature(self): | |
result = self.display.get_parameter(b'00', b'54') | |
assert result['success'] | |
print(2600 + 100 * result['current']) | |
def get_backlight(self): | |
result = self.display.get_parameter(b'00', b'10') | |
assert result['success'] | |
print(result['current']) | |
def get_temperature(self): | |
result = self.display.get_parameter(b'02', b'79') | |
current = result['current'] | |
assert result['success'] | |
if current > (1 << 8): | |
print(-1 * (((~(current - 1)) & ((1 << 8) - 1)) / 2)) | |
else: | |
print(current / 2) | |
def turn_on(self): | |
result = self.display.send_command(b'C203D6001') | |
assert result == b'00C203D6001' | |
print("ok") | |
def turn_off(self): | |
result = self.display.send_command(b'C203D6004') | |
assert result == b'00C203D6004' | |
print("ok") | |
def set_datetime(self): | |
local_time = time.localtime() | |
year = "{:02x}".format(int(local_time.tm_year - 2000)).upper().encode() | |
mon = "{:02x}".format(int(local_time.tm_mon)).upper().encode() | |
mday = "{:02x}".format(int(local_time.tm_mday)).upper().encode() | |
wday = "{:02x}".format(int((local_time.tm_wday + 1) % 7)).upper().encode() | |
hour = "{:02x}".format(int(local_time.tm_hour)).upper().encode() | |
min = "{:02x}".format(int(local_time.tm_min)).upper().encode() | |
dst = "{:02x}".format(int(local_time.tm_isdst)).upper().encode() | |
value = year + mon + mday + wday + hour + min + dst | |
command = b'C212' + value | |
result = self.display.send_command(command) | |
assert b'C312' == result[:4] | |
assert b'00' == result[4:6] | |
assert value == result[6:24] | |
print("ok") | |
def set_image_contrast(self, value): | |
result = self.display.set_parameter(b'00', b'12', int(value)) | |
assert result['success'] | |
print('ok') | |
def set_image_brightness(self, value): | |
result = self.display.set_parameter(b'00', b'92', int(value)) | |
assert result['success'] | |
print('ok') | |
def set_image_sharpness(self, value): | |
result = self.display.set_parameter(b'00', b'8C', int(value)) | |
assert result['success'] | |
print('ok') | |
def set_colour_temperature(self, value): | |
value = int(value) | |
assert value >= 2600 | |
assert value <= 10000 | |
result = self.display.set_parameter(b'00', b'54', int((value - 2600) / 100)) | |
assert result['success'] | |
print('ok') | |
def set_backlight(self, value): | |
result = self.display.set_parameter(b'00', b'10', int(value)) | |
assert result['success'] | |
print('ok') | |
def set_temperature_sensor_index(self, value): | |
result = self.display.set_parameter(b'02', b'78', int(value)) | |
assert result['success'] | |
print('ok') | |
def handle_command(self, command, arguments): | |
try: | |
getattr(self, command)(*arguments) | |
except: | |
raise Exception("bad_command") | |
display = Display(sys.argv[1], 7142) | |
handler = Handler(display).handle_command(sys.argv[2], sys.argv[3:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment