diff --git a/main.py b/main.py new file mode 100644 index 0000000..154437c --- /dev/null +++ b/main.py @@ -0,0 +1,70 @@ +import uPyConfig +import init_sample + +hw = uPyConfig.esp8266(variant='d1-r2') +#print family, variant and IP address (using oled, if available on-board) +init_sample.init_sample(hw) + +# Main app +from uPySensor import LM75A, SHT21 +thsen=SHT21(hw.i2c.bus) +tsen=LM75A(hw.i2c.bus) +json={ + 'sht21_t': '{"temperature":"%0.3f"}', + 'sht21_h': '{"humidity":"%0.3f"}', + 'sht21': '{"temperature":"%0.3f","humidity":"%0.3f"}', + 'lm75a_t': '{"temperature":"%0.1f"}', + 'lm75a': '{"temperature":"%0.1f"}', +} +json_all='{"sht21":{"temperature":"%0.3f","humidity":"%0.3f"},"lm75a":{"temperature":"%0.1f"}}' + +def header(): + return "HTTP/1.1 200 OK\r\n" \ + "Content-Type: application/json\r\n" \ + "Server: horny\r\n" \ + "\r\n" + +def get(): + return json_all % (thsen.read_tempC(), thsen.read_hum(), tsen.read_tempC()) + +def get_sht21_t(): + return json['sht21_t'] % (thsen.read_tempC()) + +def get_sht21_h(): + return json['sht21_h'] % (thsen.read_hum()) + +def get_sht21(): + return json['sht21'] % (thsen.read_tempC(), thsen.read_hum()) + +def get_lm75a_t(): + return json['lm75a_t'] % (tsen.read_tempC()) + +def get_lm75a(): + return json['lm75a'] % (tsen.read_tempC()) + +#begin web +import socket +wssock=socket.getaddrinfo("0.0.0.0",80)[0][-1] +wss=socket.socket() +wss.bind(wssock) +wss.listen(1) + +print("Server started on 0.0.0.0:80") + +#main loop +while True: + wcl, wcsock = wss.accept() + print("Client connected") + wclfh = wcl.makefile('rwb', 0) + jsn='' + while True: + wcline = wclfh.readline() + if not wcline or wcline == b'\r\n': break + wcline=wcline.split(b' ') + if len(wcline) == 3 and wcline[2].startswith('HTTP'): + if wcline[0] != b'GET': wcl.close() + elif wcline[1] == b'/': jsn=get() + elif wcline[1] == b'/sht21': jsn=get_sht21() + elif wcline[1] == b'/lm75a': jsn=get_lm75a() + wcl.send(header()+jsn) + wcl.close() diff --git a/ssd1306.py b/ssd1306.py new file mode 100644 index 0000000..e949137 --- /dev/null +++ b/ssd1306.py @@ -0,0 +1,168 @@ +# MicroPython SSD1306 OLED driver, I2C and SPI interfaces + +import time +import framebuf + + +# register definitions +SET_CONTRAST = const(0x81) +SET_ENTIRE_ON = const(0xa4) +SET_NORM_INV = const(0xa6) +SET_DISP = const(0xae) +SET_MEM_ADDR = const(0x20) +SET_COL_ADDR = const(0x21) +SET_PAGE_ADDR = const(0x22) +SET_DISP_START_LINE = const(0x40) +SET_SEG_REMAP = const(0xa0) +SET_MUX_RATIO = const(0xa8) +SET_COM_OUT_DIR = const(0xc0) +SET_DISP_OFFSET = const(0xd3) +SET_COM_PIN_CFG = const(0xda) +SET_DISP_CLK_DIV = const(0xd5) +SET_PRECHARGE = const(0xd9) +SET_VCOM_DESEL = const(0xdb) +SET_CHARGE_PUMP = const(0x8d) + + +class SSD1306: + def __init__(self, width, height, external_vcc): + self.width = width + self.height = height + self.external_vcc = external_vcc + self.pages = self.height // 8 + # Note the subclass must initialize self.framebuf to a framebuffer. + # This is necessary because the underlying data buffer is different + # between I2C and SPI implementations (I2C needs an extra byte). + self.poweron() + self.init_display() + + def init_display(self): + for cmd in ( + SET_DISP | 0x00, # off + # address setting + SET_MEM_ADDR, 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE | 0x00, + SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 + SET_MUX_RATIO, self.height - 1, + SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 + SET_DISP_OFFSET, 0x00, + SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12, + # timing and driving scheme + SET_DISP_CLK_DIV, 0x80, + SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1, + SET_VCOM_DESEL, 0x30, # 0.83*Vcc + # display + SET_CONTRAST, 0xff, # maximum + SET_ENTIRE_ON, # output follows RAM contents + SET_NORM_INV, # not inverted + # charge pump + SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, + SET_DISP | 0x01): # on + self.write_cmd(cmd) + self.fill(0) + self.show() + + def poweroff(self): + self.write_cmd(SET_DISP | 0x00) + + def contrast(self, contrast): + self.write_cmd(SET_CONTRAST) + self.write_cmd(contrast) + + def invert(self, invert): + self.write_cmd(SET_NORM_INV | (invert & 1)) + + def show(self): + x0 = 0 + x1 = self.width - 1 + if self.width == 64: + # displays with width of 64 pixels are shifted by 32 + x0 += 32 + x1 += 32 + self.write_cmd(SET_COL_ADDR) + self.write_cmd(x0) + self.write_cmd(x1) + self.write_cmd(SET_PAGE_ADDR) + self.write_cmd(0) + self.write_cmd(self.pages - 1) + self.write_framebuf() + + def fill(self, col): + self.framebuf.fill(col) + + def pixel(self, x, y, col): + self.framebuf.pixel(x, y, col) + + def scroll(self, dx, dy): + self.framebuf.scroll(dx, dy) + + def text(self, string, x, y, col=1): + self.framebuf.text(string, x, y, col) + + +class SSD1306_I2C(SSD1306): + def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False): + self.i2c = i2c + self.addr = addr + self.temp = bytearray(2) + # Add an extra byte to the data buffer to hold an I2C data/command byte + # to use hardware-compatible I2C transactions. A memoryview of the + # buffer is used to mask this byte from the framebuffer operations + # (without a major memory hit as memoryview doesn't copy to a separate + # buffer). + self.buffer = bytearray(((height // 8) * width) + 1) + self.buffer[0] = 0x40 # Set first byte of data buffer to Co=0, D/C=1 + self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height) + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.temp[0] = 0x80 # Co=1, D/C#=0 + self.temp[1] = cmd + self.i2c.writeto(self.addr, self.temp) + + def write_framebuf(self): + # Blast out the frame buffer using a single I2C transaction to support + # hardware I2C interfaces. + self.i2c.writeto(self.addr, self.buffer) + + def poweron(self): + pass + + +class SSD1306_SPI(SSD1306): + def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): + self.rate = 10 * 1024 * 1024 + dc.init(dc.OUT, value=0) + res.init(res.OUT, value=0) + cs.init(cs.OUT, value=1) + self.spi = spi + self.dc = dc + self.res = res + self.cs = cs + self.buffer = bytearray((height // 8) * width) + self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height) + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs.high() + self.dc.low() + self.cs.low() + self.spi.write(bytearray([cmd])) + self.cs.high() + + def write_framebuf(self): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs.high() + self.dc.high() + self.cs.low() + self.spi.write(self.buffer) + self.cs.high() + + def poweron(self): + self.res.high() + time.sleep_ms(1) + self.res.low() + time.sleep_ms(10) + self.res.high() diff --git a/uPyConfig.py b/uPyConfig.py new file mode 100644 index 0000000..d4aabe4 --- /dev/null +++ b/uPyConfig.py @@ -0,0 +1,158 @@ +class uPyConfig: + from machine import I2C, Pin + class features: + class connectivity: + wifi = False + bluetooth = False + bluetooth_le = False + lora = False + + class display: + oled = False + + class sensor: + capacitive = False + temperature = False + hall_effect = False + + class _i2c: + from machine import I2C, Pin + + def load_handle(self): + self.bus = self.I2C(scl=self.scl, sda=self.sda) + self.bus.init(scl=self.scl, sda=self.sda) + + def __init__(self, scl, sda): + self.scl=self.Pin(scl) + self.sda=self.Pin(sda) + self.load_handle() + + class _oled: + from machine import Pin + from time import sleep_ms + addr = 0x0 + rst = 0 + rst_hold = False + height = 0 + width = 0 + + def load_handle(self): + if self.rst_hold: + self.rst = self.Pin(self.rst, self.Pin.OUT) + self.rst.value(0) + self.sleep_ms(2) + self.rst.value(1) + from ssd1306 import SSD1306_I2C + self.handle = SSD1306_I2C(self.width, self.height, self.i2c.bus) + + def __init__(self, i2c): + self.i2c = i2c + + def __init__(self, board_defs): + self.i2c = self._i2c(board_defs['i2c_scl'], board_defs['i2c_sda']) + if 'has_wifi' in board_defs.keys(): self.features.connectivity.wifi = True + if 'has_bluetooth' in board_defs.keys(): self.features.connectivity.bluetooth = True + if 'has_bluetooth_le' in board_defs.keys(): self.features.connectivity.bluetooth_le = True + if 'sen_capacitive' in board_defs.keys(): self.features.sensor.capacitive = True + if 'sen_temperature' in board_defs.keys(): self.features.sensor.temperature = True + if 'sen_hall' in board_defs.keys(): self.features.sensor.hall_effect = True + if 'has_oled' in board_defs.keys(): + self.features.display.oled = True + self.oled = self._oled(self.i2c) + self.oled.addr = board_defs['oled_addr'] + if 'oled_hold' in board_defs.keys(): self.oled.rst_hold = board_defs['oled_hold'] + if 'oled_rst' in board_defs.keys(): self.oled.rst = board_defs['oled_rst'] + self.oled.width = board_defs['oled_width'] + self.oled.height = board_defs['oled_height'] + self.oled.load_handle() + +class esp8266(uPyConfig): + family = 'esp8266' + + defaults = { + 'has_wifi': True, + 'i2c_scl': 4, + 'i2c_sda': 5, + 'oled_addr': 0x3c, + } + + variants = { + 'generic': {}, + 'lolinv3': { + 'i2c_scl': 4, + 'i2c_sda': 5, + }, + 'd1-r2': { + 'i2c_scl': 5, + 'i2c_sda': 4, + }, + 'heltec': { + 'i2c_scl': 5, + 'i2c_sda': 4, + 'oled_rst': 16, + 'has_oled': True, + 'oled_hold': True, + 'oled_height': 32, + 'oled_width': 128, + }, + 'ttgo': { + 'i2c_scl': 14, + 'i2c_sda': 2, + 'oled_rst': 4, + 'has_oled': True, + 'oled_hold': True, + 'oled_height': 32, + 'oled_width': 128, + }, + } + + def __init__(self, variant='generic'): + if variant in self.variants.keys(): + self.variant = variant + defs = self.defaults.copy() + defs.update(self.variants[variant]) + super().__init__(defs) + else: + raise ValueError("Board variant '%s' is not known" % variant) + +class esp32(uPyConfig): + family = 'esp32' + + defaults = { + 'has_wifi': True, + 'has_bluetooth': True, + 'has_bluetooth_le': True, + 'sen_capacitive': True, + 'sen_temperature': True, + 'sen_hall': True, + 'i2c_scl': 4, + 'i2c_sda': 5, + 'oled_addr': 0x3c, + } + + variants = { + 'generic': {}, + 'wemos-lolin32': { + 'has_oled': True, + 'oled_height': 64, + 'oled_width': 128, + }, + 'heltec': { + 'i2c_scl': 15, + 'i2c_sda': 4, + 'oled_rst': 16, + 'has_oled': True, + 'oled_hold': True, + 'oled_height': 64, + 'oled_width': 128, + }, + } + + def __init__(self, variant='generic'): + if variant in self.variants.keys(): + self.variant = variant + defs = self.defaults.copy() + defs.update(self.variants[variant]) + super().__init__(defs) + else: + raise ValueError("Board variant '%s' is not known" % variant) diff --git a/uPySensor.py b/uPySensor.py new file mode 100644 index 0000000..6b921de --- /dev/null +++ b/uPySensor.py @@ -0,0 +1,80 @@ +class uPySensor: + def __init__(self): + dummy='' + + def sleep_ms(self, delay): + from time import sleep_ms + sleep_ms(delay) + + +class LM75A(uPySensor): + LM75A_ADDRESS = 0x48 + + def __init__(self, i2c): + self.i2c = i2c + + def readbyte(self): + return self.i2c.readfrom(self.LM75A_ADDRESS, 1) + + def readbytes(self, num): + return self.i2c.readfrom(self.LM75A_ADDRESS, num) + + def read_tempC(self): + import math + data = self.readbytes(2) + return float("%d.%d" % (int(data[0]), math.floor(int(data[1])/23))) + +class SHT21(uPySensor): + SOFT_RESET = 0xfe + SHT21_ADDRESS = 0x40 + TRIG_TEMP_NO_HOLD = 0xf3 + TRIG_HUM_NO_HOLD = 0xf5 + + def writebyte(self, byte): + self.i2c.writeto(self.SHT21_ADDRESS, bytearray([byte])) + + def readbyte(self): + return self.i2c.readfrom(self.SHT21_ADDRESS, 1) + + def readbytes(self, num): + return self.i2c.readfrom(self.SHT21_ADDRESS, num) + + def __init__(self, i2c): + self.i2c = i2c + self.writebyte(self.SOFT_RESET) + self.sleep_ms(15) + + def read_tempC(self): + self.writebyte(self.TRIG_TEMP_NO_HOLD) + self.sleep_ms(250) + data = self.readbytes(2) + return self._buffer_to_tempC(data) + + def read_hum(self): + self.writebyte(self.TRIG_HUM_NO_HOLD) + self.sleep_ms(250) + data = self.readbytes(2) + return self._buffer_to_hum(data) + + def _buffer_to_tempC(self, buf): + unadj = (buf[0] << 8) + buf[1] + unadj *= 175.72 + unadj /= 1 << 16 + unadj -= 46.85 + return unadj + + def _buffer_to_hum(self, buf): + unadj = (buf[0] << 8) + buf[1] + unadj *= 125 + unadj /= 1 << 16 + unadj -= 6 + return unadj + + def close(self): + self.i2c.close() + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() diff --git a/webrepl_cfg.py b/webrepl_cfg.py new file mode 100644 index 0000000..5c0b724 --- /dev/null +++ b/webrepl_cfg.py @@ -0,0 +1 @@ +PASS='WebREPLPassword' \ No newline at end of file