From fc46b1139074f7848ec949caf10e69557bfef7fc Mon Sep 17 00:00:00 2001 From: ollo Date: Tue, 12 Apr 2016 18:07:25 +0200 Subject: [PATCH] First test of word displaying functionality --- .gitignore | 1 + Readme.md | 12 + example.lua | 10 + os/bl_readMAC.sh | 15 ++ os/esptool.py | 606 ++++++++++++++++++++++++++++++++++++++++++++ os/flash.sh | 33 +++ programESP.sh | 71 ++++++ test.lua | 4 + wlancfg.lua.example | 3 + wordclock.lua | 126 +++++++++ 10 files changed, 881 insertions(+) create mode 100644 .gitignore create mode 100644 Readme.md create mode 100644 example.lua create mode 100755 os/bl_readMAC.sh create mode 100755 os/esptool.py create mode 100755 os/flash.sh create mode 100644 programESP.sh create mode 100644 test.lua create mode 100644 wlancfg.lua.example create mode 100755 wordclock.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5899e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +wlancfg.lua diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..dec42d7 --- /dev/null +++ b/Readme.md @@ -0,0 +1,12 @@ +# Ueber Traffic Light +## Setup +Generate a wlancfg.lua for your wifi based on the given example wlancfg.lua.example + +Copy the required files to the microcontroller: +
+ sudo ./programESP.sh serial wlancfg.lua.lua wlancfg.lua
+ sudo ./programESP.sh serial init.lua init.lua
+
+ +## Internal Setup +* GPIO2 LEDs diff --git a/example.lua b/example.lua new file mode 100644 index 0000000..51e2ebf --- /dev/null +++ b/example.lua @@ -0,0 +1,10 @@ +-- Example usage of the word clock +dofile("wordclock.lua") + +-- Manually set something +leds=display_timestat(15,30) +for k,v in pairs(leds) do + if (v == 1) then + print(k) + end +end diff --git a/os/bl_readMAC.sh b/os/bl_readMAC.sh new file mode 100755 index 0000000..69cc4c3 --- /dev/null +++ b/os/bl_readMAC.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +if [ $# -ne 1 ]; then + echo "One parameter required: the device of the serial interface" + echo "$0 " + echo "e.g.:" + echo "$0 ttyUSB0" + exit 1 +fi + +DEVICE=$1 + + +echo "Read the MAC address from bootloader" +./esptool.py --port /dev/$DEVICE read_mac diff --git a/os/esptool.py b/os/esptool.py new file mode 100755 index 0000000..93ab071 --- /dev/null +++ b/os/esptool.py @@ -0,0 +1,606 @@ +#!/usr/bin/env python +# +# ESP8266 ROM Bootloader Utility +# https://github.com/themadinventor/esptool +# +# Copyright (C) 2014 Fredrik Ahlberg +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys +import struct +import serial +import math +import time +import argparse +import os +import subprocess + +class ESPROM: + + # These are the currently known commands supported by the ROM + ESP_FLASH_BEGIN = 0x02 + ESP_FLASH_DATA = 0x03 + ESP_FLASH_END = 0x04 + ESP_MEM_BEGIN = 0x05 + ESP_MEM_END = 0x06 + ESP_MEM_DATA = 0x07 + ESP_SYNC = 0x08 + ESP_WRITE_REG = 0x09 + ESP_READ_REG = 0x0a + + # Maximum block sized for RAM and Flash writes, respectively. + ESP_RAM_BLOCK = 0x1800 + ESP_FLASH_BLOCK = 0x400 + + # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. + ESP_ROM_BAUD = 115200 + + # First byte of the application image + ESP_IMAGE_MAGIC = 0xe9 + + # Initial state for the checksum routine + ESP_CHECKSUM_MAGIC = 0xef + + # OTP ROM addresses + ESP_OTP_MAC0 = 0x3ff00050 + ESP_OTP_MAC1 = 0x3ff00054 + + # Sflash stub: an assembly routine to read from spi flash and send to host + SFLASH_STUB = "\x80\x3c\x00\x40\x1c\x4b\x00\x40\x21\x11\x00\x40\x00\x80" \ + "\xfe\x3f\xc1\xfb\xff\xd1\xf8\xff\x2d\x0d\x31\xfd\xff\x41\xf7\xff\x4a" \ + "\xdd\x51\xf9\xff\xc0\x05\x00\x21\xf9\xff\x31\xf3\xff\x41\xf5\xff\xc0" \ + "\x04\x00\x0b\xcc\x56\xec\xfd\x06\xff\xff\x00\x00" + + def __init__(self, port = 0, baud = ESP_ROM_BAUD): + self._port = serial.Serial(port, baud) + + """ Read bytes from the serial port while performing SLIP unescaping """ + def read(self, length = 1): + b = '' + while len(b) < length: + c = self._port.read(1) + if c == '\xdb': + c = self._port.read(1) + if c == '\xdc': + b = b + '\xc0' + elif c == '\xdd': + b = b + '\xdb' + else: + raise Exception('Invalid SLIP escape') + else: + b = b + c + return b + + """ Write bytes to the serial port while performing SLIP escaping """ + def write(self, packet): + buf = '\xc0'+(packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc'))+'\xc0' + self._port.write(buf) + + """ Calculate checksum of a blob, as it is defined by the ROM """ + @staticmethod + def checksum(data, state = ESP_CHECKSUM_MAGIC): + for b in data: + state ^= ord(b) + return state + + """ Send a request and read the response """ + def command(self, op = None, data = None, chk = 0): + if op: + # Construct and send request + pkt = struct.pack('> 16) & 0xff) == 0: + oui = (0x18, 0xfe, 0x34) + elif ((mac1 >> 16) & 0xff) == 1: + oui = (0xac, 0xd0, 0x74) + else: + raise Exception("Unknown OUI") + return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) + + """ Read SPI flash manufacturer and device id """ + def flash_id(self): + self.flash_begin(0, 0) + self.write_reg(0x60000240, 0x0, 0xffffffff) + self.write_reg(0x60000200, 0x10000000, 0xffffffff) + flash_id = esp.read_reg(0x60000240) + self.flash_finish(False) + return flash_id + + """ Read SPI flash """ + def flash_read(self, offset, size, count = 1): + # Create a custom stub + stub = struct.pack(' 16: + raise Exception('Invalid firmware image') + + for i in xrange(segments): + (offset, size) = struct.unpack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536: + raise Exception('Suspicious segment %x,%d' % (offset, size)) + self.segments.append((offset, size, f.read(size))) + + # Skip the padding. The checksum is stored in the last byte so that the + # file is a multiple of 16 bytes. + align = 15-(f.tell() % 16) + f.seek(align, 1) + + self.checksum = ord(f.read(1)) + + def add_segment(self, addr, data): + # Data should be aligned on word boundary + l = len(data) + if l % 4: + data += b"\x00" * (4 - l % 4) + self.segments.append((addr, len(data), data)) + + def save(self, filename): + f = file(filename, 'wb') + f.write(struct.pack(' 0: + esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq) + data = data[esp.ESP_RAM_BLOCK:] + seq += 1 + print 'done!' + + print 'All segments done, executing at %08x' % image.entrypoint + esp.mem_finish(image.entrypoint) + + elif args.operation == 'read_mem': + print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address)) + + elif args.operation == 'write_mem': + esp.write_reg(args.address, args.value, args.mask, 0) + print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address) + + elif args.operation == 'dump_mem': + f = file(args.filename, 'wb') + for i in xrange(args.size/4): + d = esp.read_reg(args.address+(i*4)) + f.write(struct.pack(' 0: + print '\rWriting at 0x%08x... (%d %%)' % (address + seq*esp.ESP_FLASH_BLOCK, 100*(seq+1)/blocks), + sys.stdout.flush() + block = image[0:esp.ESP_FLASH_BLOCK] + # Fix sflash config data + if address == 0 and seq == 0 and block[0] == '\xe9': + block = block[0:2] + flash_info + block[4:] + # Pad the last block + block = block + '\xff' * (esp.ESP_FLASH_BLOCK-len(block)) + esp.flash_block(block, seq) + image = image[esp.ESP_FLASH_BLOCK:] + seq += 1 + print + print '\nLeaving...' + esp.flash_finish(False) + + elif args.operation == 'run': + esp.run() + + elif args.operation == 'image_info': + image = ESPFirmwareImage(args.filename) + print ('Entry point: %08x' % image.entrypoint) if image.entrypoint != 0 else 'Entry point not set' + print '%d segments' % len(image.segments) + print + checksum = ESPROM.ESP_CHECKSUM_MAGIC + for (idx, (offset, size, data)) in enumerate(image.segments): + print 'Segment %d: %5d bytes at %08x' % (idx+1, size, offset) + checksum = ESPROM.checksum(data, checksum) + print + print 'Checksum: %02x (%s)' % (image.checksum, 'valid' if image.checksum == checksum else 'invalid!') + + elif args.operation == 'make_image': + image = ESPFirmwareImage() + if len(args.segfile) == 0: + raise Exception('No segments specified') + if len(args.segfile) != len(args.segaddr): + raise Exception('Number of specified files does not match number of specified addresses') + for (seg, addr) in zip(args.segfile, args.segaddr): + data = file(seg, 'rb').read() + image.add_segment(addr, data) + image.entrypoint = args.entrypoint + image.save(args.output) + + elif args.operation == 'elf2image': + if args.output is None: + args.output = args.input + '-' + e = ELFFile(args.input) + image = ESPFirmwareImage() + image.entrypoint = e.get_symbol_addr("call_user_start") + for section, start in ((".text", "_text_start"), (".data", "_data_start"), (".rodata", "_rodata_start")): + data = e.load_section(section) + image.add_segment(e.get_symbol_addr(start), data) + + image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] + image.flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40}[args.flash_size] + image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + + image.save(args.output + "0x00000.bin") + data = e.load_section(".irom0.text") + off = e.get_symbol_addr("_irom0_text_start") - 0x40200000 + assert off >= 0 + f = open(args.output + "0x%05x.bin" % off, "wb") + f.write(data) + f.close() + + elif args.operation == 'read_mac': + mac = esp.read_mac() + print 'MAC: %s' % ':'.join(map(lambda x: '%02x'%x, mac)) + + elif args.operation == 'flash_id': + flash_id = esp.flash_id() + print 'Manufacturer: %02x' % (flash_id & 0xff) + print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff) + + elif args.operation == 'read_flash': + print 'Please wait...' + file(args.filename, 'wb').write(esp.flash_read(args.address, 1024, int(math.ceil(args.size / 1024.)))[:args.size]) + + elif args.operation == 'erase_flash': + esp.flash_erase() diff --git a/os/flash.sh b/os/flash.sh new file mode 100755 index 0000000..c8a7905 --- /dev/null +++ b/os/flash.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +if [ $# -ne 1 ]; then + echo "One parameter required: the device of the serial interface" + echo "$0 " + echo "e.g.:" + echo "$0 ttyUSB0" + exit 1 +fi + +DEVICE=$1 + +# check the serial connection + +if [ ! -c /dev/$DEVICE ]; then + echo "/dev/$DEVICE does not exist" + exit 1 +fi + +if [ ! -f esptool.py ]; then + echo "Cannot found the required tool:" + echo "esptool.py" + exit 1 +fi + +sudo ./esptool.py --port /dev/$DEVICE read_mac + +if [ $? -ne 0 ]; then + echo "Error reading the MAC -> set the device into the bootloader!" + exit 1 +fi + +sudo ./esptool.py --port /dev/$DEVICE write_flash 0x00000 nodemcu_integer_0.9.6-dev_20150704.bin diff --git a/programESP.sh b/programESP.sh new file mode 100644 index 0000000..72d9ec1 --- /dev/null +++ b/programESP.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +DEVICE=/dev/ttyUSB0 +SLEEPTIME=0.2 +TCPPORT=2323 + +function usage() { + echo "$0 usage:" + echo "$0 serial|ip fileOnHost.lua (fileOnESP.lua)" + echo "" + echo "The flash logic can be done via ethernet or serial" + echo "The first filename is mandatory!" + echo "" + echo "The second filename is the filename on the LUA devic." + echo "" + echo "Example:" + echo -e "$0 serial hello.txt\tSends the content of hello.txt to the ESP (directly as if you type it)" + echo -e "$0 serial init_example.lua init.lua\tUpdates the init.lua on the ESP" + echo " (The init.lua file is executed each time, the ESP starts)" + exit 1 +} + +case "$1" in +serial) + IP="" + if [ "$EUID" -ne 0 ] + then echo "Please run as root" + exit 1 + fi + ;; + +*) + if [[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]];then + IP=$1 + else + usage + fi +esac + + +if [ ! -f $2 ]; then + usage +fi + +if [ "$IP" != "" ]; then + echo "Using network: $IP " + echo "Checking connection ..." + ping -c 3 $IP >> /dev/null + if [ $? -ne 0 ]; then + echo "Cannot find ESP at $IP" + exit 1 + fi + DEVICE=/dev/tcp/$IP/$TCPPORT +fi + +# The flashing logic +if [ "$3" == "" ]; then + echo "Sending to shell..." + cat $2 | while read a; do echo -e "$a\r" >> $DEVICE; echo -e "$a"; sleep $SLEEPTIME; done +else + echo "Writing $3 on the ESP" + echo "=========================" + echo "" >> $DEVICE; sleep $SLEEPTIME + echo "file.open(\"$3\",\"w\")" >> $DEVICE; sleep $SLEEPTIME + cat $2 | while read a; do echo "file.writeline([[${a}]])" >> $DEVICE; echo -e "\r" >> $DEVICE; echo "$a"; sleep $SLEEPTIME; done + # Close the init file + echo "file.close()" >> $DEVICE; sleep $SLEEPTIME + echo "=========================" + echo "now login on ESP and call" + echo "node.restart()" +fi diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..836f5f3 --- /dev/null +++ b/test.lua @@ -0,0 +1,4 @@ +dofile("wlancfg.lua") +print("Initialize Wordclock") + + diff --git a/wlancfg.lua.example b/wlancfg.lua.example new file mode 100644 index 0000000..e13555d --- /dev/null +++ b/wlancfg.lua.example @@ -0,0 +1,3 @@ +-- Tell the chip to connect to thi access point +wifi.setmode(wifi.STATION) +wifi.sta.config("SSID","password") diff --git a/wordclock.lua b/wordclock.lua new file mode 100755 index 0000000..fc4ccb5 --- /dev/null +++ b/wordclock.lua @@ -0,0 +1,126 @@ +-- + +-- Return the leds to use +-- the granuality is 5 minutes +function display_timestat(hours, minutes, longmode) + if (longmode == nil) then + longmode=0 + end + + -- generate an empty return type + local ret = { itis=0, fiveMin=0, tenMin=0, beforeMin=0, threeHour=0, quater=0, after=0, beforeHour=0, half=0, s=0, + one=0, two=0, three=0, four=0, five=0, six=0, seven=0, eight=0, nine=0, ten=0, eleven=0, twelve=0, + clock=0, sr_nc=0, min1=0, min2=0, min3=0, min4=0 } + + -- return black screen if there is no real time given + if (hours == nil or minutes == nil) then + return ret + end + + -- transcode minutes + local minutesLeds = minutes%5 + minutes=minutes/5 + + + + -- "It is" only display each half hour and each hour + -- or if longmode is set + if ((longmode==1) + or (minutes==0) + or (minutes==6)) then + ret.itis=1 + end + + -- Handle minutes + if (minutes > 0) then + if (minutes==1) then + ret.fiveMin=1 + ret.after=1 + elseif (minutes==2) then + ret.tenMin=1 + ret.after=1 + elseif (minutes==3) then + ret.quater=1 + elseif (minutes==4) then + ret.tenMin=1 + ret.half=1 + ret.beforeMin=1 + elseif (minutes==5) then + ret.fiveMin=1 + ret.half=1 + ret.beforeMin=1 + elseif (minutes==6) then + ret.half=1 + elseif (minutes==7) then + ret.fiveMin=1 + ret.half=1 + ret.afterMin=1 + elseif (minutes==8) then + ret.tenMin=1 + ret.half=1 + ret.afterMin=1 + elseif (minutes==9) then + ret.quater=1 + ret.threeHour=1 + elseif (minutes==10) then + ret.tenMin=1 + ret.beforeHour=1 + elseif (minutes==11) then + ret.fiveMin=1 + ret.beforeHour=1 + end + + if (minutes >= 4) then + hours=hours+1 + end + else + ret.clock=1 + end + -- Display the minutes as as extra gimmic on min1 to min 4 to display the cut number + if (minutesLeds==1) then + ret.min1=1 + elseif (minutesLeds==2) then + ret.min2=1 + elseif (minutesLeds==3) then + ret.min3=1 + elseif (minutesLeds==4) then + ret.min4=1 + end + + -- handle hours + if (hours > 12) then + hours = hours - 12 + end + + if (hours==0) then + hours=12 + end + + if (hours == 1) then + ret.one=1 + elseif (hours == 2) then + ret.two=1 + elseif (hours == 3) then + ret.three=1 + elseif (hours == 4) then + ret.four=1 + elseif (hours == 5) then + ret.five=1 + elseif (hours == 6) then + ret.six=1 + elseif (hours == 7) then + ret.seven=1 + elseif (hours == 8) then + ret.eight=1 + elseif (hours == 9) then + ret.nine=1 + elseif (hours == 10) then + ret.ten=1 + elseif (hours == 11) then + ret.eleven=1 + elseif (hours == 12) then + ret.twelve=1 + end + + return ret +end