module main
author unknown
version 1 0 
description ''

script 235 150 {
forever {
  sayIt 'X:' ('eng joystick' 'X') ('[data:unicodeString]' 13) 'Y:' ('eng joystick' 'Y') ('[data:unicodeString]' 13) 'Pulsador:' ('eng joystick' 'kiot;button pressed?')
}
}


module 'Distance (HC-SR04)' Input
author MicroBlocks
version 1 5 
tags sensor 'hc-sr04' distance ultrasound 
description 'Support for the HC-SR04 ultrasound distance sensor.

Originally written by Joan Guillén & Josep Ferràndiz.
Rewritten to use the new pulse capture mechanism.'
variables _sr04_last _sr04_misses 

  spec 'r' 'distance (cm)' 'distance (cm) trigger _ echo _' 'num num' 2 4
  spec 'r' 'sr04_distanceOnePin' 'distance (cm) pin _' 'num' 0
  spec 'r' '_sr04_getEchoData' '_getEchoData _ echo _' 'auto auto' 0 1

to '_sr04_getEchoData' trig echo {
  comment 'Output a 50 usec pulse on trigger pin to
start distance measurement.'
  digitalWriteOp trig false
  waitMicros 2
  digitalWriteOp trig true
  waitMicros 50
  digitalWriteOp trig false
  comment 'Capture the distance pulse. Width of
pulse is proportional to the distance.'
  '[sensors:captureStart]' echo
  local 'start' (millisOp)
  repeatUntil (('[sensors:captureCount]') >= 2) {
    if ((millisSince start) > 24) {exitLoop}
    waitMillis 1
  }
  return ('[sensors:captureEnd]')
}

to 'distance (cm)' trig echo {
  comment 'Take a distance measurement. Pulses will be two element
list if successful. If no echo detected it will have only one
element. In rare error cases the second item will be negative.'
  local 'pulses' ('_sr04_getEchoData' trig echo)
  if (and ((size pulses) >= 2) ((at 2 pulses) > 0)) {
    _sr04_misses = 0
    comment 'The divisor was determined empirically. It is
less than the expected 2 * 343 meter/sec.'
    _sr04_last = ((10 * (at 2 pulses)) / 583)
    return _sr04_last
  } else {
    comment 'No echo, probably because target is out of range.
To avoid short-term glitches, return the last distance
unless the are N misses in a row, then return 400.'
    _sr04_misses += 1
    return (ifExpression (_sr04_misses < 10) _sr04_last 400)
  }
}

to sr04_distanceOnePin pin {
  return ('distance (cm)' pin pin)
}


module 'IR Remote' Comm
author MicroBlocks
version 1 9 
tags ir infrared remote 
description 'Sends and receives infrared remote control messages like the ones used for TV sets or air conditioners. Currently supports only the NEC protocol, which is quite common but not the only IR protocol in use. An NEC message consists of a one byte device number and a one byte command.

Note: Transmitting IR does not work on ESP8266 boards.

Receiving requires an IR receiver module, such as those built into the Circuit Playground Express and ED1 boards. Transmitting requires an IR transmitter LED, such as those built into the Circuit Playground Express and several M5 Stack products. Inexpensive IR LED transmitter and receivers are available for use with other boards.
'
variables _ir_pin _ir_pulse_times _ir_last_code _ir_last_device _ir_transmit_pin 

  spec ' ' 'attachIR' 'attach IR receiver to pin _' 'num' 0
  spec 'r' 'ir_code_received' 'IR code received?'
  spec 'r' 'ir_last_code' 'IR code'
  space
  spec ' ' 'IR_Transmit' 'IR transmit device _ command _' 'num num' 48896 1
  spec ' ' 'attachIRTransmitter' 'attach IR transmitter to pin _' 'num' 0
  space
  spec 'r' 'receiveIR' 'receive IR code'
  space
  spec ' ' '_testIR' '_test IR'
  spec 'r' '_receiveIRFromDevice' '_receive IR code from device _' 'num' 48896
  space
  spec ' ' '_captureIRMessage' '_captureIRMessage' 'any'
  spec ' ' '_dumpIR' '_dumpIR' 'any'
  spec 'r' '_getIRByte' '_getIRByte _' 'auto any' 4
  spec 'r' '_got32Bits' '_got32Bits' 'any'
  spec ' ' '_IR_SendByte' '_IR_SendByte _' 'auto' '10'
  spec ' ' 'timerIRCapture' 'timerIRCapture'
  spec ' ' 'interruptIRCapture' 'interruptIRCapture'

to IR_Transmit device command {
  if (_ir_transmit_pin == 0) {if (or ((boardType) == 'CircuitPlayground') ((boardType) == 'M5Atom-Matrix')) {
    _ir_transmit_pin = 12
  } (or ((boardType) == 'M5StickC') ((boardType) == 'M5StickC+')) {
    _ir_transmit_pin = 9
  }}
  comment 'Message start pulse and space'
  '[io:playTone]' _ir_transmit_pin 38000
  waitMicros 9000
  '[io:playTone]' _ir_transmit_pin 0
  waitMicros 4500
  comment 'Send device and command and their inverses'
  '_IR_SendByte' (device & 255)
  '_IR_SendByte' ((device >> 8) & 255)
  '_IR_SendByte' command
  '_IR_SendByte' ('~' command)
  comment 'Send stop bit'
  '[io:playTone]' _ir_transmit_pin 38000
  waitMicros 562
  '[io:playTone]' _ir_transmit_pin 0
  comment 'Turn off IR transmit LED'
  if (or ((boardType) == 'M5StickC') ((boardType) == 'M5StickC+')) {
    comment 'IR pin is inverted so true means "off"'
    digitalWriteOp _ir_transmit_pin true
  } else {
    digitalWriteOp _ir_transmit_pin false
  }
}

to '_IR_SendByte' byte {
  local 'bit' 1
  repeat 8 {
    if (0 == (byte & bit)) {
      '[io:playTone]' _ir_transmit_pin 38000
      waitMicros 530
      '[io:playTone]' _ir_transmit_pin 0
      waitMicros 530
    } else {
      '[io:playTone]' _ir_transmit_pin 38000
      waitMicros 530
      '[io:playTone]' _ir_transmit_pin 0
      waitMicros 1630
    }
    bit = (bit << 1)
  }
}

to '_captureIRMessage' {
  if (_ir_pulse_times == 0) {
    _ir_pulse_times = (newList 100)
    if (_ir_pin == 0) {
      if ((boardType) == 'CircuitPlayground') {
        _ir_pin = 11
      } ((boardType) == 'Citilab ED1') {
        _ir_pin = 35
      } ((boardType) == 'D1-Mini') {
        _ir_pin = 2
      } ((boardType) == 'MakerPort') {
        _ir_pin = 18
      } ((boardType) == 'MakerPort V3') {
        _ir_pin = 22
      }
    }
  }
  comment 'Capture the next IR message.'
  if (or ((boardType) == 'micro:bit v2') ((boardType) == 'Calliope v3')) {
    timerIRCapture
  } else {
    interruptIRCapture
  }
}

to '_dumpIR' {
  comment 'Print raw pulse timings to the terminal.
Can be used to analyze new protocols.'
  local 'i' 1
  graphIt '-----'
  repeat (size _ir_pulse_times) {
    local 'mark usecs' (at i _ir_pulse_times)
    local 'space usecs' (at (i + 1) _ir_pulse_times)
    graphIt (v 'mark usecs') (v 'space usecs')
    i += 2
    if ((v 'space usecs') == 0) {
      graphIt 'timing entries:' (i - 2)
      return 0
    }
  }
}

to '_getIRByte' position {
  local 'result' 0
  local 'i' position
  local 'bit' 1
  repeat 8 {
    if ((at i _ir_pulse_times) > 1000) {result = (result | bit)}
    bit = (bit << 1)
    i += 2
  }
  return result
}

to '_got32Bits' {
  return (and ((at 67 _ir_pulse_times) != 0) ((at 68 _ir_pulse_times) == 0))
}

to '_receiveIRFromDevice' deviceID {
  forever {
    '_captureIRMessage'
    if ('_got32Bits') {
      local 'id_lowByte' ('_getIRByte' 4 nil)
      local 'id_highByte' ('_getIRByte' 20 nil)
      if (and (id_highByte == (deviceID >> 8)) (id_lowByte == (deviceID & 255))) {
        return ('_getIRByte' 36 nil)
      }
    }
  }
}

to '_testIR' {
  forever {
    '_captureIRMessage'
    if ('_got32Bits') {
      comment 'Four byte message format:
<device low byte><device high byte><command><command, bit-inverted>'
      local 'b1' ('_getIRByte' 4 nil)
      local 'b2' ('_getIRByte' 20 nil)
      local 'b3' ('_getIRByte' 36 nil)
      local 'b4' ('_getIRByte' 52 nil)
      sayIt 'Device:' ((b2 << 8) | b1) 'code:' b3
    }
  }
}

to attachIR pin {
  _ir_pin = pin
}

to attachIRTransmitter pin {
  _ir_transmit_pin = pin
}

to interruptIRCapture {
  comment 'Capture IR pulses using interrupt-based pulse capture system.'
  atPut 'all' _ir_pulse_times 0
  comment 'Wait for IR signal -- this is the start of a new message.
Note: THe pin goes low when an IR signal is detected.'
  waitUntil (not (digitalReadOp _ir_pin))
  local 'start' (millisOp)
  '[sensors:captureStart]' _ir_pin
  comment 'Collect IR pulses until we receive over 67 pulses or until the timeout.'
  repeatUntil (or (('[sensors:captureCount]') > 67) ((millisSince start) > 200)) {
    waitMillis 5
  }
  local 'pulseData' ('[sensors:captureEnd]')
  comment 'If we got at least 67 pulses, copy their absolute values (durations) into _ir_pulse_times.'
  if ((size pulseData) >= 67) {
    for i 67 {
      atPut i _ir_pulse_times (absoluteValue (at i pulseData))
    }
  }
}

to ir_code_received {
  return ((receiveIR) >= 0)
}

to ir_last_code {
  return _ir_last_code
}

to receiveIR {
  forever {
    '_captureIRMessage'
    if ('_got32Bits') {
      local 'id_lowByte' ('_getIRByte' 4 nil)
      local 'id_highByte' ('_getIRByte' 20 nil)
      _ir_last_device = ((id_highByte << 8) | id_lowByte)
      _ir_last_code = ('_getIRByte' 36 nil)
      atPut 'all' _ir_pulse_times 0
      return _ir_last_code
    }
  }
}

to timerIRCapture {
  comment 'Capture IR pulses using the microseconds timer. (Original version, not currently used.)'
  atPut 'all' _ir_pulse_times 0
  local 'i' 1
  comment 'Wait for IR signal -- this is the start of a new message.
Note: THe pin goes low when an IR signal is detected.'
  waitUntil (not (digitalReadOp _ir_pin))
  local 'start' (microsOp)
  forever {
    comment 'Record the time until the end of the current IR pulse ("mark")'
    waitUntil (digitalReadOp _ir_pin)
    local 'end' (microsOp)
    atPut i _ir_pulse_times (end - start)
    i += 1
    start = end
    comment 'Record time until the start of the next IR pulse ("space")'
    repeatUntil (not (digitalReadOp _ir_pin)) {
      if (((microsOp) - start) > 5000) {
        comment 'No IR pulse for 5000 usecs means "end of message"'
        return 0
      }
    }
    local 'end' (microsOp)
    atPut i _ir_pulse_times (end - start)
    i += 1
    start = end
  }
}


module KidsIOT
author 'Josep Ferràndiz'
version 1 9 
depends Tone '_Temperature Humidity (DHT11, DHT22)' '_Distance (HC-SR04)' NeoPixel '_IR Remote' TFT 'LED Display' '_Real Time Clock' 
choices kiot_rotarySensor 'kiot;rotation' 'kiot;button pressed?' 
choices kiot_twoPins '1' '3' '4' '6' '7' '8' '9' 
choices kiot_digitalActuators 'kiot;active buzzer' 'kiot;LED' 'kiot;laser' 'kiot;relay' 
choices kiot_analogSensors 'kiot;AD key' 'kiot;gas (MQ-135)' 'kiot;film pressure' 'kiot;flame' 'kiot;light' 'kiot;microphone' 'kiot;potentiometer' 'kiot;steam' 'kiot;UV light' 
choices kiot_allPorts '1' '2' '3' '4' '6' '7' '8' '9' 
choices kiot_motor '1' '3' '9' 
choices kiot_analogPins '3' '4' '6' '7' '8' 
choices kiot_tempUnits '°C' '°F' 
choices kiot_rotation 'kiot;clockwise' 'kiot;counter-clockwise' 'kiot;stop' 
choices kiot_dual_motor A B both 
choices kiot_threePins '1' '9' 
choices kiot_joystick X Y 'kiot;button pressed?' 
choices kiot_digitalSensors 'kiot;button pressed?' 'kiot;humidity' 'kiot;magnet detected?' 'kiot;movement detected?' 'kiot;obstacle?' 'kiot;photo sensor interrupted?' 'kiot;receive IR code' 'kiot;temperature (Celsius)' 'kiot;tilt?' 'kiot;touching?' 
choices kiot_side 'kiot;left' 'kiot;right' 
description 'KidsIoT for ESP32 easy plug connection system from KidsBits.
Large selection of modules.

https://www.kidsbits.cc/products/kidsbits-stem-electronic-building-blocks-development-board-for-esp32?VariantsId=10124
'
variables _kiot_buzzer_init _kiot_avg_loudness _kiot_encoder_port1 _kiot_encoder_port9 

  spec ' ' 'kiot play note' '#SVG#square11#0070ff play note _ octave _ for _ port _' 'menu.tone_NoteName num num menu.kiot_allPorts' 'nt;c' 0 500 1
  space
  spec ' ' 'kiot traffic light' '#SVG#square11#ffffff traffic light red _ yellow _ green _ port _' 'bool bool bool menu.kiot_threePins' true true true 1
  spec ' ' 'kiot RGB color' '#SVG#square11#ffffff RGB color _ port _' 'color menu.kiot_threePins' nil 1
  spec ' ' 'kiot set Neopixels' '#SVG#square11#0070ff set NeoPixels _ _ _ _ port _' 'color color color color menu.kiot_allPorts' nil nil nil nil 1
  spec ' ' 'kiot set digital' '#SVG#square11#0070ff set _ _ port _' 'menu.kiot_digitalActuators bool menu.kiot_allPorts' 'kiot;LED' true 1
  space
  spec 'r' 'kiot digitalSensors' '#SVG#square11#0070ff digital _ port _' 'menu.kiot_digitalSensors menu.kiot_allPorts' 'kiot;button pressed?' 1
  spec 'r' 'kiot temperature (DS18B20)' '#SVG#square11#0070ff temperature (DS18B20) _ port _ : scale (x10) _' 'menu.kiot_tempUnits menu.kiot_allPorts bool' '°C' 1 false
  spec 'r' 'kiot analogSensors' '#SVG#square11#ff0000 analog _ port _' 'menu.kiot_analogSensors menu.kiot_analogPins' 'kiot;microphone' 3
  space
  spec 'r' 'kiot rotarySensor' '#SVG#square11#ffffff rotary encoder _ port _' 'menu.kiot_rotarySensor menu.kiot_threePins' 'kiot;rotation' 1
  space
  spec ' ' 'kiot_set_dual_motor' '#SVG#square11#00c612 dual motor _ power _ (-100 to 100)' 'menu.kiot_dual_motor num' 'A' 50
  spec ' ' 'kiot_stop_dual_motors' '#SVG#square11#00c612 stop dual motors'
  space
  spec ' ' 'kiot set motor' '#SVG#square11#fff000 set motor _ port _' 'menu.kiot_rotation menu.kiot_motor' 'kiot;clockwise' 1
  spec 'r' 'kiot doubleLineTracker' '#SVG#square11#fff000 line sensor _ port _' 'menu.kiot_side menu.kiot_twoPins' 'kiot;left' 1
  spec 'r' 'kiot distance' '#SVG#square11#fff000 distance (cm) port _' 'menu.kiot_twoPins' 1
  space
  spec 'r' 'kiot joystick' '#SVG#square11#00c612 joystick _' 'menu.kiot_joystick' 'X'
  space
  spec 'r' 'kiot getTimeDate' '#SVG#square11#00c612 get _' 'menu.rtc_timeOrDate' 'time (H:M:S)'
  spec ' ' 'kiot setTime' '#SVG#square11#00c612 set hours _ minutes _ seconds _' 'num num num' 9 30 0
  spec ' ' 'kiot setDate' '#SVG#square11#00c612 set year _ month _ day _' 'num num num' 2025 1 1

to '_kiot_DS18B20_address' pin {
  '[1wire:init]' pin
  '[1wire:scanStart]'
  waitMillis 10
  local 'addr' ('[data:newByteArray]' 8)
  if (not ('[1wire:scanNext]' addr)) {
    comment 'No response; temperature probe not plugged in?'
    return 0
  }
  local 'family' (at 1 addr)
  if (not (or (family == 34) (or (family == 40) (family == 66)))) {
    comment 'Device is not a DS1822, DS18B20, or DS28EA00 temperature sensor'
    return 0
  }
  return addr
}

to '_kiot_raw_temperature' pin {
  comment 'Read the raw temperature. This function waits
a full second for the temperature sensor to
measure the temperature, so it is best called
from a dedicated task.'
  local 'addr' ('_kiot_DS18B20_address' pin)
  if (addr == 0) {
    sayIt 'Temperature 18B20 sensor not plugged in?'
    waitMillis 2000
    return 0
  }
  '[1wire:select]' addr
  '[1wire:writeByte]' (hexToInt '44') true
  waitMillis 1
  '[1wire:select]' addr
  '[1wire:writeByte]' (hexToInt 'BE')
  local 'data' ('[data:newByteArray]' 9)
  for i 9 {
    atPut i data ('[1wire:readByte]')
  }
  if (('[1wire:crc8]' data) != 0) {
    sayIt 'Bad temperature CRC'
    return 0
  }
  local 'result' (((at 2 data) << 8) | (at 1 data))
  if (result >= 32768) {
    comment 'Sign-extend 16-bit negative number'
    result = (result - 65536)
  }
  return result
}

to 'kiot RGB color' color port {
  if (('[data:convertType]' port 'number') == 1) {
    analogWriteOp 12 ((color >> 16) & 255)
    analogWriteOp 13 ((color >> 8) & 255)
    analogWriteOp 14 (color & 255)
  } (('[data:convertType]' port 'number') == 9) {
    analogWriteOp 17 ((color >> 16) & 255)
    analogWriteOp 18 ((color >> 8) & 255)
    analogWriteOp 19 (color & 255)
  } else {
    sayIt 'Invalid port number. Valid ports: 1 & 9'
  }
}

to 'kiot analogSensors' sensor port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 3 4 6 7 8)) > 0) {
    local 'value' (analogReadOp (at ('[data:convertType]' port 'number') ('[data:makeList]' '' '' 4 39 '' 36 35 34)))
    if (or (sensor == 'kiot;film pressure') (sensor == 'kiot;flame')) {
      return (1023 - value)
    } (sensor == 'kiot;microphone') {
      comment 'Simple low-pass filter to smooth some of the noise.'
      _kiot_avg_loudness = (((4 * _kiot_avg_loudness) + value) / 5)
      return _kiot_avg_loudness
    } (sensor == 'kiot;AD key') {
      if (and (value > 0) (value < 250)) {
        return 1
      } (and (value > 280) (value < 400)) {
        return 2
      } (and (value > 480) (value < 600)) {
        return 3
      } (and (value > 680) (value < 850)) {
        return 4
      } (and (value > 900) (value < 1024)) {
        return 5
      } else {
        return 0
      }
    } else {
      return value
    }
  } else {
    return 'Invalid port number. Valid ports: 3, 4, 6, 7 & 8'
  }
}

to 'kiot digitalSensors' param port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local 'pin' (at port ('[data:makeList]' 13 2 26 27 '' 23 16 5 18))
    if (param == 'kiot;temperature (Celsius)') {
      return (temperature_DHT11 pin)
    } (param == 'kiot;humidity') {
      return (humidity_DHT11 pin)
    } (('[data:find]' param ('[data:makeList]' 'kiot;movement detected?' 'kiot;photo sensor interrupted?' 'kiot;touching?')) > 0) {
      return (digitalReadOp pin)
    } (param == 'kiot;receive IR code') {
      attachIR pin
      return (receiveIR)
    } else {
      return (not (digitalReadOp pin))
    }
  } else {
    return 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot distance' port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 3 4 6 7 8 9)) > 0) {
    local '_kiot_pins' ('[data:makeList]' ('[data:makeList]' 13 14) '' ('[data:makeList]' 26 4) ('[data:makeList]' 27 39) '' ('[data:makeList]' 23 36) ('[data:makeList]' 16 35) ('[data:makeList]' 5 34) ('[data:makeList]' 18 19))
    local 'trig' (at 1 (at ('[data:convertType]' port 'number') _kiot_pins))
    local 'echo' (at 2 (at ('[data:convertType]' port 'number') _kiot_pins))
    return ('distance (cm)' trig echo)
  } else {
    return 'Invalid port number. Valid ports: 1, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot doubleLineTracker' side port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 3 4 6 7 8 9)) > 0) {
    local '_kiot_pins' ('[data:makeList]' ('[data:makeList]' 13 14) '' ('[data:makeList]' 26 4) ('[data:makeList]' 27 39) '' ('[data:makeList]' 23 36) ('[data:makeList]' 16 35) ('[data:makeList]' 5 34) ('[data:makeList]' 18 19))
    if (side == 'kiot;left') {
      return (not (digitalReadOp (at 2 (at ('[data:convertType]' port 'number') _kiot_pins))))
    } (side == 'kiot;right') {
      return (not (digitalReadOp (at 1 (at ('[data:convertType]' port 'number') _kiot_pins))))
    } else {
      return 'Invalid side. Only admits "left" or "right".'
    }
  } else {
    return 'Invalid port number. Valid ports: 1, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot getTimeDate' which {
  return (rtc_getTimeOrDate which)
}

to 'kiot joystick' param {
  if ('[sensors:i2cExists]' (hexToInt '39')) {
    if (param == 'kiot;button pressed?') {
      return ((i2cGet (hexToInt '39') (hexToInt '31')) > 0)
    } (param == 'X') {
      return ('[misc:rescale]' (((i2cGet (hexToInt '39') (hexToInt '32')) << 2) | ((i2cGet (hexToInt '39') (hexToInt '33')) >> 6)) 0 1023 -100 100)
    } (param == 'Y') {
      return ('[misc:rescale]' (((i2cGet (hexToInt '39') (hexToInt '34')) << 2) | ((i2cGet (hexToInt '39') (hexToInt '35')) >> 6)) 0 1023 100 -100)
    } else {
      return 'Only "kiot;button pressed?", "X" or "Y" are valid inputs'
    }
  } else {
    return 'Joystick not detected'
  }
}

to 'kiot play note' note octave time port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local '_kiot_pins' ('[data:makeList]' 13 2 26 27 '' 23 16 5 18)
    if (_kiot_buzzer_init != port) {
      'attach buzzer to pin' (at port _kiot_pins)
      _kiot_buzzer_init = port
    }
    'play tone' note octave time
  } else {
    sayIt 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot rotarySensor' sensor port {
  port = ('[data:convertType]' port 'number')
  if ('kiot;button pressed?' == sensor) {
    if (1 == port) {
      return (not (digitalReadOp 14 'up'))
    } (9 == port) {
      return (not (digitalReadOp 19 'up'))
    }
    return (booleanConstant false)
  }
  local 'rawCount' 0
  if (1 == port) {
    if (_kiot_encoder_port1 == 0) {
      _kiot_encoder_port1 = 3
      '[encoder:start]' _kiot_encoder_port1 12 13 true
    }
    rawCount = ('[encoder:count]' _kiot_encoder_port1)
  } (9 == port) {
    if (_kiot_encoder_port9 == 0) {
      _kiot_encoder_port9 = 4
      '[encoder:start]' _kiot_encoder_port9 17 18 true
    }
    rawCount = ('[encoder:count]' _kiot_encoder_port9)
  }
  comment 'Divide count by 4 since one click is (usually) four pulses.'
  return (rawCount / 4)
}

to 'kiot set Neopixels' c1 c2 c3 c4 port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local '_kiot_pins' ('[data:makeList]' 13 2 26 27 '' 23 16 5 18)
    neoPixelAttach 4 (at port _kiot_pins)
    '[display:neoPixelSend]' ('[data:makeList]' c1 c2 c3 c4)
    waitMicros 300
  } else {
    sayIt 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot set digital' module state port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    digitalWriteOp (at port ('[data:makeList]' 13 2 26 27 '' 23 16 5 18)) state
  } else {
    sayIt 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot set motor' rotation port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 3 9)) > 0) {
    local '_kiot_pins' ('[data:makeList]' ('[data:makeList]' 13 14) ('[data:makeList]' 26 4) ('[data:makeList]' 18 19))
    local 'pin1' (at 1 (at ('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 3 9)) _kiot_pins))
    local 'pin2' (at 2 (at ('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 3 9)) _kiot_pins))
    if (rotation == 'kiot;clockwise') {
      digitalWriteOp pin1 false
      digitalWriteOp pin2 true
    } (rotation == 'kiot;counter-clockwise') {
      digitalWriteOp pin1 true
      digitalWriteOp pin2 false
    } (rotation == 'kiot;stop') {
      digitalWriteOp pin1 false
      digitalWriteOp pin2 false
    } else {
      return 'Invalid parameter. Only "clockwise", "counter-clockwise" & "stop" are valids.'
    }
  } else {
    return 'Invalid port number. Valid ports: 1, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot setDate' year month day {
  rtc_setDate year month day
}

to 'kiot setTime' hours minutes seconds {
  rtc_setTime hours minutes seconds
}

to 'kiot temperature (DS18B20)' unit port scale {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local 'raw' ('_kiot_raw_temperature' (at port ('[data:makeList]' 13 2 26 27 '' 23 16 5 18)))
    local 'factor' (ifExpression (and ((pushArgCount) > 1) (not scale)) 5 0)
    if ('°F' == unit) {
      return ((((((10 * 9) * raw) / (16 * 5)) + 320) + factor) / (maximum 1 (factor * 2)))
    } else {
      return ((((10 * raw) / 16) + factor) / (maximum 1 (factor * 2)))
    }
  } else {
    sayIt 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'kiot traffic light' red yellow green port {
  if (('[data:convertType]' port 'number') == 1) {
    digitalWriteOp 12 red
    digitalWriteOp 13 yellow
    digitalWriteOp 14 green
  } (('[data:convertType]' port 'number') == 9) {
    digitalWriteOp 17 red
    digitalWriteOp 18 yellow
    digitalWriteOp 19 green
  } else {
    sayIt 'Invalid port number. Valid ports: 1 & 9'
  }
}

to kiot_set_dual_motor motor power {
  comment 'I2C 2-channel DC motor driver with DRV8833 controller'
  if ('both' == motor) {
    drv8833_set_motor 'A' power
    drv8833_set_motor 'B' power
    return 0
  }
  local 'i2c_addr' (hexToInt '30')
  if ('[sensors:i2cExists]' i2c_addr) {
    local 'clockwise' (booleanConstant true)
    if (power < 0) {
      clockwise = (booleanConstant false)
      power = (0 - power)
    }
    power = ('[misc:rescale]' (minimum power 100) 0 100 0 255)
    local 'reg' (ifExpression ('B' == motor) 3 1)
    if clockwise {
      i2cSet i2c_addr reg 0
      i2cSet i2c_addr (reg + 1) power
    } else {
      i2cSet i2c_addr reg power
      i2cSet i2c_addr (reg + 1) 0
    }
  } else {
    sayIt 'I2C Dual Motor controller not found (DRV8833)'
  }
}

to kiot_stop_dual_motors {
  kiot_set_dual_motor 'A' 0
  kiot_set_dual_motor 'B' 0
}


module 'KidsIOT Engineering'
author MicroBlocks
version 1 0 
depends '_IR Remote' _Servo _Tone '_Distance (HC-SR04)' TFT 'LED Display' 
choices eng_servo_port '1 (io33)' '2 (io32)' 
choices eng_analogSensors 'kiot;potentiometer' 'kiot;steam' 
choices eng_rotation 'kiot;clockwise' 'kiot;counter-clockwise' 
choices eng_joystick X Y 'kiot;button pressed?' 
choices eng_allPorts '1' '2' '3' '4' '6' '7' '8' '9' 
choices eng_digitalSensors 'kiot;button pressed?' 'kiot;receive IR code' 'kiot;touching?' 
choices eng_twoPins '1' '3' '4' '6' '7' '8' '9' 
choices eng_analogPins '3' '4' '6' '7' '8' 
choices eng_motor '1' '3' '9' 
choices eng_threePins '1' '9' 
description 'Specific library for "KidsIOT Smart Engineering" kit.
Components:
ultrasound, IR receiver, potentiometer, button, buzzer, I2C joystick, steam sensor, capacitive button, servo 270º and servo 360º (continuous rotation)
'

  spec ' ' 'eng play note' '#SVG#square11#0070ff play note _ octave _ for _ port _' 'menu.tone_NoteName num num menu.eng_allPorts' 'nt;c' 0 500 1
  space
  spec 'r' 'eng buttonPressed' '#SVG#square11#0070ff button pressed? _' 'menu.eng_allPorts' 1
  spec 'r' 'eng touching' '#SVG#square11#0070ff touching? _' 'menu.eng_allPorts' 1
  spec 'r' 'eng receiveIRCode' '#SVG#square11#0070ff receive IR code _' 'menu.eng_allPorts' 1
  space
  spec 'r' 'eng potentiometer' '#SVG#square11#ff0000 potentiometer _' 'menu.eng_analogPins' 3
  spec 'r' 'eng steam' '#SVG#square11#ff0000 steam _' 'menu.eng_analogPins' 3
  space
  spec 'r' 'eng joystick' '#SVG#square11#00c612 joystick _' 'menu.eng_joystick' 'X'
  spec 'r' 'eng distance' '#SVG#square11#fff000 distance (cm) port _' 'menu.eng_twoPins' 1
  space
  spec ' ' 'eng servoToDegrees' '#SVG#square11#C0C0C0 set servo _ to _ degrees (0 to 270) : reversed _' 'menu.eng_servo_port num bool' '1 (io33)' 90 false
  spec ' ' 'eng servoToSpeed' '#SVG#square11#FFA500 set servo _ to speed _ (0 to 100) _ : for _ seconds' 'menu.eng_servo_port num menu.eng_rotation num' '1 (io33)' 100 'kiot;clockwise' 1
  spec ' ' 'eng stopServo' '#SVG#square11#FFA500 stop servo _' 'menu.eng_servo_port' '1 (io33)'

to 'eng buttonPressed' port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local 'pin' (at port ('[data:makeList]' 13 2 26 27 '' 23 16 5 18))
    return (not (digitalReadOp pin))
  } else {
    return 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'eng distance' port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 3 4 6 7 8 9)) > 0) {
    local '_kiot_pins' ('[data:makeList]' ('[data:makeList]' 13 14) '' ('[data:makeList]' 26 4) ('[data:makeList]' 27 39) '' ('[data:makeList]' 23 36) ('[data:makeList]' 16 35) ('[data:makeList]' 5 34) ('[data:makeList]' 18 19))
    local 'trig' (at 1 (at ('[data:convertType]' port 'number') _kiot_pins))
    local 'echo' (at 2 (at ('[data:convertType]' port 'number') _kiot_pins))
    return ('distance (cm)' trig echo)
  } else {
    return 'Invalid port number. Valid ports: 1, 3, 4, 6, 7, 8 & 9'
  }
}

to 'eng joystick' param {
  if ('[sensors:i2cExists]' (hexToInt '39')) {
    if (param == 'kiot;button pressed?') {
      return ((i2cGet (hexToInt '39') (hexToInt '31')) > 0)
    } (param == 'X') {
      return ('[misc:rescale]' (((i2cGet (hexToInt '39') (hexToInt '32')) << 2) | ((i2cGet (hexToInt '39') (hexToInt '33')) >> 6)) 0 1023 -100 100)
    } (param == 'Y') {
      return ('[misc:rescale]' (((i2cGet (hexToInt '39') (hexToInt '34')) << 2) | ((i2cGet (hexToInt '39') (hexToInt '35')) >> 6)) 0 1023 100 -100)
    } else {
      return 'Only "kiot;button pressed?", "X" or "Y" are valid inputs'
    }
  } else {
    return 'Joystick not detected'
  }
}

to 'eng play note' note octave time port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local '_eng_pins' ('[data:makeList]' 13 2 26 27 '' 23 16 5 18)
    if (_eng_buzzer_init != port) {
      'attach buzzer to pin' (at port _eng_pins)
      _eng_buzzer_init = port
    }
    'play tone' note octave time
  } else {
    sayIt 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'eng potentiometer' port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 3 4 6 7 8)) > 0) {
    local 'value' (analogReadOp (at ('[data:convertType]' port 'number') ('[data:makeList]' '' '' 4 39 '' 36 35 34)))
    return value
  } else {
    return 'Invalid port number. Valid ports: 3, 4, 6, 7 & 8'
  }
}

to 'eng receiveIRCode' port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local 'pin' (at port ('[data:makeList]' 13 2 26 27 '' 23 16 5 18))
    attachIR pin
    return (receiveIR)
  } else {
    return 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}

to 'eng servoToDegrees' which degrees reversed {
  if ((size which) == 8) {which = (34 - ('[data:convertType]' ('[data:copyFromTo]' which 1 1) 'number'))}
  if ((pushArgCount) > 2) {if reversed {
    degrees = (270 - degrees)
  }}
  local 'pulseWidth' ('[misc:rescale]' degrees 0 270 600 2400)
  if ('[io:hasServo]') {
    '[io:setServo]' which pulseWidth
  } else {
    atPut ('_servoIndex' which) _servoPulseWidth pulseWidth
  }
}

to 'eng servoToSpeed' which speed rotation seconds {
  if (rotation == 'kiot;counter-clockwise') {speed = (0 - speed)}
  if ((size which) == 8) {which = (34 - ('[data:convertType]' ('[data:copyFromTo]' which 1 1) 'number'))}
  setServoSpeed which ('[data:convertType]' speed 'number')
  if ((pushArgCount) > 3) {
    waitMillis (seconds * 1000)
    stopServo which
  }
}

to 'eng steam' port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 3 4 6 7 8)) > 0) {
    local 'value' (analogReadOp (at ('[data:convertType]' port 'number') ('[data:makeList]' '' '' 4 39 '' 36 35 34)))
    return value
  } else {
    return 'Invalid port number. Valid ports: 3, 4, 6, 7 & 8'
  }
}

to 'eng stopServo' servo {
  if ((size servo) == 8) {servo = (34 - ('[data:convertType]' ('[data:copyFromTo]' servo 1 1) 'number'))}
  stopServo servo
}

to 'eng touching' port {
  if (('[data:find]' ('[data:convertType]' port 'number') ('[data:makeList]' 1 2 3 4 6 7 8 9)) > 0) {
    local 'pin' (at port ('[data:makeList]' 13 2 26 27 '' 23 16 5 18))
    return (digitalReadOp pin)
  } else {
    return 'Invalid port number. Valid ports: 1, 2, 3, 4, 6, 7, 8 & 9'
  }
}


module 'LED Display' Output
author MicroBlocks
version 1 15 
choices led_imageMenu heart 'small heart' yes no happy sad confused angry asleep surprised silly fabulous meh 't-shirt' 'roller skate' duck house tortoise butterfly 'stick figure' ghost sword giraffe skull umbrella snake rabbit cow 'quarter note' 'eight note' pitchfork target triangle 'left triangle' 'chess board' diamond 'small diamond' square 'small square' scissors 
description 'Display primitives for the 5x5 LED display on the BBC micro:bit, Calliope mini and M5Atom Matrix. Boards with TFT displays (such as the Citilab ED1 or the M5Stack family) support these primitives with a simulated "fat pixel" display.
'
variables _stop_scrolling_text 

  spec ' ' '[display:mbDisplay]' 'display _' 'microbitDisplay' 15237440
  spec ' ' 'led_displayImage' 'display image _ : x _ y _' 'menu.led_imageMenu num num' 'happy' 1 1
  spec ' ' '[display:mbDisplayOff]' 'clear display'
  space
  spec ' ' '[display:mbPlot]' 'plot x _ y _' 'num num' 3 3
  spec ' ' '[display:mbUnplot]' 'unplot x _ y _' 'num num' 3 3
  space
  spec ' ' 'displayCharacter' 'display character _' 'str' 'A'
  spec ' ' 'scroll_text' 'scroll text _ : pausing _ ms' 'str num' 'HELLO ROSA!' 100
  spec ' ' 'stopScrollingText' 'stop scrolling'
  advanced
  spec ' ' 'set display color' 'set display color _' 'color'
  spec 'r' 'led_image' 'LED image #BR# _' 'microbitDisplay' 15237440
  space
  spec 'r' '_led_namedImage' '_led_namedImage _' 'menu.led_imageMenu' 'happy'
  spec 'r' '_led_imageData' '_led_imageData'

to '_led_imageData' {
  return 'heart:4685802,small heart:145728,yes:2269696,no:18157905,happy:15237440,sad:18284864,confused:22348096,angry:23036241,asleep:459616,surprised:4526090,silly:25984017,fabulous:15008639,meh:2236443,t-shirt:15154043,roller skate:11534104,duck:489702,house:10976708,tortoise:359872,butterfly:29332475,stick figure:18158564,ghost:23068334,sword:4657284,giraffe:10946627,skull:15171246,umbrella:6460398,snake:469859,rabbit:16104613,cow:4685361,quarter note:7573636,eight note:7590276,pitchfork:4357813,target:4681156,triangle:1026176,left triangle:32805985,chess board:11184810,diamond:4539716,small diamond:141440,square:33080895,small square:469440,scissors:20287859,'
}

to '_led_namedImage' name {
  local 'data' ('_led_imageData')
  local 'i' ('[data:find]' name data)
  if (i == -1) {
    comment 'Name not found'
    return 0
  }
  local 'start' (('[data:find]' ':' data i) + 1)
  local 'end' (('[data:find]' ',' data i) - 1)
  return ('[data:convertType]' ('[data:copyFromTo]' data start end) 'number')
}

to displayCharacter s {
  s = ('[data:join]' '' s)
  if ((size s) == 0) {
    '[display:mbDisplayOff]'
    return 0
  }
  '[display:mbDrawShape]' ('[display:mbShapeForLetter]' (at 1 s)) 1 1
}

to led_displayImage imageName optionalX optionalY {
  local 'image' imageName
  if (isType image 'string') {
    image = ('_led_namedImage' imageName)
  }
  '[display:mbDrawShape]' image (argOrDefault 2 1) (argOrDefault 3 1)
}

to led_image twentyFiveBitInt {
  comment 'An LED image is a 25-bit integer'
  return twentyFiveBitInt
}

to scroll_text text optionalDelay {
  text = ('[data:join]' '' text)
  local 'delay' 100
  if ((pushArgCount) > 1) {
    delay = optionalDelay
  }
  _stop_scrolling_text = (booleanConstant false)
  if ('Pico:ed' == (boardType)) {
    for position (((size text) * 6) + 18) {
      if _stop_scrolling_text {return 0}
      '[display:mbDisplayOff]'
      '[tft:text]' text (17 - position) 0 (colorSwatch 125 125 125 255) 1 true
      waitMillis (delay / 2)
    }
  } (or ('KidsIOT' == (boardType)) ('CodingBox' == (boardType))) {
    for position (((size text) * 6) + 21) {
      if _stop_scrolling_text {return 0}
      '[tft:deferUpdates]'
      '[tft:clear]'
      '[tft:text]' text (128 - (6 * position)) 6 (colorSwatch 255 255 255 255) 6 false
      '[tft:resumeUpdates]'
      waitMillis (delay / 8)
    }
  } else {
    for position (((size text) * 6) + 6) {
      if _stop_scrolling_text {return 0}
      for i (size text) {
        '[display:mbDrawShape]' ('[display:mbShapeForLetter]' ('[data:unicodeAt]' i text)) (((i * 6) + 2) - position) 1
      }
      waitMillis delay
    }
  }
}

to 'set display color' color {
  '[display:mbSetColor]' color
}

to stopScrollingText {
  _stop_scrolling_text = (booleanConstant true)
  waitMillis 10
  '[display:mbDisplayOff]'
}


module NeoPixel Output
author MicroBlocks
version 1 15 
description 'Control NeoPixel (WS2812) RGB LED strips and rings.
'
variables _np_pixels _np_pin _np_haswhite 

  spec ' ' 'neoPixelAttach' 'attach _ LED NeoPixel strip to pin _ : has white _' 'num auto bool' 10 '' false
  spec ' ' 'setNeoPixelColors10' 'set NeoPixels _ _ _ _ _ _ _ _ _ _' 'color color color color color color color color color color'
  spec ' ' 'setNeoPixelColors25' 'set NeoPixels #BR# _ _ _ _ _ #BR# _ _ _ _ _ #BR# _ _ _ _ _ #BR# _ _ _ _ _ #BR# _ _ _ _ _' 'color color color color color color color color color color color color color color color color color color color color color color color color color'
  spec ' ' 'clearNeoPixels' 'clear NeoPixels'
  spec ' ' 'neoPixelSetAllToColor' 'set all NeoPixels color _' 'color'
  spec ' ' 'setNeoPixelColor' 'set NeoPixel _ color _' 'num color' 1
  space
  spec 'r' 'neoPixel_colorSwatch' '_' 'color'
  spec 'r' 'colorFromRGB' 'color r _ g _ b _ (0-255)' 'num num num' 0 100 100
  spec 'r' 'randomColor' 'random color'
  space
  spec ' ' 'rotateNeoPixelsBy' 'rotate NeoPixels by _' 'auto' 1
  space
  spec ' ' 'NeoPixel_brighten' 'brighten NeoPixel _ by _' 'num num' 1 10
  spec ' ' 'NeoPixel_brighten_all' 'brighten all NeoPixels by _' 'num' 10
  spec ' ' 'NeoPixel_shift_color' 'shift NeoPixel _ color by _' 'num num' 1 10
  spec ' ' 'NeoPixel_shift_all_colors' 'shift all NeoPixel colors by _' 'num' 10
  space
  spec ' ' '_NeoPixel_ensureInitialized' '_NeoPixel_ensureInitialized'
  spec ' ' '_NeoPixel_increaseRGB' '_NeoPixel_increaseRGB of _ by _' 'num num' 1 10
  spec ' ' '_NeoPixel_rotate' '_NeoPixel_rotate_left _' 'bool' true
  spec ' ' '_NeoPixel_update' '_NeoPixel_update'
  spec ' ' '_NeoPixel_shift_hue' '_NeoPixel_shift_hue of _ by _' 'auto auto' '10' '10'

to NeoPixel_brighten i delta {
  '_NeoPixel_increaseRGB' i delta
  '_NeoPixel_update'
}

to NeoPixel_brighten_all delta {
  for i (size _np_pixels) {
    '_NeoPixel_increaseRGB' i delta
  }
  '_NeoPixel_update'
}

to NeoPixel_shift_all_colors delta {
  for i (size _np_pixels) {
    '_NeoPixel_shift_hue' i delta
  }
  '_NeoPixel_update'
}

to NeoPixel_shift_color i delta {
  '_NeoPixel_shift_hue' i delta
  '_NeoPixel_update'
}

to '_NeoPixel_ensureInitialized' {
  if (_np_pixels == 0) {if (or ((boardType) == 'M5Atom-Matrix') (or ((boardType) == 'Mbits') ((boardType) == 'micro:STEAMakers'))) {
    neoPixelAttach 25 '' false
  } ((boardType) == 'D1-Mini') {
    comment 'D1 mini kit'
    neoPixelAttach 7 15 false
  } ((boardType) == 'Foxbit') {
    neoPixelAttach 35 '' false
  } ((boardType) == 'CodingBox') {
    neoPixelAttach 35 '' false
  } else {
    neoPixelAttach 10 '' false
  }}
}

to '_NeoPixel_increaseRGB' i delta {
  if (or (i < 1) (i > (size _np_pixels))) {return}
  local 'rgb' (at i _np_pixels)
  if (rgb != 0) {
    local 'h' ('[misc:hue]' rgb)
    local 's' ('[misc:saturation]' rgb)
    local 'v' (('[misc:brightness]' rgb) + delta)
    v = (maximum 20 (minimum v 100))
    atPut i _np_pixels ('[misc:hsvColor]' h s v)
  }
}

to '_NeoPixel_rotate' left {
  '_NeoPixel_ensureInitialized'
  local 'length' (size _np_pixels)
  if left {
    local 'first' (at 1 _np_pixels)
    for i (length - 1) {
      atPut i _np_pixels (at (i + 1) _np_pixels)
    }
    atPut length _np_pixels first
  } else {
    local 'last' (at length _np_pixels)
    for i (length - 1) {
      atPut ((length - i) + 1) _np_pixels (at (length - i) _np_pixels)
    }
    atPut 1 _np_pixels last
  }
}

to '_NeoPixel_shift_hue' i delta {
  if (or (i < 1) (i > (size _np_pixels))) {return}
  local 'rgb' (at i _np_pixels)
  if (rgb != 0) {
    local 'h' ((('[misc:hue]' rgb) + delta) % 360)
    local 's' ('[misc:saturation]' rgb)
    local 'v' ('[misc:brightness]' rgb)
    atPut i _np_pixels ('[misc:hsvColor]' h s v)
  }
}

to '_NeoPixel_update' {
  comment 'NeoPixel pin and hasWhite may have been changed by another library.'
  '[display:neoPixelSetPin]' _np_pin _np_hasWhite
  '[display:neoPixelSend]' _np_pixels
  waitMicros 300
}

to clearNeoPixels {
  '_NeoPixel_ensureInitialized'
  atPut 'all' _np_pixels 0
  '_NeoPixel_update'
}

to colorFromRGB r g b {
  r = (maximum 0 (minimum r 255))
  g = (maximum 0 (minimum g 255))
  b = (maximum 0 (minimum b 255))
  return (((r << 16) | (g << 8)) | b)
}

to neoPixelAttach number pinNumber optionalHasWhite {
  _np_pin = pinNumber
  _np_hasWhite = false
  if ((pushArgCount) > 2) {
    _np_hasWhite = optionalHasWhite
  }
  if (or (_np_pixels == 0) (number != (size _np_pixels))) {
    _np_pixels = (newList number)
  }
  atPut 'all' _np_pixels 0
  '[display:neoPixelSetPin]' _np_pin _np_hasWhite
}

to neoPixelSetAllToColor color {
  '_NeoPixel_ensureInitialized'
  atPut 'all' _np_pixels color
  '_NeoPixel_update'
}

to neoPixel_colorSwatch color {
  return color
}

to rotateNeoPixelsBy n {
  '_NeoPixel_ensureInitialized'
  local 'rotateLeft' (n < 0)
  if (or ((boardType) == 'CircuitPlayground') ((boardType) == 'CircuitPlayground Bluefruit')) {
    rotateLeft = (n > 0)
  }
  repeat (absoluteValue n) {
    '_NeoPixel_rotate' rotateLeft
  }
  '_NeoPixel_update'
}

to setNeoPixelColor i color {
  '_NeoPixel_ensureInitialized'
  if (and (1 <= i) (i <= (size _np_pixels))) {
    atPut i _np_pixels color
    '_NeoPixel_update'
  }
}

to setNeoPixelColors10 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 {
  '_NeoPixel_ensureInitialized'
  for i (minimum (size _np_pixels) (pushArgCount)) {
    atPut i _np_pixels (getArg i)
  }
  '_NeoPixel_update'
}

to setNeoPixelColors25 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 c16 c17 c18 c19 c20 c21 c22 c23 c24 c25 {
  '_NeoPixel_ensureInitialized'
  for i (minimum (size _np_pixels) (pushArgCount)) {
    atPut i _np_pixels (getArg i)
  }
  '_NeoPixel_update'
}


module 'Real Time Clock'
author MicroBlocks
version 1 9 
choices rtc_timeOrDate 'time (H:M:S)' 'date (Y-M-D)' 
choices rtc_modules '' DS1307 DS3231 PCF8523 PCF8563 PCF85063 RV1805 SD3031 
choices rtc_component second minute hour day month year 'day of week' 
description 'Real Time Clock (RTC)

Simple functions to set and read the current time and date of a real-time clock module.
With a 3V coin cell battery intalled, the clock will maintain the time and date while powered off.

Currently supports the following RTC modules: DS1307, DS3231, PCF8523, PCF8563, PCF85063, RV1805, SD3031.
May be we will extend the list and support others in the future.

https://www.analog.com/media/en/technical-documentation/data-sheets/DS1307.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/DS3231.pdf
https://www.nxp.com/docs/en/data-sheet/PCF8523.pdf
https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf
https://www.nxp.com/docs/en/data-sheet/PCF85063A.pdf
https://www.nxp.com/docs/en/data-sheet/PCF85063TP.pdf
https://cdn.sparkfun.com/assets/0/8/4/2/f/RV-1805-C3_App-Manual.pdf
https://datasheet.lcsc.com/lcsc/2205310930_WAVE-SD3031_C2988356.pdf

(Many thanks to Ralf Krause)
'
variables rtc_i2cAddr rtc_module rtc_registerMap 

  spec 'r' 'rtc_getTimeOrDate' 'clock _' 'menu.rtc_timeOrDate' 'time (H:M:S)'
  spec 'r' 'rtc_getComponent' 'clock _' 'menu.rtc_component' 'second'
  space
  spec ' ' 'rtc_setTime' 'set hours _ minutes _ seconds _' 'num num num' 9 30 0
  spec ' ' 'rtc_setDate' 'set year _ month _ day _' 'num num num' 2025 1 1
  spec ' ' 'rtc_setDayOfWeek' 'set day of week _ (1-7)' 'num' 1
  advanced
  spec ' ' 'rtc_initModule' 'init clock _' 'menu.rtc_modules' ''
  space
  spec ' ' '_rtc_init' '_rtc_init'
  spec 'r' '_rtc_bcd2dec' '_rtc_bcd2dec _' 'num'
  spec 'r' '_rtc_dec2bcd' '_rtc_dec2bcd _' 'num'
  spec 'r' '_rtc_twoDigit' '_rtc_twoDigit _' 'num'
  spec 'r' '_rtc_getReg' '_rtc_get_register _' 'num' 0
  spec ' ' '_rtc_setReg' '_rtc_set_register _ to _' 'num num' 0 0
  spec 'r' '_rtc_mapRegNum' '_rtc_map_register_number _' 'num' 0
  spec 'r' '_rtc_testReg' '_rtc_test_register _ with _' 'num num' 0 0
  spec 'r' '_rtc_testRegSeconds' '_rtc_test_register _ for seconds?' 'num' 0
  spec ' ' '_rtc_set_SD3031_writable' '_rtc_set_SD3031_writable _' 'bool' true

to '_rtc_bcd2dec' bcd {
  comment 'Convert an 8-bit "binary-coded decimal" number to a decimal number in range 0-99'
  return (((bcd / 16) * 10) + (bcd % 16))
}

to '_rtc_dec2bcd' decimal {
  comment 'Convert a decimal in range 0-99 to an 8-bit "binary-coded decimal" value.'
  decimal = (maximum 0 (minimum decimal 99))
  return (((decimal / 10) * 16) + (decimal % 10))
}

to '_rtc_getReg' reg {
  '_rtc_init'
  if (rtc_i2cAddr == 0) {
    return
  }
  '[sensors:i2cSetClockSpeed]' 100000
  local 'result' (i2cGet rtc_i2cAddr ('_rtc_mapRegNum' reg))
  '[sensors:i2cSetClockSpeed]' 400000
  return result
}

to '_rtc_init' {
  if (rtc_i2cAddr != 0) {return}
  if ('[sensors:i2cExists]' (hexToInt '51')) {
    rtc_i2cAddr = (hexToInt '51')
    comment 'PCF8563 uses register 0x02 for seconds.
PCF85063 uses register 0x04 for seconds.'
    if ('_rtc_testRegSeconds' (hexToInt '02')) {
      rtc_initModule 'PCF8563'
    } else {
      rtc_initModule 'PCF85063'
    }
  } ('[sensors:i2cExists]' (hexToInt '68')) {
    rtc_i2cAddr = (hexToInt '68')
    comment 'DS1307 has register 0x3F.
DS3231 uses register 0x00 for seconds.
PCF8523 uses register 0x03 for seconds.'
    if (and ('_rtc_testReg' (hexToInt '3F') (hexToInt 'AA')) ('_rtc_testReg' (hexToInt '3F') (hexToInt '55'))) {
      rtc_initModule 'DS1307'
    } else {
      if ('_rtc_testRegSeconds' (hexToInt '00')) {
        rtc_initModule 'DS3231'
      } else {
        rtc_initModule 'PCF8523'
      }
    }
  } ('[sensors:i2cExists]' (hexToInt '69')) {
    rtc_i2cAddr = (hexToInt '69')
    comment 'RV1805
No other RTC module uses this i2c address.'
    rtc_initModule 'RV1805'
  } ('[sensors:i2cExists]' (hexToInt '32')) {
    rtc_i2cAddr = (hexToInt '32')
    comment 'SD3031
No other RTC module uses this i2c address.'
    rtc_initModule 'SD3031'
  } else {
    comment 'No RTC module found.'
    rtc_i2cAddr = 0
    rtc_module = ''
    rtc_registerMap = ''
    sayIt 'No RTC module found.'
  }
}

to '_rtc_mapRegNum' reg {
  return (hexToInt (at (reg + 1) ('[data:join]' '' rtc_registerMap)))
}

to '_rtc_setReg' reg value {
  '_rtc_init'
  if (rtc_i2cAddr == 0) {
    return
  }
  '[sensors:i2cSetClockSpeed]' 100000
  i2cSet rtc_i2cAddr ('_rtc_mapRegNum' reg) value
  '[sensors:i2cSetClockSpeed]' 400000
}

to '_rtc_set_SD3031_writable' on {
  if on {
    comment 'SD3031 write enable
1. set WRTC1 = 1 (register 0x10 bit 7)
2. set WRTC2 = 1 and WRTC3 = 1 (register 0x0F bit7 and bit2)'
    i2cSet rtc_i2cAddr (hexToInt '10') ((i2cGet rtc_i2cAddr (hexToInt '10')) | 128)
    i2cSet rtc_i2cAddr (hexToInt '0F') ((i2cGet rtc_i2cAddr (hexToInt '0F')) | 132)
  } else {
    comment 'SD3031 write disable
1. set WRTC2 = 0 and WRTC3 = 0 (register 0x0F bit7 and bit2)
2. set WRTC1 = 0 (register 0x10 bit 7)'
    i2cSet rtc_i2cAddr (hexToInt '0F') ((i2cGet rtc_i2cAddr (hexToInt '0F')) & 123)
    i2cSet rtc_i2cAddr (hexToInt '10') ((i2cGet rtc_i2cAddr (hexToInt '10')) & 127)
  }
}

to '_rtc_testReg' reg num {
  if (rtc_i2cAddr == 0) {return 0}
  comment 'Test if the register exists and can be set to number.'
  '[sensors:i2cSetClockSpeed]' 100000
  local 't' (i2cGet rtc_i2cAddr reg)
  i2cSet rtc_i2cAddr reg num
  local 'result' ((i2cGet rtc_i2cAddr reg) == num)
  if (and (0 <= t) (t <= 255)) {
    i2cSet rtc_i2cAddr reg t
  }
  '[sensors:i2cSetClockSpeed]' 400000
  return result
}

to '_rtc_testRegSeconds' reg {
  comment 'Read the register three times to get the values t0, t1, and t2.'
  '[sensors:i2cSetClockSpeed]' 100000
  local 't0' (i2cGet rtc_i2cAddr reg)
  waitMillis 1000
  local 't1' (i2cGet rtc_i2cAddr reg)
  waitMillis 1000
  local 't2' (i2cGet rtc_i2cAddr reg)
  '[sensors:i2cSetClockSpeed]' 400000
  local 'result' (and (t2 > t1) (t1 > t0))
  comment 'If the result is false perhaps the second gets 0 and the next minute starts.'
  if (t1 == 0) {
    result = (and (t0 > 0) (t2 > t1))
  }
  if (t2 == 0) {
    result = (and (t0 > 0) (t1 > t0))
  }
  return result
}

to '_rtc_twoDigit' n {
  comment 'Return a two-digit decimal representation for n (range 0-99).'
  decimal = (maximum 0 (minimum n 99))
  return ('[data:join]' (ifExpression (n < 10) '0' '') n)
}

to rtc_getComponent which {
  local 'component' ('[data:makeList]' 'second' 'minute' 'hour' 'day of week' 'day' 'month' 'year')
  local 'reg' (('[data:find]' which component) - 1)
  if (reg < 0) {
    return ''
  }
  local 'result' ('_rtc_getReg' reg)
  if (or ('second' == which) ('minute' == which)) {
    return ('_rtc_bcd2dec' (result & 127))
  } (or ('hour' == which) ('day' == which)) {
    return ('_rtc_bcd2dec' (result & 63))
  } ('month' == which) {
    return ('_rtc_bcd2dec' (result & 31))
  } ('year' == which) {
    return (('_rtc_bcd2dec' result) + 2000)
  } ('day of week' == which) {
    result = (result & 7)
    if (0 < ('[data:find]' rtc_module ('[data:makeList]' 'PCF8523' 'PCF8563' 'PCF85063' 'RV1805''SD3031'))) {
      comment 'Clock uses weekday range 0-6.'
      if (result == 0) {
        result = 7
      }
    }
    return result
  } else {
    return ''
  }
}

to rtc_getTimeOrDate which {
  if ('time (H:M:S)' == which) {
    return ('[data:join]' ('_rtc_twoDigit' (rtc_getComponent 'hour')) ':' ('_rtc_twoDigit' (rtc_getComponent 'minute')) ':' ('_rtc_twoDigit' (rtc_getComponent 'second')))
  } else {
    return ('[data:join]' (rtc_getComponent 'year') '-' ('_rtc_twoDigit' (rtc_getComponent 'month')) '-' ('_rtc_twoDigit' (rtc_getComponent 'day')))
  }
}

to rtc_initModule name {
  rtc_i2cAddr = 0
  if (name == '') {
    '_rtc_init'
  } (name == 'DS1307') {
    rtc_i2cAddr = (hexToInt '68')
    rtc_module = 'DS1307'
    rtc_registerMap = ('[data:join]' '' '0123456')
  } (name == 'DS3231') {
    rtc_i2cAddr = (hexToInt '68')
    rtc_module = 'DS3231'
    rtc_registerMap = ('[data:join]' '' '0123456')
  } (name == 'PCF8523') {
    rtc_i2cAddr = (hexToInt '68')
    rtc_module = 'PCF8523'
    rtc_registerMap = 3457689
  } (name == 'PCF8563') {
    rtc_i2cAddr = (hexToInt '51')
    rtc_module = 'PCF8563'
    rtc_registerMap = 2346578
  } (name == 'PCF85063') {
    rtc_i2cAddr = (hexToInt '51')
    rtc_module = 'PCF85063'
    rtc_registerMap = '456879A'
  } (name == 'RV1805') {
    rtc_i2cAddr = (hexToInt '69')
    rtc_module = 'RV1805'
    rtc_registerMap = 1237456
  } (name == 'SD3031') {
    rtc_i2cAddr = (hexToInt '32')
    rtc_module = 'SD3031'
    rtc_registerMap = ('[data:join]' '' '0123456')
    '_rtc_set_SD3031_writable' true
  } else {
    rtc_i2cAddr = 0
    rtc_module = ''
    rtc_registerMap = ''
    sayIt 'RTC module not found.'
  }
}

to rtc_setDate year month day {
  day = (maximum 1 (minimum day 31))
  month = (maximum 1 (minimum month 12))
  year = (maximum 0 (minimum (year - 2000) 99))
  '_rtc_setReg' 4 ('_rtc_dec2bcd' day)
  '_rtc_setReg' 5 ('_rtc_dec2bcd' month)
  '_rtc_setReg' 6 ('_rtc_dec2bcd' year)
}

to rtc_setDayOfWeek weekdayNum {
  '_rtc_init'
  weekdayNum = (maximum 1 (minimum weekdayNum 7))
  if (0 < ('[data:find]' rtc_module ('[data:makeList]' 'PCF8523' 'PCF8563' 'PCF85063' 'RV1805' 'SD3031'))) {
    comment 'Clock uses weekday range 0-6.'
    if (weekdayNum == 7) {
      weekdayNum = 0
    }
  }
  '_rtc_setReg' 3 ('_rtc_dec2bcd' weekdayNum)
}

to rtc_setTime hours minutes seconds {
  '_rtc_init'
  comment 'Clock is running (bit 7 of seconds = 0).
Clock is using 24-hour time (bit 6 of hours = 0).'
  seconds = (maximum 0 (minimum seconds 59))
  minutes = (maximum 0 (minimum minutes 59))
  hours = (maximum 0 (minimum hours 23))
  '_rtc_setReg' 0 ('_rtc_dec2bcd' seconds)
  '_rtc_setReg' 1 ('_rtc_dec2bcd' minutes)
  if (rtc_module == 'SD3031') {
    comment 'SD3031 is using 24-hour time (bit 7 of hours = 1).'
    '_rtc_setReg' 2 (('_rtc_dec2bcd' hours) | 128)
  } else {
    '_rtc_setReg' 2 ('_rtc_dec2bcd' hours)
  }
}


module Servo Output
author MicroBlocks
version 1 4 
tags servo motor angle rotation position 
description 'Control both positional (angle) and rotational servo motors.
'
variables _servoPin _servoPulseWidth 

  spec ' ' 'setServoAngle' 'set servo _ to _ degrees (-90 to 90)' 'num num' 1 90
  spec ' ' 'setServoSpeed' 'set servo _ to speed _ (-100 to 100)' 'num num' 1 100
  spec ' ' 'stopServo' 'stop servo _' 'num' 1
  spec 'r' '_servoIndex' '_servoIndex _' 'num' 1
  spec ' ' '_servoPulse' '_servoPulse pin _ usecs _' 'num num' 1 1500
  spec ' ' '_servoUpdateLoop' '_servoUpdateLoop'

to '_servoIndex' which {
  if (_servoPin == 0) {
    _servoPin = ('[data:makeList]')
    _servoPulseWidth = ('[data:makeList]')
    sendBroadcast '_servoUpdateLoop'
  }
  local 'i' ('[data:find]' which _servoPin)
  if (i < 0) {
    comment 'Add new pin'
    '[data:addLast]' which _servoPin
    '[data:addLast]' 1500 _servoPulseWidth
    i = (size _servoPin)
  }
  return i
}

to '_servoPulse' pin usecs {
  if (usecs == 0) {
    comment 'Servo stopped; do nothing'
    return 0
  }
  usecs = (maximum 500 (minimum usecs 2900))
  comment 'Split wait into a long wait followed by a wait of <= 30 usecs for greater accuracy'
  local 'endTime' ((microsOp) + usecs)
  digitalWriteOp pin true
  waitMicros (usecs - 30)
  waitMicros (endTime - (microsOp))
  digitalWriteOp pin false
}

to '_servoUpdateLoop' {
  forever {
    if (_servoPin != 0) {
      comment 'If the _servoPin list is not 0, update the servos'
      for i (size _servoPin) {
        local 'pin' (at i _servoPin)
        local 'usecs' (at i _servoPulseWidth)
        if (and (pin >= 0) (usecs != 0)) {
          '_servoPulse' pin usecs
        }
      }
      waitMillis 15
    }
  }
}

to setServoAngle which degrees optionalReverse {
  local 'reversed' false
  if ((pushArgCount) > 2) {
    reversed = optionalReverse
  }
  if reversed {
    degrees = (0 - degrees)
  }
  local 'pulseWidth' (1500 - (10 * degrees))
  if ('[io:hasServo]') {
    '[io:setServo]' which pulseWidth
  } else {
    atPut ('_servoIndex' which) _servoPulseWidth pulseWidth
  }
}

to setServoSpeed which speed optionalReverse {
  local 'reversed' false
  if ((pushArgCount) > 2) {
    reversed = optionalReverse
  }
  if reversed {
    speed = (0 - speed)
  }
  local 'pulseWidth' (1500 - (10 * speed))
  if ((absoluteValue speed) < 2) {
    pulseWidth = 0
  }
  if ('[io:hasServo]') {
    '[io:setServo]' which pulseWidth
  } else {
    atPut ('_servoIndex' which) _servoPulseWidth pulseWidth
  }
}

to stopServo which {
  if ('[io:hasServo]') {
    '[io:setServo]' which 0
  } else {
    atPut ('_servoIndex' which) _servoPulseWidth 0
  }
}


module TFT Output
author MicroBlocks
version 1 19 
description 'Draw graphics and write text on boards with a TFT display, such as the M5Stack, M5Stick, Citilab ED1 or (discontinued) IoT-Bus.'

  spec ' ' '[tft:clear]' 'clear TFT display'
  space
  spec ' ' '[tft:rect]' 'draw rectangle on TFT at x _ y _ width _ height _ color _ : filled _' 'num num num num color bool' 10 10 40 30 nil true
  spec ' ' '[tft:roundedRect]' 'draw rounded rectangle on TFT at x _ y _ width _ height _ radius _ color _ : filled _' 'num num num num num color bool' 10 10 40 30 8 nil true
  spec ' ' '[tft:circle]' 'draw circle on TFT at x _ y _ radius _ color _ : filled _' 'num num num color bool' 40 40 30 nil true
  spec ' ' '[tft:triangle]' 'draw triangle on TFT at x _ y _ , x _ y _ , x _ y _ color _ : filled _' 'num num num num num num color bool' 20 20 30 80 60 5 nil true
  spec ' ' '[tft:line]' 'draw line on TFT from x _ y _ to x _ y _ color _' 'num num num num color' 12 8 25 15
  spec ' ' 'tft_drawVector' 'draw vector x _ y _ angle _ length _ color _' 'num num num num color' 40 40 45 40
  space
  spec ' ' '[tft:text]' 'write _ on TFT at x _ y _ color _ : scale _ wrap _ : bg color _' 'str num num color num bool color' 'Hello World!' 5 5 nil 2 true
  spec ' ' 'tft_drawText' 'draw text _ on TFT at x _ y _ color _ : scale _ : bg color _' 'str num num color num color' 'Line 1
Line 2' 50 20 nil 2
  space
  spec ' ' '[tft:setPixel]' 'set TFT pixel x _ y _ to _' 'num num color' 10 10
  spec ' ' '[tft:pixelRow]' 'draw pixel row _ x _ y _ : bytes per pixel _ : palette _' 'auto num num num str' 'aList' 0 0 4
  spec ' ' '[tft:drawBitmap]' 'draw bitmap _ palette _ on TFT at x _ y _' 'str str num num' 'aBitmap' 'a list of colors' 10 10
  space
  spec 'r' 'tft_colorSwatch' '_' 'color'
  spec 'r' 'makeColor' 'color r _ g _ b _ (0-255)' 'num num num' 0 100 100
  spec 'r' 'makeGray' 'gray _ %' 'num' 50
  spec 'r' 'randomColor' 'random color'
  space
  spec ' ' 'tft_drawImage' 'draw image _ on TFT at x _ y _ color _ : #BR# scale _' 'str num num color num' '' 0 0 nil 1
  spec 'r' 'tft_image' 'image _' 'str' '00000
01010
00000
10001
01110'
  space
  spec 'r' '[tft:getWidth]' 'TFT width'
  spec 'r' '[tft:getHeight]' 'TFT height'
  space
  spec ' ' '[tft:setBacklight]' 'set TFT backlight _ (0-10)' 'num' 10
  spec ' ' '[tft:invertDisplay]' 'invert TFT display _' 'bool' false
  advanced
  spec ' ' 'tft_deferOLEDDisplayUpdates' 'defer OLED display updates'
  spec ' ' 'tft_resumeOLEDDisplayUpdates' 'resume OLED display updates'
  space
  spec ' ' '_deferMonochromeDisplayUpdates' '_defer monochrome display updates'
  spec ' ' '_resumeMonochromeDisplayUpdates' '_resume monochrome display updates'

to '_deferMonochromeDisplayUpdates' {
  '[tft:deferUpdates]'
}

to '_resumeMonochromeDisplayUpdates' {
  '[tft:resumeUpdates]'
}

to makeColor r g b {
  r = (maximum 0 (minimum r 255))
  g = (maximum 0 (minimum g 255))
  b = (maximum 0 (minimum b 255))
  return ((r << 16) | ((g << 8) | b))
}

to makeGray percent {
  gray = ((percent * 255) / 100)
  gray = (maximum 0 (minimum gray 255))
  return ((gray << 16) | ((gray << 8) | gray))
}

to randomColor {
  local 'n1' (random 100 200)
  local 'n2' (random 0 100)
  if (1 == (random 1 3)) {
    return ((n1 << 16) | (n2 << 8))
  } (1 == (random 1 2)) {
    return ((n2 << 16) | n1)
  } else {
    return ((n1 << 8) | n2)
  }
}

to tft_colorSwatch color {
  return color
}

to tft_deferOLEDDisplayUpdates {
  '[tft:deferUpdates]'
}

to tft_drawImage imageString x y c optionalScale {
  comment 'Draw an image at the given position. An image is represented by a multi-line string
of 0 and 1 characters where each line is one row of the image. 1 means that pixel on (lit).'
  local 'scale' (argOrDefault 5 1)
  local 'left' x
  for ch ('[data:convertType]' imageString 'byte array') {
    if (ch == 10) {
      y += scale
      x = left
    } else {
      if (ch == 49) {'[tft:rect]' x y scale scale c}
      x += scale
    }
  }
}

to tft_drawText s x y color optionalScale optionalBGColor {
  s = ('[data:convertType]' s 'string')
  local 'scale' (argOrDefault 5 2)
  local 'bgColor' (argOrDefault 6 '')
  local 'lines' ('[data:split]' s ('[data:unicodeString]' 10))
  for line ('[data:split]' s ('[data:unicodeString]' 10)) {
    if (isType bgColor 'number') {
      '[tft:text]' line x y color scale false bgColor
    } else {
      '[tft:text]' line x y color scale false
    }
    y += (8 * scale)
  }
}

to tft_drawVector x y angle length color {
  local 'endX' (x + ((length * ('[misc:sin]' (100 * (angle + 90)))) >> 14))
  local 'endY' (y + ((length * ('[misc:sin]' (100 * angle))) >> 14))
  '[tft:line]' x y endX endY color
}

to tft_image s {
  return ('[data:convertType]' s 'string')
}

to tft_resumeOLEDDisplayUpdates {
  '[tft:resumeUpdates]'
}


module 'Temperature Humidity (DHT11, DHT22)' Input
author MicroBlocks
version 1 3 
tags sensor dht11 dht22 temperature humidity 
description 'Support for the DHT11 and DHT22 environmental sensors. These sensors provide temperature and humidity readings.
'
variables _dht_temperature _dht_humidity _dhtData _dhtLastReadTime data 

  spec 'r' 'temperature_DHT11' 'temperature (Celsius) DHT11 pin _' 'auto' 4
  spec 'r' 'humidity_DHT11' 'humidity DHT11 pin _' 'auto' 4
  spec 'r' 'temperature_DHT22' 'temperature (Celsius) DHT22 pin _' 'auto' 4
  spec 'r' 'humidity_DHT22' 'humidity DHT22 pin _' 'auto' 4
  spec ' ' '_dhtReadData' '_dhtReadData pin _' 'auto any' 4
  spec 'r' '_dhtChecksumOkay' '_dhtChecksumOkay' 'any'
  spec ' ' '_dhtUpdate' '_dhtUpdate _ isDHT11 _' 'auto bool any' 4 true
  spec 'r' '_dhtReady' '_dhtReady' 'any'
  spec ' ' '_dhtCaptureData' '_dhtCaptureData _' 'num' 4

to '_dhtCaptureData' pin {
  '[sensors:captureStart]' pin
  waitMillis 10
  local 'pulses' ('[sensors:captureEnd]')
  local 'pulseCount' (size pulses)
  if (pulseCount < 80) {
    return 0
  } (pulseCount > 80) {
    pulses = ('[data:copyFromTo]' pulses (pulseCount - 79))
  }
  data = pulses
  local 'byte' 0
  for i 40 {
    if ((at ((2 * i) - 1) pulses) > 40) {
      comment 'Long pulse - appends a "1" bit'
      byte += 1
    }
    if ((i % 8) == 0) {
      atPut (i / 8) _dhtData byte
      byte = 0
    } else {
      byte = (byte << 1)
    }
  }
}

to '_dhtChecksumOkay' {
  if (not (isType _dhtData 'list')) {return (booleanConstant false)}
  local 'checksum' 0
  for i 4 {
    checksum += (at i _dhtData)
  }
  checksum = (checksum & 255)
  return (checksum == (at 5 _dhtData))
}

to '_dhtReadData' pin {
  comment 'Create DHT data array the first time'
  if (_dhtData == 0) {
    _dhtData = (newList 5)
  }
  comment 'fill with 1''s set checksum will be bad if read fails'
  atPut 'all' _dhtData 1
  comment 'Pull pin low for >18msec to request data'
  digitalWriteOp pin false
  waitMillis 20
  local 'useDHTPrimitive' (booleanConstant true)
  if useDHTPrimitive {
    '_dhtCaptureData' pin
    return 0
  }
  comment 'Read DHT start pulses (H L H L)'
  waitUntil (digitalReadOp pin)
  waitUntil (not (digitalReadOp pin))
  waitUntil (digitalReadOp pin)
  waitUntil (not (digitalReadOp pin))
  local 'i' 1
  local 'byte' 0
  local 'bit' 1
  comment 'Read 40 bits (5 bytes)'
  repeat 40 {
    waitUntil (digitalReadOp pin)
    local 'start' (microsOp)
    waitUntil (not (digitalReadOp pin))
    if (((microsOp) - start) > 40) {
      comment 'Long pulse - append a "1" bit'
      byte += 1
    }
    if (bit == 8) {
      atPut i _dhtData byte
      i += 1
      byte = 0
      bit = 1
    } else {
      byte = (byte << 1)
      bit += 1
    }
    waitUntil (not (digitalReadOp pin))
  }
}

to '_dhtReady' {
  local 'elapsed' ((millisOp) - _dhtLastReadTime)
  return (or (elapsed < 0) (elapsed > 2000))
}

to '_dhtUpdate' pin isDHT11 {
  if ('_dhtReady') {
    _dht_temperature = 0
    _dht_humidity = 0
    '_dhtReadData' pin
    _dhtLastReadTime = (millisOp)
  }
  if ('_dhtChecksumOkay') {
    if isDHT11 {
      _dht_temperature = (at 3 _dhtData)
      _dht_humidity = (at 1 _dhtData)
    } else {
      local 'n' (((at 1 _dhtData) * 256) + (at 2 _dhtData))
      _dht_humidity = ((n + 5) / 10)
      n = ((((at 3 _dhtData) & 127) * 256) + (at 4 _dhtData))
      if (((at 3 _dhtData) & 128) != 0) {
        n = (0 - n)
      }
      _dht_temperature = ((n + 5) / 10)
    }
  }
}

to humidity_DHT11 pin {
  '_dhtUpdate' pin true
  return _dht_humidity
}

to humidity_DHT22 pin {
  '_dhtUpdate' pin false
  return _dht_humidity
}

to temperature_DHT11 pin {
  '_dhtUpdate' pin true
  return _dht_temperature
}

to temperature_DHT22 pin {
  '_dhtUpdate' pin false
  return _dht_temperature
}


module Tone Output
author MicroBlocks
version 1 11 
tags tone sound music audio note speaker 
choices tone_NoteName 'nt;c' 'nt;c#' 'nt;d' 'nt;d#' 'nt;e' 'nt;f' 'nt;f#' 'nt;g' 'nt;g#' 'nt;a' 'nt;a#' 'nt;b' 
description 'Audio tone generation. Make music with MicroBlocks!
'
variables _tonePin _toneInitalized _toneLoopOverhead _toneNoteNames _toneArezzoNotes _toneFrequencies 

  spec ' ' 'play tone' 'play note _ octave _ for _ ms' 'str.tone_NoteName num num' 'nt;c' 0 500
  spec ' ' 'playMIDIKey' 'play midi key _ for _ ms' 'num num' 60 500
  spec ' ' 'play frequency' 'play frequency _ for _ ms' 'num num' 261 500
  space
  spec ' ' 'startTone' 'start tone _ Hz' 'num' 440
  spec ' ' 'tone_startNote' 'start note _ octave _' 'str.tone_NoteName num' 'nt;c' 0
  spec ' ' 'tone_startMIDIKey' 'start midi key _' 'num' 60
  spec ' ' 'stopTone' 'stop tone'
  space
  spec ' ' 'attach buzzer to pin' 'attach buzzer to pin _' 'auto' ''
  space
  spec 'r' '_measureLoopOverhead' '_measureLoopOverhead'
  spec 'r' '_baseFreqForNote' '_baseFreqForNote _' 'auto' 'c'
  spec 'r' '_baseFreqForSemitone' '_baseFreqForSemitone _' 'num' 0
  spec ' ' '_toneLoop' '_toneLoop _ for _ ms' 'num num' 440000 100
  spec 'r' '_trimmedLowercase' '_trimmedLowercase _' 'str' 'A. b C...'
  spec ' ' '_tone init note names' '_tone init note names'

to '_baseFreqForNote' note {
  comment 'Return the frequency for the given note in the middle-C octave
scaled by 1000. For example, return 440000 (440Hz) for A.
Note names may be upper or lower case. Note names
may be followed by # for a sharp or b for a flat.'
  local 'normalized note' ('_trimmedLowercase' note)
  'normalized note' = (ifExpression ((at 1 (v 'normalized note')) == 'n') (v 'normalized note') ('[data:join]' 'nt;' (v 'normalized note')))
  '_tone init note names'
  if (('[data:find]' (v 'normalized note') _toneArezzoNotes) > 0) {
    return ('_baseFreqForSemitone' ('[data:find]' (v 'normalized note') _toneArezzoNotes))
  } else {
    return ('_baseFreqForSemitone' ('[data:find]' (v 'normalized note') _toneNoteNames))
  }
}

to '_baseFreqForSemitone' semitone {
  if (_toneFrequencies == 0) {_toneFrequencies = ('[data:makeList]' 261626 277183 293665 311127 329628 349228 369994 391995 415305 440000 466164 493883 246942 277183 277183 311127 311127 349228 329628 369994 369994 415305 415305 466164 466164 523252)}
  if (and (1 <= semitone) (semitone <= (size _toneFrequencies))) {
    return (at semitone _toneFrequencies)
  } else {
    comment 'Bad note name; return 10 Hz'
    return 10000
  }
}

to '_measureLoopOverhead' {
  comment 'Measure the loop overhead on this device'
  local 'halfCycle' 100
  local 'startT' (microsOp)
  repeat 100 {
    digitalWriteOp _tonePin false
    waitMicros halfCycle
    digitalWriteOp _tonePin false
    waitMicros halfCycle
  }
  local 'usecs' ((microsOp) - startT)
  return ((usecs - 20000) / 200)
}

to '_tone init note names' {
  if (_toneNoteNames == 0) {
    _toneNoteNames = ('[data:makeList]' 'nt;c' 'nt;c#' 'nt;d' 'nt;d#' 'nt;e' 'nt;f' 'nt;f#' 'nt;g' 'nt;g#' 'nt;a' 'nt;a#' 'nt;b' 'nt;c_' 'nt;db' 'nt;d_' 'nt;eb' 'nt;e_' 'nt;e#' 'nt;f_' 'nt;gb' 'nt;g_' 'nt;ab' 'nt;a_' 'nt;bb' 'nt;b_' 'nt;b#')
    _toneArezzoNotes = ('[data:makeList]' 'nt;do' 'nt;do#' 'nt;re' 'nt;re#' 'nt;mi' 'nt;fa' 'nt;fa#' 'nt;sol' 'nt;sol#' 'nt;la' 'nt;la#' 'nt;si' 'nt;do_' 'nt;dob' 'nt;re_' 'nt;reb' 'nt;mi_' 'nt;mi#' 'nt;fa_' 'nt;solb' 'nt;sol_' 'nt;lab' 'nt;la_' 'nt;sib' 'nt;si_' 'nt;si#')
  }
}

to '_toneLoop' scaledFreq ms {
  if (_toneInitalized == 0) {'attach buzzer to pin' ''}
  if ('[io:hasTone]') {
    '[io:playTone]' _tonePin (scaledFreq / 1000)
    waitMillis ms
    '[io:playTone]' _tonePin 0
  } else {
    local 'halfCycle' ((500000000 / scaledFreq) - _toneLoopOverhead)
    local 'cycles' ((ms * 500) / halfCycle)
    repeat cycles {
      digitalWriteOp _tonePin true
      waitMicros halfCycle
      digitalWriteOp _tonePin false
      waitMicros halfCycle
    }
  }
}

to '_trimmedLowercase' s {
  comment 'Return a copy of the given string without whitespace
or periods and all lowercase.'
  local 'result' (newList (size s))
  '[data:delete]' 'all' result
  for i (size s) {
    local 'ch' ('[data:unicodeAt]' i s)
    if (and (ch > 32) (ch != 46)) {
      if (and (65 <= ch) (ch <= 90)) {ch = (ch + 32)}
      '[data:addLast]' ch result
    }
  }
  return ('[data:unicodeString]' result)
}

to 'attach buzzer to pin' pinNumber {
  if (pinNumber == '') {
    comment 'Pin number not specified; use default pin for this device'
    if ((boardType) == 'Citilab ED1') {
      _tonePin = 26
    } ((boardType) == 'M5Stack-Core') {
      _tonePin = 25
    } ((boardType) == 'M5StickC') {
      _tonePin = 26
    } ((boardType) == 'Calliope') {
      digitalWriteOp 23 true
      digitalWriteOp 24 true
      _tonePin = 25
    } ((boardType) == 'D1-Mini') {
      _tonePin = 12
    } ((boardType) == 'CodingBox') {
      _tonePin = 32
    } else {
      _tonePin = -1
    }
  } else {
    _tonePin = pinNumber
  }
  _toneLoopOverhead = ('_measureLoopOverhead')
  _toneInitalized = (booleanConstant true)
}

to 'play frequency' freq ms {
  '_toneLoop' (freq * 1000) ms
}

to 'play tone' note octave ms {
  local 'freq' ('_baseFreqForNote' note)
  if (freq <= 10000) {
    waitMillis ms
    return 0
  }
  if (octave < 0) {
    repeat (absoluteValue octave) {
      freq = (freq / 2)
    }
  }
  repeat octave {
    freq = (freq * 2)
  }
  '_toneLoop' freq ms
}

to playMIDIKey key ms {
  local 'freq' ('_baseFreqForSemitone' ((key % 12) + 1))
  local 'octave' ((key / 12) - 5)
  if (octave < 0) {
    repeat (absoluteValue octave) {
      freq = (freq / 2)
    }
  }
  repeat octave {
    freq = (freq * 2)
  }
  '_toneLoop' freq ms
}

to startTone freq {
  if (_toneInitalized == 0) {'attach buzzer to pin' ''}
  if ('[io:hasTone]') {'[io:playTone]' _tonePin freq}
}

to stopTone {
  startTone 0
}

to tone_startMIDIKey key {
  local 'freq' ('_baseFreqForSemitone' ((key % 12) + 1))
  local 'octave' ((key / 12) - 5)
  if (octave < 0) {
    repeat (absoluteValue octave) {
      freq = (freq / 2)
    }
  }
  repeat octave {
    freq = (freq * 2)
  }
  startTone ((freq + 500) / 1000)
}

to tone_startNote note octave {
  local 'freq' ('_baseFreqForNote' note)
  if (freq <= 10000) {
    return 0
  }
  if (octave < 0) {
    repeat (absoluteValue octave) {
      freq = (freq / 2)
    }
  }
  repeat octave {
    freq = (freq * 2)
  }
  startTone ((freq + 500) / 1000)
}

