text: g_instance = null;
  
  var getQuickPiConnection = function (userName, _onConnect, _onDisconnect) {
      this.onConnect = _onConnect;
      this.onDisconnect = _onDisconnect;
  
      if (g_instance) {
          return g_instance;
      }
  
      this.raspiServer = "";
      this.wsSession = null;
      this.resultsCallback = null;
      this.resultsSeq = 0;
      this.commandMode = false;
      this.userName = userName;
      this.sessionTainted = false;
      this.connected = false;
      this.onConnect = _onConnect;
      this.onDisconnect = _onDisconnect;
      this.locked = "";
      this.pingInterval = null;
      this.pingsWithoutPong = 0;
      this.oninstalled = null;
      this.commandQueue = [];
      this.seq = 0;
  
      this.connect = function(ipaddress) {
          if (this.wsSession != null) {
              return;
          }
  
          url = "ws://" + ipaddress + ":5000/api/v1/commands";
          this.locked = "";
          this.pingsWithoutPong = 0;
          this.commandQueue = [];
          this.resultsCallback = null;
          this.seq = Math.random() * 65536;
  
          this.wsSession = new WebSocket(url);
  
          this.wsSession.onopen = function () {
              var command =
              {
                  "command": "grabLock",
                  "username": userName
              }
  
              wsSession.send(JSON.stringify(command));
  
              connected = true;
              onConnect();
  
              pingInterval = setInterval(function() {
                  var command =
                  {
                      "command": "ping"
                  }
  
                  if (pingsWithoutPong > 8)
                  {
                      wsSession.close();
                      onclose();
                  } else {
                      pingsWithoutPong++;
                      wsSession.send(JSON.stringify(command));
                      lastPingSend = + new Date();
                  }
  
                  }, 1000);
  
          }
  
          this.wsSession.onmessage = function (evt) {
              var message = JSON.parse(evt.data);
  
              if (message.command == "locked") {
                  locked = message.lockedby;
              } else if (message.command == "pong") {
                  pingsWithoutPong = 0;
              } else if (message.command == "installed") {
  
                  if (oninstalled != null)
                      oninstalled();
  
              } else if (message.command == "startCommandMode") {
                  if (commandQueue.length > 0)
                  {
                      var command = commandQueue.shift();
  
                      sendCommand(command.command, command.callback);
                  }
              } else if (message.command == "execLineresult") {
                  if (commandMode && resultsCallback != null) {
  
                      if (!message.seq || message.seq == resultsSeq) {
                          tempCallback = resultsCallback;
                          resultsCallback = null;
                          tempCallback(message.result);
                      }
  
                      if (commandQueue.length > 0)
                      {
                          var command = commandQueue.shift();
  
                          sendCommand(command.command, command.callback);
                      }
                  }
              }
          }
  
          this.wsSession.onclose = function () {
  
              if (wsSession != null) {
                  clearInterval(pingInterval);
                  pingInterval = null;
  
                  wsSession = null;
                  commandMode = false;
                  sessionTainted = false;
  
                  connected = false;
  
                  onDisconnect(connected);
              }
          }
      }
  
      this.isAvailable = function(ipaddress, callback) {
          url = "ws://" + ipaddress + ":5000/api/v1/commands";
  
          var websocket = new WebSocket(url);
  
          websocket.onopen = function () {
              websocket.onclose = null;
              websocket.close();
              callback(true);
          }
          websocket.onclose = function () {
              callback(false);
          }
      }
  
      this.onclose = function() {
          clearInterval(pingInterval);
          pingInterval = null;
  
          wsSession = null;
          commandMode = false;
          sessionTainted = false;
  
          onDisconnect(connected);
  
          connected = false;
      }
  
      this.wasLocked = function()
      {
          if (this.locked)
              return true;
  
          return false;
      }
  
      this.isConnecting = function () {
          return this.wsSession != null;
      }
  
      this.isConnected = function () {
          return this.connected;
      }
  
      this.executeProgram = function (pythonProgram) {
          if (this.wsSession == null)
              return;
  
          this.commandMode = false;
  
          var fullProgram = pythonLib + pythonProgram;
          var command =
          {
              "command": "startRunMode",
              "program": fullProgram
          }
  
          this.wsSession.send(JSON.stringify(command));
      }
  
  
      this.installProgram = function (pythonProgram, oninstall) {
          if (this.wsSession == null)
              return;
  
          this.commandMode = false;
          this.oninstalled = oninstall;
  
          var fullProgram = pythonLib + pythonProgram;
          var command =
          {
              "command": "install",
              "program": fullProgram
          }
  
          this.wsSession.send(JSON.stringify(command));
      }
  
      this.stopProgram = function() {
          if (this.wsSession != null) {
              var command =
              {
                  "command": "stopAll",
              }
  
              this.wsSession.send(JSON.stringify(command));
          }
      }
  
      this.releaseLock = function() {
          if (this.wsSession == null)
              return;
  
          if (this.wsSession != null) {
              var command =
              {
                  "command": "close",
              }
  
              this.wsSession.send(JSON.stringify(command));
          }
      }
  
      this.startNewSession = function() {
          if (this.wsSession == null)
              return;
  
          if (this.commandMode && !this.sessionTainted)
              return;
  
          this.resultsCallback = null;
          this.commandMode = true;
          this.sessionTainted = false;
  
          var command =
          {
              "command": "startCommandMode",
              "library": pythonLib
          }
  
          this.commandQueue = [];
          this.wsSession.send(JSON.stringify(command));
      }
  
      this.sendCommand = function (command, callback) {
          if (this.wsSession != null) {
              if (this.resultsCallback == null) {
                  if (!this.commandMode) {
                      this.startNewSession();
  
                      this.commandQueue.push ({
                          "command": command,
                          "callback": callback
                      });    
                  } else { 
                      this.seq++;
                      var command =
                      {
                          "command": "execLine",
                          "line": command,
                          "seq": seq
                      }
  
                      this.sessionTainted = true;
                      this.resultsCallback = callback;
                      this.resultsSeq = seq;
                      this.wsSession.send(JSON.stringify(command));
                  }
              }
              else {
                  this.commandQueue.push ({
                      command: command,
                      callback: callback
                  });
              }
          }
      }
  
  
      g_instance = this;
      return this;
  }
  
  
  var pythonLib = `
  import RPi.GPIO as GPIO
  import time
  import smbus
  import math
  import pigpio 
  
  GPIO.setmode(GPIO.BCM)
  GPIO.setwarnings(False)
  
  button_interrupt_enabled = {}
  button_was_pressed = {}
  servo_object = {}
  servo_last_value = {}
  
  DHT11_last_value = {}
  
  distance_last_value = {}
  
  screenLine1 = None
  screenLine2 = None
  
  pi = pigpio.pi()
  
  def normalizePin(pin):
      returnpin = 0
      hadporttype = False
  
      if isinstance(pin, str):
          if pin[0].isalpha():
              returnpin = int(pin[1:])
              hadporttype = True
  
      if not hadporttype:
          returnpin = int(pin)
  
      return returnpin
  
  def cleanupPin(pin):
      pi.set_mode(pin, pigpio.INPUT)
  
  def changePinState(pin, state):
      pin = normalizePin(pin)
      state = int(state)
  
      cleanupPin(pin)
      GPIO.setup(pin, GPIO.OUT)
      if state:
          GPIO.output(pin, GPIO.HIGH)
      else:
          GPIO.output(pin, GPIO.LOW)
  
  def turnLedOn(pin=5):
  	changePinState(pin, 1)
  
  def turnLedOff(pin=5):
  	changePinState(pin, 0)
  
  def changeLedState(pin, state):
  	changePinState(pin, state)
  
  def toggleLedState(pin):
      pin = normalizePin(pin)
  
      GPIO.setup(pin, GPIO.OUT)
      if GPIO.input(pin):
          GPIO.output(pin, GPIO.LOW)
      else:
          GPIO.output(pin, GPIO.HIGH)
  
  def buzzOn(pin):
    changePinState(pin, 1)
  
  def buzzOff(pin):
    changePinState(pin, 0)
  
  def changeBuzzerState(pin, state):
      changePinState(pin, state)
  
  def magnetOn(pin):
    changePinState(pin, 1)
  
  def magnetOff(pin):
    changePinState(pin, 0)
  
  def buttonStateInPort(pin):
      pin = normalizePin(pin)
  
      GPIO.setup(pin, GPIO.IN)
      return GPIO.input(pin)
  
  def buttonState():
      return buttonStateInPort(22)
  
  def waitForButton(pin):
      pin = normalizePin(pin)
      cleanupPin(pin)
      GPIO.setup(pin, GPIO.IN)
      while not GPIO.input(pin):
          time.sleep(0.01)
      time.sleep(0.1) # debounce
  
  def buttonWasPressedCallback(pin):
      button_was_pressed[pin] = 1
  
  def buttonWasPressed(pin):
      pin = normalizePin(pin)
      init = False
      try:
          init = button_interrupt_enabled[pin]
      except:
          pass
  
      if not init:
          button_interrupt_enabled[pin] = True
          GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
          GPIO.add_event_detect(pin, GPIO.FALLING, callback=buttonWasPressedCallback, bouncetime=300)
  
      wasPressed = 0
  
      try:
          wasPressed = button_was_pressed[pin]
          button_was_pressed[pin] = 0
      except:
              pass
  
      return wasPressed
  
  usleep = lambda x: time.sleep(x / 1000000.0)
  
  _TIMEOUT1 = 1000
  _TIMEOUT2 = 10000
  
  def readDistance(pin):
      pin = normalizePin(pin)
  
      cleanupPin(pin)
  
      last_value = 0
      try:
          last_value = distance_last_value[pin]
      except:
          pass
  
      GPIO.setup(pin, GPIO.OUT)
      GPIO.output(pin, GPIO.LOW)
      usleep(2)
      GPIO.output(pin, GPIO.HIGH)
      usleep(10)
      GPIO.output(pin, GPIO.LOW)
  
      GPIO.setup(pin, GPIO.IN)
  
      t0 = time.time()
      count = 0
      while count < _TIMEOUT1:
          if GPIO.input(pin):
              break
          count += 1
      if count >= _TIMEOUT1:
          return last_value
  
      t1 = time.time()
      count = 0
      while count < _TIMEOUT2:
          if not GPIO.input(pin):
              break
          count += 1
      if count >= _TIMEOUT2:
          return last_value
  
      t2 = time.time()
  
      dt = int((t1 - t0) * 1000000)
      if dt > 530:
          return last_value
  
      distance = ((t2 - t1) * 1000000 / 29 / 2)    # cm
  
      distance = round(distance, 1)
  
      distance_last_value[pin] = distance
  
      return distance
  
  def displayText(line1, line2=""):
      global screenLine1
      global screenLine2
      
      if line1 == screenLine1 and line2 == screenLine2:
          return
  
      screenLine1 = line1
      screenLine2 = line2
  
      address = 0x3e
      bus = smbus.SMBus(1)
  
      bus.write_byte_data(address, 0x80, 0x01) #clear
      time.sleep(0.05)
      bus.write_byte_data(address, 0x80, 0x08 | 0x04) # display on, no cursor
      bus.write_byte_data(address, 0x80, 0x28) # two lines
      time.sleep(0.05)
  
      # This will allow arguments to be numbers
      line1 = str(line1)
      line2 = str(line2)
  
      count = 0
      for c in line1:
          bus.write_byte_data(address, 0x40, ord(c))
          count += 1
          if count == 16:
              break
  
      bus.write_byte_data(address, 0x80, 0xc0) # Next line
      count = 0
      for c in line2:
          bus.write_byte_data(address, 0x40, ord(c))
          count += 1
          if count == 16:
              break
  
  def setServoAngle(pin, angle):
      pin = normalizePin(pin)
      angle = int(angle)
  
      pulsewidth = (angle * 11.11) + 500
      pi.set_servo_pulsewidth(pin, pulsewidth)
  
  def readADC(pin):
      pin = normalizePin(pin)
  
      reg = 0x30 + pin
      address = 0x04
  
      try:
          bus = smbus.SMBus(1)
          bus.write_byte(address, reg)
          return bus.read_word_data(address, reg)
      except:
          return 0
  
  
  def readTemperature(pin):
      B = 4275.
      R0 = 100000.
  
      val = readADC(pin)
      
      if val == 0:
          return 0
  
      r = 1000. / val - 1.
      r = R0 * r
  
      return round(1. / (math.log10(r / R0) / B + 1 / 298.15) - 273.15, 1)
  
  def readRotaryAngle(pin):
  	return int(readADC(pin) / 10)
  
  def readSoundSensor(pin):
  	return int(readADC(pin) / 10)
  
  def readLightIntensity(pin):
  	return int(readADC(pin) * 100 / 631)
  
  def sleep(sleep_time):
  	sleep_time = float(sleep_time)
  	time.sleep(sleep_time/1000)
  
  def reportBlockValue(id, state):
      return state
      
  
  class DHT11Result:
      'DHT11 sensor result returned by DHT11.read() method'
  
      ERR_NO_ERROR = 0
      ERR_MISSING_DATA = 1
      ERR_CRC = 2
  
      error_code = ERR_NO_ERROR
      temperature = -1
      humidity = -1
  
      def __init__(self, error_code, temperature, humidity):
          self.error_code = error_code
          self.temperature = temperature
          self.humidity = humidity
  
      def is_valid(self):
          return self.error_code == DHT11Result.ERR_NO_ERROR
  
  # Taken from https://github.com/szazo/DHT11_Python
  class DHT11:
      'DHT11 sensor reader class for Raspberry'
  
      __pin = 0
  
      def __init__(self, pin):
          self.__pin = pin
  
      def read(self):
          GPIO.setup(self.__pin, GPIO.OUT)
  
          # send initial high
          self.__send_and_sleep(GPIO.HIGH, 0.05)
  
          # pull down to low
          self.__send_and_sleep(GPIO.LOW, 0.02)
  
          # change to input using pull up
          GPIO.setup(self.__pin, GPIO.IN, GPIO.PUD_UP)
  
          # collect data into an array
          data = self.__collect_input()
  
          # parse lengths of all data pull up periods
          pull_up_lengths = self.__parse_data_pull_up_lengths(data)
  
          # if bit count mismatch, return error (4 byte data + 1 byte checksum)
          if len(pull_up_lengths) != 40:
              return DHT11Result(DHT11Result.ERR_MISSING_DATA, 0, 0)
  
          # calculate bits from lengths of the pull up periods
          bits = self.__calculate_bits(pull_up_lengths)
  
          # we have the bits, calculate bytes
          the_bytes = self.__bits_to_bytes(bits)
  
          # calculate checksum and check
          checksum = self.__calculate_checksum(the_bytes)
          if the_bytes[4] != checksum:
              return DHT11Result(DHT11Result.ERR_CRC, 0, 0)
  
          # ok, we have valid data, return it
          return DHT11Result(DHT11Result.ERR_NO_ERROR, the_bytes[2], the_bytes[0])
  
      def __send_and_sleep(self, output, sleep):
          GPIO.output(self.__pin, output)
          time.sleep(sleep)
  
      def __collect_input(self):
          # collect the data while unchanged found
          unchanged_count = 0
  
          # this is used to determine where is the end of the data
          max_unchanged_count = 100
  
          last = -1
          data = []
          while True:
              current = GPIO.input(self.__pin)
              data.append(current)
              if last != current:
                  unchanged_count = 0
                  last = current
              else:
                  unchanged_count += 1
                  if unchanged_count > max_unchanged_count:
                      break
  
          return data
  
      def __parse_data_pull_up_lengths(self, data):
          STATE_INIT_PULL_DOWN = 1
          STATE_INIT_PULL_UP = 2
          STATE_DATA_FIRST_PULL_DOWN = 3
          STATE_DATA_PULL_UP = 4
          STATE_DATA_PULL_DOWN = 5
  
          state = STATE_INIT_PULL_DOWN
  
          lengths = [] # will contain the lengths of data pull up periods
          current_length = 0 # will contain the length of the previous period
  
          for i in range(len(data)):
  
              current = data[i]
              current_length += 1
  
              if state == STATE_INIT_PULL_DOWN:
                  if current == GPIO.LOW:
                      # ok, we got the initial pull down
                      state = STATE_INIT_PULL_UP
                      continue
                  else:
                      continue
              if state == STATE_INIT_PULL_UP:
                  if current == GPIO.HIGH:
                      # ok, we got the initial pull up
                      state = STATE_DATA_FIRST_PULL_DOWN
                      continue
                  else:
                      continue
              if state == STATE_DATA_FIRST_PULL_DOWN:
                  if current == GPIO.LOW:
                      # we have the initial pull down, the next will be the data pull up
                      state = STATE_DATA_PULL_UP
                      continue
                  else:
                      continue
              if state == STATE_DATA_PULL_UP:
                  if current == GPIO.HIGH:
                      # data pulled up, the length of this pull up will determine whether it is 0 or 1
                      current_length = 0
                      state = STATE_DATA_PULL_DOWN
                      continue
                  else:
                      continue
              if state == STATE_DATA_PULL_DOWN:
                  if current == GPIO.LOW:
                      # pulled down, we store the length of the previous pull up period
                      lengths.append(current_length)
                      state = STATE_DATA_PULL_UP
                      continue
                  else:
                      continue
  
          return lengths
  
      def __calculate_bits(self, pull_up_lengths):
          # find shortest and longest period
          shortest_pull_up = 1000
          longest_pull_up = 0
  
          for i in range(0, len(pull_up_lengths)):
              length = pull_up_lengths[i]
              if length < shortest_pull_up:
                  shortest_pull_up = length
              if length > longest_pull_up:
                  longest_pull_up = length
  
          # use the halfway to determine whether the period it is long or short
          halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
          bits = []
  
          for i in range(0, len(pull_up_lengths)):
              bit = False
              if pull_up_lengths[i] > halfway:
                  bit = True
              bits.append(bit)
  
          return bits
  
      def __bits_to_bytes(self, bits):
          the_bytes = []
          byte = 0
  
          for i in range(0, len(bits)):
              byte = byte << 1
              if (bits[i]):
                  byte = byte | 1
              else:
                  byte = byte | 0
              if ((i + 1) % 8 == 0):
                  the_bytes.append(byte)
                  byte = 0
  
          return the_bytes
  
      def __calculate_checksum(self, the_bytes):
          return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
  
  
  def readTemperatureDHT11(pin):
      pin = normalizePin(pin)
      haveold = False
  
      try:
          lasttime = DHT11_last_value[pin]["time"]
          haveold = True
          if time.time() - lasttime < 2:
              return DHT11_last_value[pin]["temperature"]
      except:
          pass
  
         
      instance = DHT11(pin=pin)
      result = instance.read()
      if result.is_valid():
          DHT11_last_value[pin] = {
              "time": time.time(),
              "temperature": result.temperature,
              "humidity": result.humidity
          }
          return result.temperature
      elif haveold:
          return DHT11_last_value[pin]["temperature"] 
  
      return 0
  
  def readHumidity(pin):
      pin = normalizePin(pin)
      haveold = False
  
      try:
          lasttime = DHT11_last_value[pin]["time"]
          haveold = True
          if time.time() - lasttime < 2:
              return DHT11_last_value[pin]["humidity"]
      except:
          pass
  
         
      instance = DHT11(pin=pin)
      result = instance.read()
      if result.is_valid():
          DHT11_last_value[pin] = {
              "time": time.time(),
              "temperature": result.temperature,
              "humidity": result.humidity
          }
          return result.humidity
      elif haveold:
          return DHT11_last_value[pin]["humidity"] 
  
      return 0
  
  def currentTime():
      return time.time() * 1000
      
  `