Last active
August 25, 2024 23:10
-
-
Save Staars/3f72a4151229cf99fcfc3472b4d6bc38 to your computer and use it in GitHub Desktop.
Chromecast remote control
This file contains hidden or 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
# Simple Berry driver for the Chromecast remote - VERY UNFINISHED | |
# DOES NOT REALLY WORK - RC disconnects after short time!!!!! | |
import BLE | |
var hid | |
class HID | |
static hid_service = "1812" | |
var hid_chars | |
static battery_service = "180f" | |
static device_info_service = "180a" | |
var dev_chars | |
var init_step, init_finished | |
def init() | |
self.init_step = 0 | |
self.init_finished = false | |
self.hid_chars = ["2a4a","2a4b","2a4d"] | |
self.dev_chars = ["2A23","2A24","2A25","2A26","2A27","2A28","2A29","2A2a","2A50"] | |
self.nextStep() | |
end | |
def nextStep() | |
if self.init_step == 0 | |
self.getDeviceInfo() | |
elif self.init_step == 1 | |
self.readBattery() | |
elif self.init_step == 2 | |
self.initHID() | |
else | |
log("HID: initialized") | |
end | |
end | |
def getDeviceInfo() | |
BLE.set_svc(self.device_info_service,true) | |
BLE.set_chr(self.dev_chars[0]) | |
if size(self.dev_chars) > 1 | |
self.dev_chars = self.dev_chars[1..] | |
else | |
self.init_step += 1 | |
self.dev_chars = nil | |
end | |
BLE.run(1) | |
end | |
def readBattery() | |
BLE.set_svc(self.battery_service) | |
BLE.set_chr("2a19") | |
BLE.run(1) | |
if self.init_finished == false | |
self.init_step += 1 | |
end | |
end | |
def initHID() | |
BLE.set_svc(self.hid_service) | |
BLE.set_chr(self.hid_chars[0]) | |
if size(self.hid_chars) > 1 | |
self.hid_chars = self.hid_chars[1..] | |
BLE.run(1) | |
else | |
self.hid_chars = nil | |
BLE.run(3) | |
self.init_step += 1 | |
self.init_finished = true | |
end | |
end | |
def readCB(error,uuid,handle,buffer) | |
if error != 0 | |
log(f"HID: characteristic {uuid:x} not supported") | |
else | |
if uuid == 0x2a23 | |
log(f"HID: System ID: {buffer.tohex()}") | |
elif uuid == 0x2a2a | |
log(f"HID: Certification: {buffer.tohex()}") | |
elif uuid == 0x2a50 | |
log(f"HID: PNP ID: {buffer.tohex()}") | |
elif self.init_step == 0 | |
log(f"HID: device info {uuid:x} : {buffer.asstring()}") | |
elif uuid == 0x2a19 | |
log(f"HID: battery: {buffer.tohex()}") | |
elif uuid == 0x2a4a | |
log(f"HID: info: {buffer.tohex()}") | |
elif uuid == 0x2a4b | |
log(f"HID: report map: {buffer.tohex()}") | |
end | |
end | |
self.nextStep() | |
end | |
end | |
class ADPCM | |
static indexTable = [ | |
-1, -1, -1, -1, 2, 4, 6, 8, | |
-1, -1, -1, -1, 2, 4, 6, 8 | |
] | |
static stepSizeTable = [ | |
7, 8, 9, 10, 11, 12, 13, 14, 16, 17, | |
19, 21, 23, 25, 28, 31, 34, 37, 41, 45, | |
50, 55, 60, 66, 73, 80, 88, 97, 107, 118, | |
130, 143, 157, 173, 190, 209, 230, 253, 279, 307, | |
337, 371, 408, 449, 494, 544, 598, 658, 724, 796, | |
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, | |
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, | |
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, | |
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 | |
] | |
var statePrevSample, statePrevIndex | |
# util to clamp a number within a given range | |
def clamp(num, min, max) | |
return num <= min ? min : num >= max ? max : num | |
end | |
def decodeAdpcm(inputBuffer, statePrevSample, statePrevIndex) | |
var i_sz = size(inputBuffer) | |
print(i_sz) | |
var outputBuffer = bytes(-i_sz * 4) | |
var outputBufferOffset = 0 | |
self.statePrevSample = statePrevSample | |
self.statePrevIndex = 0 | |
for inputBufferOffset:range(0,i_sz - 1) | |
var b = inputBuffer[inputBufferOffset] | |
outputBuffer.seti(outputBufferOffset,self.decodeSample((b >> 4) & 0xF),2) | |
outputBuffer.seti(outputBufferOffset+2,self.decodeSample(b & 0xF),2) | |
outputBufferOffset += 4 | |
end | |
return outputBuffer; | |
end | |
def decodeSample(sample) | |
var predSample = self.statePrevSample | |
var index = self.statePrevIndex | |
var step = self.stepSizeTable[index] | |
var difference = step >> 3 | |
# compute difference and new predicted value | |
if (sample & 0x4) difference += step end | |
if (sample & 0x2) difference += (step >> 1) end | |
if (sample & 0x1) difference += (step >> 2) end | |
# handle sign bit | |
predSample += (sample & 0x8) ? -difference : difference | |
# find new index value | |
index += self.indexTable[sample] | |
index = self.clamp(index, 0, 88) | |
# clamp output value | |
predSample = self.clamp(predSample, -32768, 32767) | |
self.statePrevSample = predSample | |
self.statePrevIndex = index | |
predSample <<= 2 # amplify | |
return predSample | |
end | |
end | |
var adpcm | |
class BLE_CCast : Driver | |
var buf, hid | |
var connecting, connected, init_step | |
var adpcm_b, recording, rec_counter, start_rec, save_rec | |
def init(MAC,addr_type) | |
import global | |
var cbp = tasmota.gen_cb(/e,o,u,h->self.cb(e,o,u,h)) | |
self.buf = bytes(-256) | |
BLE.conn_cb(cbp,self.buf) | |
BLE.set_MAC(bytes(MAC),addr_type) | |
print("BLE: will try to connect to Chromecast RC with MAC:",MAC) | |
global.hid = HID() | |
self.init_step = 0 | |
self.adpcm_b = bytes(32000) | |
# self.connect() | |
tasmota.add_fast_loop(/-> BLE.loop()) # needed for mouse position | |
end | |
def every_second() | |
if (self.connecting == false && self.connected == false) | |
print("BLE: try to reconnect Chromecast RC") | |
hid.readBattery() | |
end | |
if self.recording == true | |
print(size(self.adpcm_b)) | |
end | |
end | |
def writeCharTX(payload) | |
self.buf[0] = size(payload) | |
self.buf.setbytes(1,payload) | |
BLE.set_svc("AB5E0001-5A21-4F05-BC7D-AF01F617B664") | |
BLE.set_chr("AB5E0002-5A21-4F05-BC7D-AF01F617B664") | |
BLE.run(2) | |
end | |
def subMicCRTL() | |
BLE.set_svc("AB5E0001-5A21-4F05-BC7D-AF01F617B664") | |
BLE.set_chr("AB5E0004-5A21-4F05-BC7D-AF01F617B664") | |
print("Subscribe to Mic CTRL") | |
BLE.run(3) | |
end | |
def subMicRX() | |
BLE.set_svc("AB5E0001-5A21-4F05-BC7D-AF01F617B664") | |
BLE.set_chr("AB5E0003-5A21-4F05-BC7D-AF01F617B664") | |
print("Subscribe to Mic RX") | |
BLE.run(3) | |
end | |
def subUnknown() | |
BLE.set_svc("11110001-1111-1111-1111-111111111111") | |
BLE.set_chr("11110003-1111-1111-1111-111111111111") | |
print("Subscribe to CCast unknown thing") | |
BLE.run(3) | |
end | |
def handleCTRL() | |
if self.buf[1] == 0x8 | |
self.writeCharTX(bytes("0c00)")) # mic open | |
print("Open mic") | |
elif self.buf[1] == 0x0 | |
print("Audio stop") | |
self.recording = false | |
elif self.buf[1] == 0x4 | |
print("Audio start") | |
elif self.buf[1] == 0xa | |
print("Audio sync") | |
elif self.buf[1] == 0xb | |
import global | |
var v = self.buf.geti(2,-2) | |
print("Version:",f"{v:04x}","Framesize:",self.buf.geti(7,2)) | |
print("Got audio caps -> Subscribe to Mic RX") | |
global.adpcm = ADPCM() | |
self.subMicRX() | |
end | |
print(self.buf[1..self.buf[0]]) | |
end | |
def recAudioFrame() | |
self.adpcm_b..adpcm.decodeAdpcm(self.buf[4..self.buf[0]],self.buf.geti(1,-2),self.buf[3]) | |
# self.adpcm_b..adpcm.decodeAdpcm(self.buf[1..self.buf[0]],0,0) | |
self.recording = true | |
# print(self.buf[1..4]) | |
end | |
def saveADPCM() | |
var f = open("audio.raw", "w") | |
f.write(self.adpcm_b) | |
f.close() | |
end | |
def handle_HID_notification(h) | |
import mqtt | |
var t = "key" | |
var v = "" | |
if h == 50 | |
var k = self.buf[1] | |
if k == 0x24 | |
v = "back" | |
elif k == 0x23 | |
v = "home" | |
elif k == 0xe2 | |
v = "mute" | |
elif k == 0x21 | |
v = "circle" | |
elif k == 0x77 | |
v = "YT" | |
elif k == 0x78 | |
v = "Netflix" | |
self.saveADPCM() | |
self.adpcm_b.clear() | |
self.recording = false | |
elif k == 0x9e | |
v = "on" | |
elif k == 0x89 | |
v = "return" | |
elif k == 0x44 | |
v = "left" | |
elif k == 0x43 | |
v = "down" | |
elif k == 0x45 | |
v = "right" | |
elif k == 0x42 | |
v = "up" | |
elif k == 0x41 | |
v = "set" | |
elif k == 0xe9 | |
v = "plus" | |
elif k == 0xea | |
v = "minus" | |
end | |
elif h == 58 # button assistant, 120 bytes | |
# print(self.buf[0]) | |
self.recAudioFrame() | |
return | |
elif h == 72 | |
self.handleCTRL() | |
return | |
elif h == 69 | |
self.recAudioFrame() | |
return | |
end | |
if v != '' | |
mqtt.publish("tele/CCast",format('{"%s":"%s"}',t,v)) | |
else # will be triggered on button release too | |
print(self.buf[1..self.buf[0]],h) # show the packet as byte buffer | |
end | |
end | |
def cb(error,op,uuid,handle) | |
if error == 0 | |
if op == 1 # read OP | |
# print(op,uuid) | |
hid.readCB(error,uuid,handle,self.buf[1..self.buf[0]]) | |
elif op == 3 | |
print(self.buf[1..self.buf[0]]) | |
if self.init_step == 0 | |
self.subMicCRTL() | |
import global | |
global.adpcm = ADPCM() | |
self.init_step = 2 | |
elif self.init_step == 1 | |
self.subMicRX() | |
self.init_step = 2 | |
elif self.init_step == 2 | |
self.subUnknown() | |
self.init_step = 3 | |
elif self.init_step == 3 | |
print("Request caps") | |
self.writeCharTX(bytes("0A0100000301)")) # get caps | |
self.init_step = 4 | |
self.connecting = false | |
self.connected = true | |
print("BLE: init completed for CCast") | |
end | |
elif op == 5 | |
self.connected = false | |
self.connecting = false | |
print("BLE: did disconnect CCast ... will try to reconnect") | |
elif op == 103 # notification OP | |
if self.connected == false return end | |
self.handle_HID_notification(handle) | |
end | |
else | |
if op == 1 # read OP | |
# print(op,uuid) | |
hid.readCB(error,uuid,handle,self.buf[1..self.buf[0]]) | |
else | |
print("BLE: error:",error) | |
if self.connecting == true | |
print("BLE: init sequence failed ... try to repeat") | |
self.connecting = false | |
end | |
end | |
end | |
end | |
end | |
ble_hid = BLE_CCast("19D000193930",0) # Chromecast MAC and address type | |
tasmota.add_driver(ble_hid) | |
#- | |
12:54:45.960 Service: UUID: 1800 | |
12:54:45.961 Characteristics: | |
12:54:46.154 UUID: 2A00 , handle: 0x0003 , ['Read', 'Write'] | |
12:54:46.156 UUID: 2A01 , handle: 0x0005 , ['Read'] | |
12:54:46.158 UUID: 2A04 , handle: 0x0007 , ['Read'] | |
12:54:46.169 __________________________ | |
12:54:46.170 Service: UUID: 180A | |
12:54:46.171 Characteristics: | |
12:54:46.254 UUID: 2A29 , handle: 0x000a , ['Read'] | |
12:54:46.259 UUID: 2A24 , handle: 0x000c , ['Read'] | |
12:54:46.261 UUID: 2A25 , handle: 0x000e , ['Read'] | |
12:54:46.263 UUID: 2A27 , handle: 0x0010 , ['Read'] | |
12:54:46.276 UUID: 2A26 , handle: 0x0012 , ['Read'] | |
12:54:46.278 UUID: 2A28 , handle: 0x0014 , ['Read'] | |
12:54:46.280 UUID: 2A23 , handle: 0x0016 , ['Read'] | |
12:54:46.292 UUID: 2A2A , handle: 0x0018 , ['Read'] | |
12:54:46.294 UUID: 2A50 , handle: 0x001a , ['Read'] | |
12:54:46.295 __________________________ | |
12:54:46.296 Service: UUID: 180F | |
12:54:46.307 Characteristics: | |
12:54:46.464 UUID: 2A19 , handle: 0x001d , ['Read', 'Notify'] | |
12:54:46.466 __________________________ | |
12:54:46.467 Service: UUID: 1812 | |
12:54:46.467 Characteristics: | |
12:54:46.614 UUID: 2A4A , handle: 0x0021 , ['Read'] | |
12:54:46.616 UUID: 2A4C , handle: 0x0023 , ['WriteNoResp'] | |
12:54:46.619 UUID: 2A4B , handle: 0x0025 , ['Read'] | |
12:54:46.632 UUID: 2A4E , handle: 0x0027 , ['Read', 'WriteNoResp'] | |
12:54:46.634 UUID: 2A22 , handle: 0x0029 , ['Read', 'Notify'] | |
12:54:46.636 UUID: 2A32 , handle: 0x002c , ['Read', 'WriteNoResp', 'Write'] | |
12:54:46.649 UUID: 2A4D , handle: 0x002e , ['Read', 'Notify'] | |
12:54:46.651 UUID: 2A4D , handle: 0x0032 , ['Read', 'Notify'] | |
12:54:46.663 UUID: 2A4D , handle: 0x0036 , ['Read', 'Notify'] | |
12:54:46.666 UUID: 2A4D , handle: 0x003a , ['Read', 'Notify'] | |
12:54:46.678 UUID: 2A4D , handle: 0x003e , ['Read', 'WriteNoResp', 'Write'] | |
12:54:46.680 __________________________ | |
12:54:46.681 Service: UUID: AB5E0001-5A21-4F05-BC7D-AF01F617B664 | |
12:54:46.692 Characteristics: | |
12:54:46.850 UUID: AB5E0002-5A21-4F05-BC7D-AF01F617B664 , handle: 0x0042 , ['WriteNoResp', 'Write'] | |
12:54:46.857 UUID: AB5E0003-5A21-4F05-BC7D-AF01F617B664 , handle: 0x0045 , ['Notify'] | |
12:54:46.861 UUID: AB5E0004-5A21-4F05-BC7D-AF01F617B664 , handle: 0x0048 , ['Notify'] | |
12:54:46.872 __________________________ | |
12:54:46.873 Service: UUID: 11110001-1111-1111-1111-111111111111 | |
12:54:46.884 Characteristics: | |
12:54:46.000 UUID: 11110002-1111-1111-1111-111111111111 , handle: 0x004c , ['WriteNoResp'] | |
12:54:47.004 UUID: 11110003-1111-1111-1111-111111111111 , handle: 0x004f , ['Write', 'Notify'] | |
12:54:47.015 _______________________________________________________________ | |
-# |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment