Skip to content

Instantly share code, notes, and snippets.

@Staars
Last active August 25, 2024 23:10
Show Gist options
  • Save Staars/3f72a4151229cf99fcfc3472b4d6bc38 to your computer and use it in GitHub Desktop.
Save Staars/3f72a4151229cf99fcfc3472b4d6bc38 to your computer and use it in GitHub Desktop.
Chromecast remote control
# 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