GSV4BT.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import sys
  2. import bluetooth
  3. class GSV4BT():
  4. # https://www.manualslib.com/manual/1380505/Me-Gsv-4.html?page=30#manual
  5. scalings = {
  6. # (ID, value for 0xFFFF, unit)
  7. '2mV': (0x01, 2.1 , 'mV/V'), # Measuring range ±2 mV/V (set_gain 0xB2 <p1> <p2>) with p1=ch, p2=0x01
  8. '10mV': (0x02, 10.5, 'mV/V'), # Measuring range ±10 mV/V (set_gain 0xB2 <p1> <p2>) with p1=ch, p2=0x02
  9. '5V': (0x03, 5.25, 'V' ), # Measuring range 0-5 V (set_gain 0xB2 <p1> <p2>) with p1=ch, p2=0x03
  10. '10V': (0x07, 10.5, 'V' ), # Measuring range 0-10 V (set_gain 0xB2 <p1> <p2>) with p1=ch, p2=0x07
  11. 'PT1000': (0x04, 1050, '°C' ), # Measuring range PT1000 (set_gain 0xB2 <p1> <p2>) with p1=ch, p2=0x04
  12. 'K': (0x06, 1050, '°C' ) # Measuring range K-thermocouple cable (set_gain 0xB2 <p1> <p2>) with p1=ch, p2=0x06
  13. }
  14. channelModes = [
  15. None,
  16. None,
  17. None,
  18. None
  19. ]
  20. frequencies = [
  21. # (ID, fNom in Hz, fEff in Hz)
  22. (0xA0, 0.63, 0.625),
  23. (0xA1, 1.25, 1.250),
  24. (0xA2, 2.5 , 2.500),
  25. (0xA3, 3.75, 3.750),
  26. (0xA4, 6.25, 6.250),
  27. (0xA5, 7.5 , 7.500),
  28. (0xA6, 12.5, 12.40),
  29. (0xA7, 15 , 14.7 ),
  30. (0xA8, 25 , 24.4 ),
  31. (0xA9, 125 , 125 ),
  32. (0xAA, 250 , 250 ),
  33. (0xAB, 500 , 500 ),
  34. (0xAC, 937.5, 900 ),
  35. ]
  36. def __init__(self, addr):
  37. self.addr = addr
  38. self.uuid = None
  39. self.sock = None
  40. def printError(self, msg):
  41. print("ERROR: GSV4BT {}:".format(self.addr), msg)
  42. def printWarning(self, msg):
  43. print("WARNING: GSV4BT {}:".format(self.addr), msg)
  44. def connect(self):
  45. if self.sock:
  46. return True
  47. service_matches = bluetooth.find_service(address=self.addr)
  48. if len(service_matches) == 0:
  49. self.printError("BT device not found.")
  50. return False
  51. first_match = service_matches[0]
  52. self.uuid = first_match["uuid"]
  53. self.port = first_match["port"]
  54. self.name = first_match["name"]
  55. self.host = first_match["host"]
  56. self.printWarning("Connecting to \"{}\" on {}".format(self.name, self.host))
  57. self.sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
  58. ret = self.sock.connect((self.host, self.port))
  59. self.sock.settimeout(0.3)
  60. return ret
  61. def isConnected(self):
  62. return self.sock and True
  63. def sendRaw(self, data):
  64. self.sock.send(data)
  65. def sendCommand(self, code, *params):
  66. data = bytes([code] + params)
  67. return self.sendRaw(data)
  68. def setNormalMode(self):
  69. return self.sendRaw(bytes(0x26, 0x01, 0x62, 0x65, 0x72, 0x6C, 0x69, 0x6E))
  70. def getMode(self):
  71. self.sendCommand(0x27)
  72. return self.waitResponse(0x27)
  73. def getFirmwareVersion(self):
  74. self.sendCommand(0x2B)
  75. return self.waitResponse(0x2B)
  76. def getTxStatus(self):
  77. self.sendCommand(0x29)
  78. return self.waitResponse(0x29)
  79. # https://www.manualslib.com/manual/1380505/Me-Gsv-4.html?page=34#manual
  80. def setGain(self, channel, scalingName = '2mV'):
  81. self.channelModes[channel] = scalingName
  82. self.sendCommand(0xB2, channel, self.scalings[scalingName][0])
  83. return self.waitResponse(0xB2)
  84. def setFrequency(self, freq = 10):
  85. for id, fNom, fEff in self.frequencies:
  86. value = id
  87. if freq < fEff:
  88. break
  89. self.sendCommand(0x12, value)
  90. return self.waitResponse(0x12)
  91. def startTransmission(self):
  92. self.sendCommand(0x24)
  93. return self.waitResponse(0x24)
  94. def stopTransmission(self):
  95. self.sendCommand(0x23)
  96. return self.waitResponse(0x23)
  97. def getValue(self):
  98. self.sendCommand(0x3B)
  99. lastFrameCarry = None
  100. def recvRaw(self):
  101. data = self.sock.recv(1024)
  102. if self.lastFrameCarry:
  103. data = self.lastFrameCarry + data
  104. i = 0
  105. while i < len(data):
  106. prefix = data[i]
  107. start = i
  108. i += 1
  109. end = data.find(b'\r\n', start)
  110. if end == -1:
  111. self.lastFrameCarry = data[start:]
  112. break
  113. else:
  114. i = end + 2
  115. if prefix == 0xA5:
  116. # measured values
  117. if end+2 - start != 11:
  118. self.printError("invalid frame: values", data[start:end+2])
  119. continue
  120. self.parseValues(data[start+1:end])
  121. elif prefix == 0x3B:
  122. # response
  123. if end+2 - start < 10:
  124. self.printError("invalid frame: response", data[start:end+2])
  125. continue
  126. code = int(data[start+1])
  127. n = int(data[start+2])
  128. length = int.from_bytes(data[start+3:start+5], 'little', False)
  129. no = int.from_bytes(data[start+5:start+8], 'little', False)
  130. if end+2 - start != length + 10:
  131. self.printError("invalid length: {}", data[start:end+2])
  132. continue
  133. self.parseResponse(code, n, no, data[start+8:end])
  134. _valuesCb = None
  135. def setValuesCb(self, cb):
  136. self._valuesCb = cb
  137. def parseValues(self, data):
  138. values = [None]*4
  139. for i in range(4):
  140. values[i] = int.from_bytes(data[i*2:i*2+2], 'little', False)
  141. # map range to units
  142. if self.channelModes[i] == None:
  143. continue
  144. _, scale, unit = self.scalings[self.channelModes[i]]
  145. values[i] = (values[i] - 32768) / 32768 * scale
  146. if not self._valuesCb:
  147. self.printWarning("missed reading: ch {}: {:8.3f} {}".format(i, values[i], unit))
  148. if self._valuesCb:
  149. _valuesCb(values)
  150. _responseCb = None
  151. def setResponseCb(self, cb):
  152. self._responseCb = cb
  153. def waitResponse(self, sentCode):
  154. userRespCb = self._responseCb
  155. recvCode = None
  156. recvData = None
  157. def cb(code, data):
  158. recvCode = code
  159. recvData = data
  160. self.setResponseCb(cb)
  161. self.recvRaw()
  162. if recvCode != sentCode:
  163. self.printError("invalid response code:", recvCode)
  164. return None
  165. self.setResponseCb(userRespCb)
  166. return recvData
  167. _responseNo = 0
  168. def parseResponse(self, code, n, no, data):
  169. if no != (self._responseNo + 1) % 2**24:
  170. self.printWarning("responses skipped:", (no-self._responseNo+2**24-1) % 2**24)
  171. self._responseNo = no
  172. if n > 0:
  173. self.printWarning("more than 1 response:", n-1)
  174. if self._responseCb:
  175. self._responseCb(code, data)
  176. def getForces(self):
  177. return (0, 0, 0)
  178. def close(self):
  179. self.sock.close()
  180. if __name__ == "__main__":
  181. cell = GSV4BT("01:23:45:67:89:01")
  182. while True:
  183. if cell.isConnected():
  184. cell.recvRaw()
  185. else:
  186. cell.connect()
  187. cell.setNormalMode()
  188. cell.setFrequency(20) # Hz
  189. cell.setGain(0, '2mV')
  190. cell.setGain(1, '2mV')
  191. cell.setGain(2, '2mV')
  192. cell.setGain(3, '5V')
  193. cell.setValuesCb(lambda values: print(values))
  194. time.sleep(0.3)