diff --git a/LoraOledESP32/DoESDash.ipynb b/LoraOledESP32/DoESDash.ipynb new file mode 100644 index 0000000..619ac65 --- /dev/null +++ b/LoraOledESP32/DoESDash.ipynb @@ -0,0 +1,172 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34mNo possible ports found\u001b[0m\u001b[34mConnecting to --port=/dev/ttyUSB0 --baud=115200 \u001b[0mcould not open port /dev/ttyUSB0: [Errno 2] No such file or directory: '/dev/ttyUSB0'\n", + "\u001b[34m\n", + "Are you sure your ESP-device is plugged in?\u001b[0m" + ] + } + ], + "source": [ + "%serialconnect" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sent 116 lines (2791 bytes) to main.py.\n" + ] + } + ], + "source": [ + "%sendtofile main.py\n", + "\n", + "import time\n", + "import utime\n", + "from machine import Pin, I2C\n", + "import machine\n", + "import ssd1306\n", + "import framebuf\n", + "import network\n", + "import ubinascii\n", + "\n", + "# Definitions\n", + "ssid = 'DoESLiverpool'\n", + "password = '****'\n", + "\n", + "# Why did we wake up?\n", + "print(machine.wake_description())\n", + "\n", + "# Setup OLED\n", + "rst = Pin(16, Pin.OUT)\n", + "rst.value(1)\n", + "scl = Pin(15, Pin.OUT, Pin.PULL_UP)\n", + "sda = Pin(4, Pin.OUT, Pin.PULL_UP)\n", + "\n", + "i2c = I2C(scl=scl, sda=sda, freq=450000)\n", + "\n", + "oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3c)\n", + " \n", + "# Gimme a welcome screen!\n", + "oled.fill(0)\n", + "oled.text('DoES Dash', 0, 0)\n", + "oled.show()\n", + "time.sleep(0.25)\n", + "\n", + "# Set up networking\n", + "sta_if = network.WLAN(network.STA_IF)\n", + " \n", + "oled.text('Wifi Connecting', 0, 10)\n", + "#oled.text(ssid, 0, 20)\n", + "oled.show()\n", + " \n", + "if not sta_if.isconnected():\n", + " print(\"Connecting to SSID\", ssid)\n", + " sta_if.active(True)\n", + " sta_if.connect(ssid, password)\n", + " while not sta_if.isconnected():\n", + " pass\n", + "print(\"Connected! IP = \", sta_if.ifconfig()[0])\n", + "\n", + "#oled.text(\"IP: \" + sta_if.ifconfig()[0], 0, 30)\n", + "#oled.show()\n", + "#time.sleep(0.25)\n", + "\n", + "rtc = machine.RTC()\n", + "\n", + "# Sync RTC over NTP\n", + "if not rtc.synced():\n", + " rtc.ntp_sync(server=\"hr.pool.ntp.org\", tz=\"CET-1CEST\")\n", + " while not rtc.synced():\n", + " pass\n", + " print(utime.gmtime())\n", + " print(utime.localtime())\n", + "\n", + "# Send MQtt message\n", + "def conncb(task):\n", + " print(\"[{}] Connected\".format(task))\n", + "\n", + "def disconncb(task):\n", + " print(\"[{}] Disconnected\".format(task))\n", + "\n", + "def subscb(task):\n", + " print(\"[{}] Subscribed\".format(task))\n", + "\n", + "def pubcb(pub):\n", + " print(\"[{}] Published: {}\".format(pub[0], pub[1]))\n", + "\n", + "def datacb(msg):\n", + " print(\"[{}] Data arrived from topic: {}, Message:\\n\".format(msg[0], msg[1]), msg[2])\n", + "\n", + "mqtt = network.mqtt(\"does\", \"mqtt://10.0.29.187\", user=\"user\", password=\"pass\", cleansession=True, connected_cb=conncb, disconnected_cb=disconncb, subscribed_cb=subscb, published_cb=pubcb, data_cb=datacb)\n", + "\n", + "# secure connection requires more memory and may not work\n", + "#mqtts = network.mqtt(\"eclipse\", \"mqtts://iot.eclipse.org\", cleansession=True, data_cb=datacb)\n", + "\n", + "# mqtt over Websocket can also be used\n", + "# mqttws = network.mqtt(\"eclipse\", \"ws://iot.eclipse.org:80\", cleansession=True, data_cb=datacb)\n", + "# mqttwss = network.mqtt(\"eclipse\", \"wss://iot.eclipse.org:80\", cleansession=True, data_cb=datacb)\n", + "\n", + "# Start the mqtt\n", + "print(\"Connecting to MQtt\")\n", + "oled.text(\"Publishing Msg\", 0, 20)\n", + "oled.show()\n", + "\n", + "mqtt.start()\n", + "\n", + "# Wait until status is: (1, 'Connected')\n", + "while mqtt.status()[0] < 2:\n", + " print(mqtt.status())\n", + " time.sleep(1)\n", + "\n", + "#mqtt.subscribe('test')\n", + "mqtt.publish('button/' + ubinascii.hexlify(machine.unique_id()).decode('ascii'), 'woke up')\n", + "\n", + "oled.text(\"Done. Sleeping\", 0, 30)\n", + "oled.show()\n", + "\n", + "i2c.deinit()\n", + "\n", + "# Debug\n", + "time.sleep(1)\n", + "\n", + "# Setup Sleep (wake on button press)\n", + "pin=machine.Pin(0)\n", + "pin.init(mode=pin.IN, pull=pin.PULL_UP)\n", + "rtc.wake_on_ext0(pin, 0)\n", + "\n", + "print(\"Sleeping...\")\n", + "machine.deepsleep()\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "MicroPython - USB", + "language": "micropython", + "name": "micropython" + }, + "language_info": { + "codemirror_mode": "python", + "file_extension": ".py", + "mimetype": "text/python", + "name": "micropython" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/LoraOledESP32/ssd1306oled.ipynb b/LoraOledESP32/ssd1306oled.ipynb index ab2346f..a8b187f 100644 --- a/LoraOledESP32/ssd1306oled.ipynb +++ b/LoraOledESP32/ssd1306oled.ipynb @@ -2,14 +2,14 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 83, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[34mConnecting to Serial /dev/ttyUSB2 baud=115200 \u001b[0m\n", + "\u001b[34mConnecting to --port=/dev/ttyUSB0 --baud=115200 \u001b[0m\n", "\u001b[34mReady.\n", "\u001b[0m" ] @@ -21,27 +21,191 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Sent 168 lines (5477 bytes) to ssd1306.py.\n" + "Sent 147 lines (4584 bytes) to ssd1306.py.\n" ] } ], "source": [ - "%sendtofile --source ssd1306.py\n" + "%sendtofile ssd1306.py\n", + "\n", + "# MicroPython SSD1306 OLED driver, I2C and SPI interfaces\n", + "\n", + "from micropython import const\n", + "import framebuf\n", + "\n", + "\n", + "# register definitions\n", + "SET_CONTRAST = const(0x81)\n", + "SET_ENTIRE_ON = const(0xa4)\n", + "SET_NORM_INV = const(0xa6)\n", + "SET_DISP = const(0xae)\n", + "SET_MEM_ADDR = const(0x20)\n", + "SET_COL_ADDR = const(0x21)\n", + "SET_PAGE_ADDR = const(0x22)\n", + "SET_DISP_START_LINE = const(0x40)\n", + "SET_SEG_REMAP = const(0xa0)\n", + "SET_MUX_RATIO = const(0xa8)\n", + "SET_COM_OUT_DIR = const(0xc0)\n", + "SET_DISP_OFFSET = const(0xd3)\n", + "SET_COM_PIN_CFG = const(0xda)\n", + "SET_DISP_CLK_DIV = const(0xd5)\n", + "SET_PRECHARGE = const(0xd9)\n", + "SET_VCOM_DESEL = const(0xdb)\n", + "SET_CHARGE_PUMP = const(0x8d)\n", + "\n", + "# Subclassing FrameBuffer provides support for graphics primitives\n", + "# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html\n", + "class SSD1306(framebuf.FrameBuffer):\n", + " def __init__(self, width, height, external_vcc):\n", + " self.width = width\n", + " self.height = height\n", + " self.external_vcc = external_vcc\n", + " self.pages = self.height // 8\n", + " self.buffer = bytearray(self.pages * self.width)\n", + " super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)\n", + " self.init_display()\n", + "\n", + " def init_display(self):\n", + " for cmd in (\n", + " SET_DISP | 0x00, # off\n", + " # address setting\n", + " SET_MEM_ADDR, 0x00, # horizontal\n", + " # resolution and layout\n", + " SET_DISP_START_LINE | 0x00,\n", + " SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0\n", + " SET_MUX_RATIO, self.height - 1,\n", + " SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0\n", + " SET_DISP_OFFSET, 0x00,\n", + " SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,\n", + " # timing and driving scheme\n", + " SET_DISP_CLK_DIV, 0x80,\n", + " SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,\n", + " SET_VCOM_DESEL, 0x30, # 0.83*Vcc\n", + " # display\n", + " SET_CONTRAST, 0xff, # maximum\n", + " SET_ENTIRE_ON, # output follows RAM contents\n", + " SET_NORM_INV, # not inverted\n", + " # charge pump\n", + " SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,\n", + " SET_DISP | 0x01): # on\n", + " self.write_cmd(cmd)\n", + " self.fill(0)\n", + " self.show()\n", + "\n", + " def poweroff(self):\n", + " self.write_cmd(SET_DISP | 0x00)\n", + "\n", + " def poweron(self):\n", + " self.write_cmd(SET_DISP | 0x01)\n", + "\n", + " def contrast(self, contrast):\n", + " self.write_cmd(SET_CONTRAST)\n", + " self.write_cmd(contrast)\n", + "\n", + " def invert(self, invert):\n", + " self.write_cmd(SET_NORM_INV | (invert & 1))\n", + "\n", + " def show(self):\n", + " x0 = 0\n", + " x1 = self.width - 1\n", + " if self.width == 64:\n", + " # displays with width of 64 pixels are shifted by 32\n", + " x0 += 32\n", + " x1 += 32\n", + " self.write_cmd(SET_COL_ADDR)\n", + " self.write_cmd(x0)\n", + " self.write_cmd(x1)\n", + " self.write_cmd(SET_PAGE_ADDR)\n", + " self.write_cmd(0)\n", + " self.write_cmd(self.pages - 1)\n", + " self.write_data(self.buffer)\n", + "\n", + "\n", + "class SSD1306_I2C(SSD1306):\n", + " def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):\n", + " self.i2c = i2c\n", + " self.addr = addr\n", + " self.temp = bytearray(2)\n", + " super().__init__(width, height, external_vcc)\n", + "\n", + " def write_cmd(self, cmd):\n", + " self.temp[0] = 0x80 # Co=1, D/C#=0\n", + " self.temp[1] = cmd\n", + " self.i2c.writeto(self.addr, self.temp)\n", + "\n", + " def write_data(self, buf):\n", + " self.temp[0] = self.addr << 1\n", + " self.temp[1] = 0x40 # Co=0, D/C#=1\n", + " self.i2c.start()\n", + " self.i2c.write(self.temp)\n", + " self.i2c.write(buf)\n", + " self.i2c.stop()\n", + "\n", + "\n", + "class SSD1306_SPI(SSD1306):\n", + " def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):\n", + " self.rate = 10 * 1024 * 1024\n", + " dc.init(dc.OUT, value=0)\n", + " res.init(res.OUT, value=0)\n", + " cs.init(cs.OUT, value=1)\n", + " self.spi = spi\n", + " self.dc = dc\n", + " self.res = res\n", + " self.cs = cs\n", + " import time\n", + " self.res(1)\n", + " time.sleep_ms(1)\n", + " self.res(0)\n", + " time.sleep_ms(10)\n", + " self.res(1)\n", + " super().__init__(width, height, external_vcc)\n", + "\n", + " def write_cmd(self, cmd):\n", + " self.spi.init(baudrate=self.rate, polarity=0, phase=0)\n", + " self.cs(1)\n", + " self.dc(0)\n", + " self.cs(0)\n", + " self.spi.write(bytearray([cmd]))\n", + " self.cs(1)\n", + "\n", + " def write_data(self, buf):\n", + " self.spi.init(baudrate=self.rate, polarity=0, phase=0)\n", + " self.cs(1)\n", + " self.dc(1)\n", + " self.cs(0)\n", + " self.spi.write(buf)\n", + " self.cs(1)" ] }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['boot.py', 'hmac.py', 'hotpie.py', 'ssd1306.py']\r\n" + ] + } + ], + "source": [ + "import os\n", + "print(os.listdir())" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, "outputs": [], "source": [ "#https://forum.micropython.org/viewtopic.php?f=18&p=23080\n", @@ -53,6 +217,7 @@ "scl = Pin(15, Pin.OUT, Pin.PULL_UP)\n", "sda = Pin(4, Pin.OUT, Pin.PULL_UP)\n", "i2c = I2C(scl=scl, sda=sda, freq=450000)\n", + "\n", "o = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3c)\n", "\n", "o.fill(0)\n", @@ -63,14 +228,16 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 80, "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "[60]\r\n" + "Traceback (most recent call last):\n", + " File \"\", line 1, in \n", + "NameError: name 'i2c' is not defined\n" ] } ], @@ -80,12 +247,22 @@ }, { "cell_type": "code", - "execution_count": 27, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Traceback (most recent call last):\n", + " File \"\", line 3, in \n", + "NameError: name 'o' is not defined\n" + ] + } + ], "source": [ + "import framebuf\n", + "\n", "o.fill(0)\n", "for i in range(128):\n", " o.framebuf.fill_rect(1, 3, i, 34, 1)\n", @@ -114,12 +291,60 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] + "execution_count": 84, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".......\u001b[34m\n", + "\n", + "*** Sending Ctrl-C\n", + "\n", + "\u001b[0m" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Traceback (most recent call last):\n", + " File \"\", line 28, in \n", + "KeyboardInterrupt: \n" + ] + } + ], + "source": [ + "# Show the DoES logo. Scrolls across the screen\n", + "\n", + "import ssd1306\n", + "import framebuf\n", + "from machine import I2C, Pin\n", + "rst = Pin(16, Pin.OUT)\n", + "rst.value(1)\n", + "scl = Pin(15, Pin.OUT, Pin.PULL_UP)\n", + "sda = Pin(4, Pin.OUT, Pin.PULL_UP)\n", + "i2c = I2C(scl=scl, sda=sda, freq=450000)\n", + "display = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3c)\n", + "#o = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3c)\n", + "\n", + "buffer = bytearray(\n", + " b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xe7\\xe0\\x30\\x0f\\xff\\xf0\\x07\\x00\\xe0\\x30\\x03\\xff\\xf0\\x06\\x18\\xe0\\x31\\xe3\\xe1\\xf3\\xfe\\x3c\\xe0\\x31\\xf1\\xc0\\x73\\xfe\\x3f\\xe0\\x31\\xf1\\x8c\\x73\\xff\\x07\\xe0\\x31\\xf1\\x9e\\x30\\x07\\x80\\xe0\\x31\\xf1\\x1e\\x30\\x0f\\xf0\\x60\\x31\\xf1\\x1e\\x33\\xff\\xfc\\x60\\x31\\xf3\\x9e\\x33\\xfe\\x7e\\x60\\x31\\xc3\\x8e\\x73\\xfe\\x3c\\x60\\x30\\x07\\xc0\\x70\\x07\\x00\\xe0\\x30\\x1f\\xe1\\xf0\\x07\\x81\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x3f\\xff\\xff\\xff\\xff\\xff\\xe0\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x40\\x00\\x00\\x00\\x00\\x60\\x30\\x00\\x00\\x00\\x00\\x00\\x60\\x30\\x48\\x8c\\x15\\x86\\x0c\\x60\\x30\\x49\\xb6\\xe6\\xcd\\x9b\\x60\\x30\\x49\\xb2\\xc4\\x58\\xb3\\x60\\x30\\x4d\\x3e\\xc4\\x58\\xb3\\x60\\x30\\x47\\x30\\xc4\\x49\\xb3\\x60\\x3f\\x46\\x1e\\xc7\\xcf\\x1e\\x60\\x00\\x00\\x00\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n", + ")\n", + "# FRAMEBUF_MHLSB = 3\n", + "fb = framebuf.FrameBuffer(buffer, 56, 64,3)\n", + "\n", + "while True:\n", + " for x in range(0,128-56):\n", + " display.fill(0)\n", + " display.blit(fb, x, 0)\n", + " display.show()\n", + " for x in range(0,128-56):\n", + " display.fill(0)\n", + " display.blit(fb, (128-56-x), 0)\n", + " display.show()\n" + ] } ], "metadata": { diff --git a/LoraOledESP32/ttgo_2fa_totp.ipynb b/LoraOledESP32/ttgo_2fa_totp.ipynb new file mode 100644 index 0000000..3d9a0d8 --- /dev/null +++ b/LoraOledESP32/ttgo_2fa_totp.ipynb @@ -0,0 +1,274 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34mConnecting to --port=/dev/ttyUSB0 --baud=115200 \u001b[0m\n", + "\u001b[34mReady.\n", + "\u001b[0m" + ] + } + ], + "source": [ + "%serialconnect" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sent 219 lines (6263 bytes) to main.py.\n" + ] + } + ], + "source": [ + "%sendtofile main.py\n", + "\n", + "# Based on Lady Ada's work here:\n", + "# https://learn.adafruit.com/circuitpython-totp-otp-2fa-authy-authenticator-friend/introduction\n", + "\n", + "import time\n", + "from machine import I2C, Pin\n", + "import ssd1306\n", + "import framebuf\n", + "import network\n", + "import ntptime\n", + "import ubinascii\n", + "import uhashlib\n", + "\n", + "# pylint: disable=broad-except\n", + " \n", + "# https://github.com/pyotp/pyotp example\n", + "totp = [(\"GMail\", 'abcdefghijklmnopqrstuvwxyz234567')]\n", + "\n", + "ssid = 'DoESLiverpool'\n", + "password = 'decafbad00'\n", + " \n", + "TEST = False # if you want to print out the tests the hashers\n", + "ALWAYS_ON = False # Set to true if you never want to go to sleep!\n", + "ON_SECONDS = 60 # how long to stay on if not in always_on mode\n", + " \n", + "rst = Pin(16, Pin.OUT)\n", + "rst.value(1)\n", + "scl = Pin(15, Pin.OUT, Pin.PULL_UP)\n", + "sda = Pin(4, Pin.OUT, Pin.PULL_UP)\n", + "i2c = I2C(scl=scl, sda=sda, freq=450000)\n", + "\n", + "oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3c)\n", + " \n", + "# Gimme a welcome screen!\n", + "oled.fill(0)\n", + "oled.text('CircuitPython', 0, 0)\n", + "oled.text('PyTOTP Pal!', 0, 10)\n", + "oled.text(' <3 adafruit <3 ', 0, 20)\n", + "oled.show()\n", + "time.sleep(0.25)\n", + " \n", + "EPOCH_DELTA = 946684800 # seconds between year 2000 and year 1970\n", + "SECS_DAY = 86400\n", + " \n", + "SHA1 = uhashlib.sha1\n", + " \n", + "if TEST:\n", + " print(\"===========================================\")\n", + " print(\"SHA1 test: \", ubinascii.hexlify(SHA1(b'hello world').digest()))\n", + " # should be 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed\n", + " \n", + " \n", + "# HMAC implementation, as hashlib/hmac wouldn't fit\n", + "# From https://en.wikipedia.org/wiki/Hash-based_message_authentication_code\n", + "def HMAC(k, m):\n", + " SHA1_BLOCK_SIZE = 64\n", + " KEY_BLOCK = k + (b'\\0' * (SHA1_BLOCK_SIZE - len(k)))\n", + " KEY_INNER = bytes((x ^ 0x36) for x in KEY_BLOCK)\n", + " KEY_OUTER = bytes((x ^ 0x5C) for x in KEY_BLOCK)\n", + " inner_message = KEY_INNER + m\n", + " outer_message = KEY_OUTER + SHA1(inner_message).digest()\n", + " return SHA1(outer_message)\n", + " \n", + " \n", + "if TEST:\n", + " KEY = b'abcd'\n", + " MESSAGE = b'efgh'\n", + " print(\"===========================================\")\n", + " print(\"HMAC test: \", ubinascii.hexlify(HMAC(KEY, MESSAGE).digest()))\n", + " # should be e5dbcf9263188f9fce90df572afeb39b66b27198\n", + " \n", + " \n", + "# Base32 decoder, since base64 lib wouldnt fit\n", + " \n", + " \n", + "def base32_decode(encoded):\n", + " missing_padding = len(encoded) % 8\n", + " if missing_padding != 0:\n", + " encoded += '=' * (8 - missing_padding)\n", + " encoded = encoded.upper()\n", + " chunks = [encoded[i:i + 8] for i in range(0, len(encoded), 8)]\n", + " \n", + " out = []\n", + " for chunk in chunks:\n", + " bits = 0\n", + " bitbuff = 0\n", + " for c in chunk:\n", + " if 'A' <= c <= 'Z':\n", + " n = ord(c) - ord('A')\n", + " elif '2' <= c <= '7':\n", + " n = ord(c) - ord('2') + 26\n", + " elif n == '=':\n", + " continue\n", + " else:\n", + " raise ValueError(\"Not base32\")\n", + " # 5 bits per 8 chars of base32\n", + " bits += 5\n", + " # shift down and add the current value\n", + " bitbuff <<= 5\n", + " bitbuff |= n\n", + " # great! we have enough to extract a byte\n", + " if bits >= 8:\n", + " bits -= 8\n", + " byte = bitbuff >> bits # grab top 8 bits\n", + " bitbuff &= ~(0xFF << bits) # and clear them\n", + " out.append(byte) # store what we got\n", + " return out\n", + " \n", + " \n", + "if TEST:\n", + " print(\"===========================================\")\n", + " print(\"Base32 test: \", bytes(base32_decode(\"IFSGCZTSOVUXIIJB\")))\n", + " # should be \"Adafruit!!\"\n", + " \n", + " \n", + "# Turns an integer into a padded-with-0x0 bytestr\n", + " \n", + " \n", + "def int_to_bytestring(i, padding=8):\n", + " result = []\n", + " while i != 0:\n", + " result.insert(0, i & 0xFF)\n", + " i >>= 8\n", + " result = [0] * (padding - len(result)) + result\n", + " return bytes(result)\n", + " \n", + " \n", + "# HMAC -> OTP generator, pretty much same as\n", + "# https://github.com/pyotp/pyotp/blob/master/src/pyotp/otp.py\n", + " \n", + " \n", + "def generate_otp(int_input, secret_key, digits=6):\n", + " if int_input < 0:\n", + " raise ValueError('input must be positive integer')\n", + " hmac_hash = bytearray(\n", + " HMAC(bytes(base32_decode(secret_key)),\n", + " int_to_bytestring(int_input)).digest()\n", + " )\n", + " offset = hmac_hash[-1] & 0xf\n", + " code = ((hmac_hash[offset] & 0x7f) << 24 |\n", + " (hmac_hash[offset + 1] & 0xff) << 16 |\n", + " (hmac_hash[offset + 2] & 0xff) << 8 |\n", + " (hmac_hash[offset + 3] & 0xff))\n", + " str_code = str(code % 10 ** digits)\n", + " while len(str_code) < digits:\n", + " str_code = '0' + str_code\n", + " \n", + " return str_code\n", + " \n", + " \n", + "print(\"===========================================\")\n", + " \n", + "# Set up networking\n", + "sta_if = network.WLAN(network.STA_IF)\n", + " \n", + "oled.fill(0)\n", + "oled.text('Connecting to', 0, 0)\n", + "oled.text(ssid, 0, 10)\n", + "oled.show()\n", + " \n", + "if not sta_if.isconnected():\n", + " print(\"Connecting to SSID\", ssid)\n", + " sta_if.active(True)\n", + " sta_if.connect(ssid, password)\n", + " while not sta_if.isconnected():\n", + " pass\n", + "print(\"Connected! IP = \", sta_if.ifconfig()[0])\n", + " \n", + "# Done! Let them know we made it\n", + "oled.text(\"IP: \" + sta_if.ifconfig()[0], 0, 20)\n", + "oled.show()\n", + "time.sleep(0.25)\n", + " \n", + "# Get the latest time from NTP\n", + "t = None\n", + "while not t:\n", + " try:\n", + " t = ntptime.time()\n", + " except Exception:\n", + " pass\n", + " time.sleep(0.1)\n", + " \n", + "# NTP time is seconds-since-2000\n", + "print(\"NTP time: \", t)\n", + " \n", + "# But we need Unix time, which is seconds-since-1970\n", + "t += EPOCH_DELTA\n", + "ts = time.time()\n", + "print(\"Unix time: \", t)\n", + " \n", + "\n", + "countdown = ON_SECONDS # how long to stay on if not in always_on mode\n", + "while ALWAYS_ON or (countdown > 0):\n", + " # Calculate current time based on NTP + monotonic\n", + " unix_time = t - ts + time.time()\n", + " print(\"Unix time: \", unix_time)\n", + " \n", + " # Clear the screen\n", + " oled.fill(0)\n", + " y = 0\n", + " oled.text(\"Two Factor Auth\", 0, y)\n", + " y += 10\n", + " y += 10\n", + " # We can do up to 3 per line on the Feather OLED\n", + " for name, secret in totp:\n", + " otp = generate_otp(unix_time // 30, secret)\n", + " print(name + \" OTP output: \", otp) # serial debugging output\n", + " oled.text(name + \": \" + str(otp), 0, y) # display name & OTP on OLED\n", + " y += 10 # Go to next line on OLED\n", + " # Display a little bar that 'counts down' how many seconds you have left\n", + " oled.line(0, 63, 128 - (unix_time % 30) * 4, 63, True)\n", + " oled.show()\n", + " # We'll update every 1/4 second, we can hash very fast so its no biggie!\n", + " countdown -= 0.25\n", + " time.sleep(0.25)\n", + " \n", + "# All these hashes will be lost in time(), like tears in rain. Time to die\n", + "oled.fill(0)\n", + "oled.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "MicroPython - USB", + "language": "micropython", + "name": "micropython" + }, + "language_info": { + "codemirror_mode": "python", + "file_extension": ".py", + "mimetype": "text/python", + "name": "micropython" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}