diff --git a/displayword.lua b/displayword.lua index 3d12716..c7fe7fe 100644 --- a/displayword.lua +++ b/displayword.lua @@ -1,95 +1,139 @@ -- Module filling a buffer, sent to the LEDs -function updateColor(data) - if (data.usedCharacters <= data.charsPerMinute) then - if (data.words.min1 == 1 or data.words.min2 == 1 or data.words.min3 == 1 or data.words.min4 == 1) then +function updateColor(data, inverseRow, characters2draw) + if (inverseRow == nil) then + inverseRow=false + end + -- special case, and there are exactly 4 words to display (so each word for each minute) + if (not inverseRow) then -- nomral row + if (data.drawnCharacters < data.charsPerMinute) then + return data.colorFg + elseif (data.drawnCharacters < data.charsPerMinute*2) then return data.colorMin1 - else - return data.colorFg - end - elseif (data.usedCharacters <= data.charsPerMinute*2) then - if (data.words.min2 == 1 or data.words.min3 == 1 or data.words.min4 == 1) then + elseif (data.drawnCharacters < data.charsPerMinute*3) then return data.colorMin2 - else - return data.colorFg - end - elseif (data.usedCharacters <= data.charsPerMinute*3) then - if (data.words.min3 == 1 or data.words.min4 == 1) then + elseif (data.drawnCharacters > data.charsPerMinute*4) then return data.colorMin3 - else - return data.colorFg - end - elseif (data.usedCharacters > data.charsPerMinute*3) then - if (data.words.min4 == 1) then + elseif (data.drawnCharacters > data.charsPerMinute*5) then return data.colorMin4 else return data.colorFg end - else - return data.colorFg + else -- inverse row + --FIXME magic missing to start on the left side + if (data.drawnCharacters < data.charsPerMinute) then + return data.colorMin1 + elseif (data.drawnCharacters < data.charsPerMinute*2) then + return data.colorMin2 + elseif (data.drawnCharacters < data.charsPerMinute*3) then + return data.colorMin3 + elseif (data.drawnCharacters > data.charsPerMinute*4) then + return data.colorMin4 + else + return data.colorFg + end end end -function drawLEDs(data, numberNewChars) +function drawLEDs(data, numberNewChars, inverseRow) + if (inverseRow == nil) then + inverseRow=false + end + if (numberNewChars == nil) then + numberNewChars=0 + end + print(tostring(numberNewChars) .. " charactes " .. tostring(data.charsPerMinute) .. " per minute; " .. tonumber(data.drawnCharacters) .. " used characters") local tmpBuf=nil for i=1,numberNewChars do if (tmpBuf == nil) then - tmpBuf = updateColor(data) + tmpBuf = updateColor(data, inverseRow, numberNewChars) else - tmpBuf=tmpBuf .. updateColor(data) + tmpBuf=tmpBuf .. updateColor(data, inverseRow, numberNewChars) end - data.usedCharacters=data.usedCharacters+1 - + data.drawnCharacters=data.drawnCharacters+1 end + data.drawnWords=data.drawnWords+1 return tmpBuf end +-- Utility function for round +function round(num) + under = math.floor(num) + upper = math.floor(num) + 1 + underV = -(under - num) + upperV = upper - num + if (upperV > underV) then + return under + else + return upper + end +end + -- Module displaying of the words -function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4, characters) +function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4, characters) -- Set the local variables needed for the colored progress bar data={} - data.charsPerMinute=math.floor(characters/3) -- devide by three (Minute 1 to Minute 3, Minute 4 takes the last chars) + + local minutes=1 + if (words.min1 == 1) then + minutes = minutes + 1 + elseif (words.min2 == 1) then + minutes = minutes + 2 + elseif (words.min3 == 1) then + minutes = minutes + 3 + elseif (words.min4 == 1) then + minutes = minutes + 4 + end + data.charsPerMinute = round( (characters / minutes) ) + + -- devide by five (Minute 0, Minute 1 to Minute 4 takes the last chars) + print("Minutes : " .. tostring(minutes) .. " Char minutes: " .. tostring(data.charsPerMinute) ) data.words=words - data.colorFg=colorFg + data.colorFg=colorForground data.colorMin1=colorMin1 data.colorMin2=colorMin2 data.colorMin3=colorMin3 data.colorMin4=colorMin4 - data.usedCharacters=0 + data.drawnCharacters=0 + data.drawnWords=0 + data.amountWords=display_countwords_de(words) + local charsPerLine=11 + -- Space / background has no color by default local space=string.char(0,0,0) - -- update the background color, if set - if (colorBg ~= nil) then - space = colorBg - end + -- set FG to fix value: + colorFg = string.char(255,255,255) -- Set the foreground color as the default color local buf=colorFg -- line 1---------------------------------------------- - if (words.itis == 1) then + if (words.it==1) then buf=drawLEDs(data,2) -- ES - print(tostring(buf)) - -- K fill character - buf=buf .. space:rep(1) - buf=buf .. drawLEDs(data,3) -- IST - -- L fill character - buf=buf .. space:rep(1) - else - buf=space:rep(7) + else + buf=space:rep(2) end - if (words.fiveMin== 1) then +-- K fill character +buf=buf .. space:rep(1) + if (words.is == 1) then + buf=buf .. drawLEDs(data,3) -- IST + else + buf=buf .. space:rep(3) + end + -- L fill character +buf=buf .. space:rep(1) +if (words.fiveMin== 1) then buf= buf .. drawLEDs(data,4) -- FUENF else buf= buf .. space:rep(4) end -- line 2-- even row (so inverted) -------------------- if (words.twenty == 1) then - buf= buf .. drawLEDs(data,7) -- ZWANZIG + buf= buf .. drawLEDs(data,7,true) -- ZWANZIG else buf= buf .. space:rep(7) end if (words.tenMin == 1) then - buf= buf .. drawLEDs(data,4) -- ZEHN + buf= buf .. drawLEDs(data,4,true) -- ZEHN else buf= buf .. space:rep(4) end @@ -105,12 +149,12 @@ function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4 --line 4-------- even row (so inverted) ------------- if (words.before == 1) then buf=buf .. space:rep(2) - buf= buf .. drawLEDs(data,3) -- VOR + buf= buf .. drawLEDs(data,3,true) -- VOR else buf= buf .. space:rep(5) end if (words.after == 1) then - buf= buf .. drawLEDs(data,4) -- NACH + buf= buf .. drawLEDs(data,4,true) -- NACH buf= buf .. space:rep(2) -- TG else buf= buf .. space:rep(6) @@ -130,19 +174,19 @@ function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4 end ------------even row (so inverted) --------------------- if (words.seven == 1) then - buf= buf .. drawLEDs(data,6) -- SIEBEN + buf= buf .. drawLEDs(data,6,true) -- SIEBEN buf= buf .. space:rep(5) elseif (words.oneLong == 1) then buf= buf .. space:rep(5) - buf= buf .. drawLEDs(data,4) -- EINS + buf= buf .. drawLEDs(data,4,true) -- EINS buf= buf .. space:rep(2) elseif (words.one == 1) then buf= buf .. space:rep(6) - buf= buf .. drawLEDs(data,3) -- EIN + buf= buf .. drawLEDs(data,3,true) -- EIN buf= buf .. space:rep(2) elseif (words.two == 1) then buf= buf .. space:rep(7) - buf= buf .. drawLEDs(data,4) -- ZWEI + buf= buf .. drawLEDs(data,4,true) -- ZWEI else buf= buf .. space:rep(11) end @@ -159,15 +203,15 @@ function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4 end ------------even row (so inverted) --------------------- if (words.four == 1) then - buf= buf .. drawLEDs(data,4) -- VIER + buf= buf .. drawLEDs(data,4,true) -- VIER buf= buf .. space:rep(7) elseif (words.nine == 1) then buf= buf .. space:rep(4) - buf= buf .. drawLEDs(data,4) -- NEUN + buf= buf .. drawLEDs(data,4,true) -- NEUN buf= buf .. space:rep(3) elseif (words.eleven == 1) then buf= buf .. space:rep(8) - buf= buf .. drawLEDs(data,3) -- ELEVEN + buf= buf .. drawLEDs(data,3,true) -- ELEVEN else buf= buf .. space:rep(11) end @@ -185,13 +229,13 @@ function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4 end ------------even row (so inverted) --------------------- if (words.clock == 1) then - buf= buf .. drawLEDs(data,3) -- UHR + buf= buf .. drawLEDs(data,3,true) -- UHR else buf= buf .. space:rep(3) end if (words.six == 1) then buf= buf .. space:rep(2) - buf= buf .. drawLEDs(data,5) -- SECHS + buf= buf .. drawLEDs(data,5,true) -- SECHS buf= buf .. space:rep(1) else buf= buf .. space:rep(8) @@ -218,5 +262,7 @@ function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4 buf= buf .. space:rep(1) end collectgarbage() + return buf end + diff --git a/index.html b/index.html index a6caac6..a938ea3 100644 --- a/index.html +++ b/index.html @@ -19,4 +19,4 @@
$ADDITIONAL_LINE - \ No newline at end of file + diff --git a/init.lua b/init.lua index 74d28a2..54c20bf 100644 --- a/init.lua +++ b/init.lua @@ -7,7 +7,8 @@ counter1=0 ws2812.write(string.char(0,0,0):rep(114)) tmr.alarm(2, 85, 1, function() counter1=counter1+1 - ws2812.write(string.char(128,0,0):rep(counter1) .. string.char(0,0,0):rep(MAXLEDS - (counter1*2)) .. string.char(0,0,64):rep(counter1)) + spaceLeds = math.max(MAXLEDS - (counter1*2), 0) + ws2812.write(string.char(128,0,0):rep(counter1) .. string.char(0,0,0):rep(spaceLeds) .. string.char(0,0,64):rep(counter1)) end) local blacklistfile="init.lua config.lua config.lua.new" @@ -54,3 +55,4 @@ tmr.alarm(1, 5000, 0, function() print("No Main file found") end end) +print("Init file end reached") diff --git a/main.lua b/main.lua index 1eb98a9..060e438 100644 --- a/main.lua +++ b/main.lua @@ -50,14 +50,31 @@ function displayTime() time = getTime(sec, timezoneoffset) words = display_timestat(time.hour, time.minute) - local charactersOfTime = display_countwords_de(words) + local charactersOfTime = display_countcharacters_de(words) + local wordsOfTime = display_countwords_de(words) ledBuf = generateLEDs(words, color, color1, color2, color3, color4, charactersOfTime) - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " in " .. charactersOfTime .. " chars") + print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " in " .. charactersOfTime .. " chars " .. wordsOfTime .. " words") - -- Write the buffer to the LEDs - ws2812.write(ledBuf) + --if lines 4 to 6 are inverted due to hardware-fuckup, unfuck it here + if ((inv46 ~= nil) and (inv46 == "on")) then + tempstring = ledBuf:sub(1,99) -- first 33 leds + rowend = {44,55,66} + for _, startled in ipairs(rowend) do + for i = 0,10 do + tempstring = tempstring .. ledBuf:sub((startled-i)*3-2,(startled-i)*3) + end + end + tempstring = tempstring .. ledBuf:sub((67*3)-2,ledBuf:len()) + ws2812.write(tempstring) + tempstring=nil + else + ws2812.write(ledBuf) + ledBuf=nil + end + + -- Used for debugging if (clockdebug ~= nil) then @@ -68,7 +85,7 @@ function displayTime() end end -- cleanup - ledBuf=nil + words=nil time=nil collectgarbage() @@ -115,7 +132,6 @@ function normalOperation() mydofile("webserver") startWebServer() end) - displayTime() -- Start the time Thread tmr.alarm(1, 20000, 1 ,function() diff --git a/os/esptool.py b/os/esptool.py index 93ab071..38ffb72 100755 --- a/os/esptool.py +++ b/os/esptool.py @@ -1,15 +1,16 @@ #!/usr/bin/env python +# NB: Before sending a PR to change the above line to '#!/usr/bin/env python2', please read https://github.com/themadinventor/esptool/issues/21 # # ESP8266 ROM Bootloader Utility # https://github.com/themadinventor/esptool # -# Copyright (C) 2014 Fredrik Ahlberg +# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, other contributors as noted. # # 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 +# +# 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. # @@ -17,17 +18,23 @@ # 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 hashlib +import inspect +import json import os +import serial +import struct import subprocess +import sys +import tempfile +import time -class ESPROM: +__version__ = "1.2-dev" + + +class ESPROM(object): # These are the currently known commands supported by the ROM ESP_FLASH_BEGIN = 0x02 ESP_FLASH_DATA = 0x03 @@ -55,72 +62,64 @@ class ESPROM: # OTP ROM addresses ESP_OTP_MAC0 = 0x3ff00050 ESP_OTP_MAC1 = 0x3ff00054 + ESP_OTP_MAC3 = 0x3ff0005c - # 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" + # Flash sector size, minimum unit of erase. + ESP_FLASH_SECTOR = 0x1000 - def __init__(self, port = 0, baud = ESP_ROM_BAUD): - self._port = serial.Serial(port, baud) + def __init__(self, port=0, baud=ESP_ROM_BAUD): + self._port = serial.Serial(port) + self._slip_reader = slip_reader(port) + # setting baud rate in a separate step is a workaround for + # CH341 driver on some Linux versions (this opens at 9600 then + # sets), shouldn't matter for other platforms/drivers. See + # https://github.com/themadinventor/esptool/issues/44#issuecomment-107094446 + self._port.baudrate = 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 + """ Read a SLIP packet from the serial port """ + def read(self): + return self._slip_reader.next() """ 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' + 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): + 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 + def command(self, op=None, data=None, chk=0): + if op is not None: pkt = struct.pack('> 16) & 0xff) == 0: + mac0 = self.read_reg(self.ESP_OTP_MAC0) + mac1 = self.read_reg(self.ESP_OTP_MAC1) + mac3 = self.read_reg(self.ESP_OTP_MAC3) + if (mac3 != 0): + oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff) + elif ((mac1 >> 16) & 0xff) == 0: oui = (0x18, 0xfe, 0x34) elif ((mac1 >> 16) & 0xff) == 1: oui = (0xac, 0xd0, 0x74) else: - raise Exception("Unknown OUI") + raise FatalError("Unknown OUI") return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) + """ Read Chip ID from OTP ROM - see http://esp8266-re.foogod.com/wiki/System_get_chip_id_%28IoT_RTOS_SDK_0.9.9%29 """ + def chip_id(self): + id0 = self.read_reg(self.ESP_OTP_MAC0) + id1 = self.read_reg(self.ESP_OTP_MAC1) + return (id0 >> 24) | ((id1 & 0xffffff) << 8) + """ 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) + flash_id = self.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): + def add_segment(self, addr, data, pad_to=4): + """ Add a segment to the image, with specified address & data + (padded to a boundary of pad_to size) """ # 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)) + if l % pad_to: + data += b"\x00" * (pad_to - l % pad_to) + if l > 0: + self.segments.append((addr, len(data), data)) - def save(self, filename): - f = file(filename, 'wb') - f.write(struct.pack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536: + raise FatalError('Suspicious segment 0x%x, length %d' % (offset, size)) + segment_data = f.read(size) + if len(segment_data) < size: + raise FatalError('End of file reading segment 0x%x, length %d (actual length %d)' % (offset, size, len(segment_data))) + segment = (offset, size, segment_data) + self.segments.append(segment) + return segment - checksum = ESPROM.ESP_CHECKSUM_MAGIC - for (offset, size, data) in self.segments: - f.write(struct.pack(' 16: + raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments)) + + for i in xrange(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + def save(self, filename): + with open(filename, 'wb') as f: + self.write_v1_header(f, self.segments) + checksum = ESPROM.ESP_CHECKSUM_MAGIC + for segment in self.segments: + checksum = self.save_segment(f, segment, checksum) + self.append_checksum(f, checksum) + + +class OTAFirmwareImage(BaseFirmwareImage): + """ 'Version 2' firmware image, segments loaded by software bootloader stub + (ie Espressif bootloader or rboot) + """ + def __init__(self, load_file=None): + super(OTAFirmwareImage, self).__init__() + self.version = 2 + if load_file is not None: + (magic, segments, first_flash_mode, first_flash_size_freq, first_entrypoint) = struct.unpack(' 16: + raise FatalError('Invalid V2 second header magic=%d segments=%d' % (magic, segments)) + + # load all the usual segments + for _ in xrange(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + def save(self, filename): + with open(filename, 'wb') as f: + # Save first header for irom0 segment + f.write(struct.pack(' 0: + esp._port.baudrate = baud_rate + # Read the greeting. + p = esp.read() + if p != 'OHAI': + raise FatalError('Failed to connect to the flasher (got %s)' % hexify(p)) + + def flash_write(self, addr, data, show_progress=False): + assert addr % self._esp.ESP_FLASH_SECTOR == 0, 'Address must be sector-aligned' + assert len(data) % self._esp.ESP_FLASH_SECTOR == 0, 'Length must be sector-aligned' + sys.stdout.write('Writing %d @ 0x%x... ' % (len(data), addr)) + sys.stdout.flush() + self._esp.write(struct.pack(' length: + raise FatalError('Read more than expected') + p = self._esp.read() + if len(p) != 16: + raise FatalError('Expected digest, got: %s' % hexify(p)) + expected_digest = hexify(p).upper() + digest = hashlib.md5(data).hexdigest().upper() + print + if digest != expected_digest: + raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) + p = self._esp.read() + if len(p) != 1: + raise FatalError('Expected status, got: %s' % hexify(p)) + status_code = struct.unpack(', ) or a single +# argument. + +def load_ram(esp, args): + image = LoadFirmwareImage(args.filename) + + print 'RAM boot...' + for (offset, size, data) in image.segments: + print 'Downloading %d bytes at %08x...' % (size, offset), + sys.stdout.flush() + esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, offset) + + seq = 0 + while len(data) > 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) + + +def read_mem(esp, args): + print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address)) + + +def write_mem(esp, args): + esp.write_reg(args.address, args.value, args.mask, 0) + print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address) + + +def dump_mem(esp, args): + f = file(args.filename, 'wb') + for i in xrange(args.size / 4): + d = esp.read_reg(args.address + (i * 4)) + f.write(struct.pack('> 8) & 0xff, (flash_id >> 16) & 0xff) + + +def read_flash(esp, args): + flasher = CesantaFlasher(esp, args.baud) + t = time.time() + data = flasher.flash_read(args.address, args.size, not args.no_progress) + t = time.time() - t + print ('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' + % (len(data), args.address, t, len(data) / t * 8 / 1000)) + file(args.filename, 'wb').write(data) + + +def _verify_flash(flasher, args, flash_params=None): + differences = False + for address, argfile in args.addr_filename: + image = argfile.read() + argfile.seek(0) # rewind in case we need it again + if address == 0 and image[0] == '\xe9' and flash_params is not None: + image = image[0:2] + flash_params + image[4:] + image_size = len(image) + print 'Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name) + # Try digest first, only read if there are differences. + digest, _ = flasher.flash_digest(address, image_size) + digest = hexify(digest).upper() + expected_digest = hashlib.md5(image).hexdigest().upper() + if digest == expected_digest: + print '-- verify OK (digest matched)' + continue + else: + differences = True + if getattr(args, 'diff', 'no') != 'yes': + print '-- verify FAILED (digest mismatch)' + continue + + flash = flasher.flash_read(address, image_size) + assert flash != image + diff = [i for i in xrange(image_size) if flash[i] != image[i]] + print '-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0]) + for d in diff: + print ' %08x %02x %02x' % (address + d, ord(flash[d]), ord(image[d])) + if differences: + raise FatalError("Verify failed.") + + +def verify_flash(esp, args, flash_params=None): + flasher = CesantaFlasher(esp) + _verify_flash(flasher, args, flash_params) + + +def version(args): + print __version__ + +# +# End of operations functions +# + + +def main(): + parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool') parser.add_argument( - '--port', '-p', - help = 'Serial port device', - default = '/dev/ttyUSB0') + '--port', '-p', + help='Serial port device', + default=os.environ.get('ESPTOOL_PORT', '/dev/ttyUSB0')) parser.add_argument( - '--baud', '-b', - help = 'Serial port baud rate', - type = arg_auto_int, - default = ESPROM.ESP_ROM_BAUD) + '--baud', '-b', + help='Serial port baud rate used when flashing/reading', + type=arg_auto_int, + default=os.environ.get('ESPTOOL_BAUD', ESPROM.ESP_ROM_BAUD)) subparsers = parser.add_subparsers( - dest = 'operation', - help = 'Run esptool {command} -h for additional help') + dest='operation', + help='Run esptool {command} -h for additional help') parser_load_ram = subparsers.add_parser( - 'load_ram', - help = 'Download an image to RAM and execute') - parser_load_ram.add_argument('filename', help = 'Firmware image') + 'load_ram', + help='Download an image to RAM and execute') + parser_load_ram.add_argument('filename', help='Firmware image') parser_dump_mem = subparsers.add_parser( - 'dump_mem', - help = 'Dump arbitrary memory to disk') - parser_dump_mem.add_argument('address', help = 'Base address', type = arg_auto_int) - parser_dump_mem.add_argument('size', help = 'Size of region to dump', type = arg_auto_int) - parser_dump_mem.add_argument('filename', help = 'Name of binary dump') + 'dump_mem', + help='Dump arbitrary memory to disk') + parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int) + parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_dump_mem.add_argument('filename', help='Name of binary dump') parser_read_mem = subparsers.add_parser( - 'read_mem', - help = 'Read arbitrary memory location') - parser_read_mem.add_argument('address', help = 'Address to read', type = arg_auto_int) + 'read_mem', + help='Read arbitrary memory location') + parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int) parser_write_mem = subparsers.add_parser( - 'write_mem', - help = 'Read-modify-write to arbitrary memory location') - parser_write_mem.add_argument('address', help = 'Address to write', type = arg_auto_int) - parser_write_mem.add_argument('value', help = 'Value', type = arg_auto_int) - parser_write_mem.add_argument('mask', help = 'Mask of bits to write', type = arg_auto_int) + 'write_mem', + help='Read-modify-write to arbitrary memory location') + parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int) + parser_write_mem.add_argument('value', help='Value', type=arg_auto_int) + parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int) + + def add_spi_flash_subparsers(parent): + """ Add common parser arguments for SPI flash properties """ + parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency', + choices=['40m', '26m', '20m', '80m'], + default=os.environ.get('ESPTOOL_FF', '40m')) + parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode', + choices=['qio', 'qout', 'dio', 'dout'], + default=os.environ.get('ESPTOOL_FM', 'qio')) + parent.add_argument('--flash_size', '-fs', help='SPI Flash size in Mbit', type=str.lower, + choices=['4m', '2m', '8m', '16m', '32m', '16m-c1', '32m-c1', '32m-c2', '64m', '128m'], + default=os.environ.get('ESPTOOL_FS', '4m')) parser_write_flash = subparsers.add_parser( - 'write_flash', - help = 'Write a binary blob to flash') - parser_write_flash.add_argument('addr_filename', nargs = '+', help = 'Address and binary file to write there, separated by space') - parser_write_flash.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency', - choices = ['40m', '26m', '20m', '80m'], default = '40m') - parser_write_flash.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode', - choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio') - parser_write_flash.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit', - choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m') + 'write_flash', + help='Write a binary blob to flash') + parser_write_flash.add_argument('addr_filename', metavar='
', help='Address followed by binary filename, separated by space', + action=AddrFilenamePairAction) + add_spi_flash_subparsers(parser_write_flash) + parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + parser_write_flash.add_argument('--verify', help='Verify just-written data (only necessary if very cautious, data is already CRCed', action='store_true') - parser_run = subparsers.add_parser( - 'run', - help = 'Run application code in flash') + subparsers.add_parser( + 'run', + help='Run application code in flash') parser_image_info = subparsers.add_parser( - 'image_info', - help = 'Dump headers from an application image') - parser_image_info.add_argument('filename', help = 'Image file to parse') + 'image_info', + help='Dump headers from an application image') + parser_image_info.add_argument('filename', help='Image file to parse') parser_make_image = subparsers.add_parser( - 'make_image', - help = 'Create an application image from binary files') - parser_make_image.add_argument('output', help = 'Output image file') - parser_make_image.add_argument('--segfile', '-f', action = 'append', help = 'Segment input file') - parser_make_image.add_argument('--segaddr', '-a', action = 'append', help = 'Segment base address', type = arg_auto_int) - parser_make_image.add_argument('--entrypoint', '-e', help = 'Address of entry point', type = arg_auto_int, default = 0) + 'make_image', + help='Create an application image from binary files') + parser_make_image.add_argument('output', help='Output image file') + parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file') + parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int) + parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0) parser_elf2image = subparsers.add_parser( - 'elf2image', - help = 'Create an application image from ELF file') - parser_elf2image.add_argument('input', help = 'Input ELF file') - parser_elf2image.add_argument('--output', '-o', help = 'Output filename prefix', type = str) - parser_elf2image.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency', - choices = ['40m', '26m', '20m', '80m'], default = '40m') - parser_elf2image.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode', - choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio') - parser_elf2image.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit', - choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m') + 'elf2image', + help='Create an application image from ELF file') + parser_elf2image.add_argument('input', help='Input ELF file') + parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str) + parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2'], default='1') + add_spi_flash_subparsers(parser_elf2image) - parser_read_mac = subparsers.add_parser( - 'read_mac', - help = 'Read MAC address from OTP ROM') + subparsers.add_parser( + 'read_mac', + help='Read MAC address from OTP ROM') - parser_flash_id = subparsers.add_parser( - 'flash_id', - help = 'Read SPI flash manufacturer and device ID') + subparsers.add_parser( + 'chip_id', + help='Read Chip ID from OTP ROM') + + subparsers.add_parser( + 'flash_id', + help='Read SPI flash manufacturer and device ID') parser_read_flash = subparsers.add_parser( - 'read_flash', - help = 'Read SPI flash content') - parser_read_flash.add_argument('address', help = 'Start address', type = arg_auto_int) - parser_read_flash.add_argument('size', help = 'Size of region to dump', type = arg_auto_int) - parser_read_flash.add_argument('filename', help = 'Name of binary dump') + 'read_flash', + help='Read SPI flash content') + parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int) + parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_read_flash.add_argument('filename', help='Name of binary dump') + parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") - parser_erase_flash = subparsers.add_parser( - 'erase_flash', - help = 'Perform Chip Erase on SPI flash') + parser_verify_flash = subparsers.add_parser( + 'verify_flash', + help='Verify a binary blob against flash') + parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space', + action=AddrFilenamePairAction) + parser_verify_flash.add_argument('--diff', '-d', help='Show differences', + choices=['no', 'yes'], default='no') + + subparsers.add_parser( + 'erase_flash', + help='Perform Chip Erase on SPI flash') + + subparsers.add_parser( + 'version', help='Print esptool version') + + # internal sanity check - every operation matches a module function of the same name + for operation in subparsers.choices.keys(): + assert operation in globals(), "%s should be a module function" % operation args = parser.parse_args() - # Create the ESPROM connection object, if needed - esp = None - if args.operation not in ('image_info','make_image','elf2image'): - esp = ESPROM(args.port, args.baud) + print 'esptool.py v%s' % __version__ + + # operation function can take 1 arg (args), 2 args (esp, arg) + # or be a member function of the ESPROM class. + + operation_func = globals()[args.operation] + operation_args,_,_,_ = inspect.getargspec(operation_func) + if operation_args[0] == 'esp': # operation function takes an ESPROM connection object + initial_baud = min(ESPROM.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate + esp = ESPROM(args.port, initial_baud) esp.connect() + operation_func(esp, args) + else: + operation_func(args) - # Do the actual work. Should probably be split into separate functions. - if args.operation == 'load_ram': - image = ESPFirmwareImage(args.filename) - print 'RAM boot...' - for (offset, size, data) in image.segments: - print 'Downloading %d bytes at %08x...' % (size, offset), - sys.stdout.flush() - esp.mem_begin(size, math.ceil(size / float(esp.ESP_RAM_BLOCK)), esp.ESP_RAM_BLOCK, offset) +class AddrFilenamePairAction(argparse.Action): + """ Custom parser class for the address/filename pairs passed as arguments """ + def __init__(self, option_strings, dest, nargs='+', **kwargs): + super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs) - seq = 0 - while len(data) > 0: - esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq) - data = data[esp.ESP_RAM_BLOCK:] - seq += 1 - print 'done!' + def __call__(self, parser, namespace, values, option_string=None): + # validate pair arguments + pairs = [] + for i in range(0,len(values),2): + try: + address = int(values[i],0) + except ValueError as e: + raise argparse.ArgumentError(self,'Address "%s" must be a number' % values[i]) + try: + argfile = open(values[i + 1], 'rb') + except IOError as e: + raise argparse.ArgumentError(self, e) + except IndexError: + raise argparse.ArgumentError(self,'Must be pairs of an address and the binary filename to write there') + pairs.append((address, argfile)) + setattr(namespace, self.dest, pairs) - print 'All segments done, executing at %08x' % image.entrypoint - esp.mem_finish(image.entrypoint) +# This is "wrapped" stub_flasher.c, to be loaded using run_stub. +_CESANTA_FLASHER_STUB = """\ +{"code_start": 1074790404, "code": "080000601C000060000000601000006031FCFF71FCFF\ +81FCFFC02000680332D218C020004807404074DCC48608005823C0200098081BA5A9239245005803\ +1B555903582337350129230B446604DFC6F3FF21EEFFC0200069020DF0000000010078480040004A\ +0040B449004012C1F0C921D911E901DD0209312020B4ED033C2C56C2073020B43C3C56420701F5FF\ +C000003C4C569206CD0EEADD860300202C4101F1FFC0000056A204C2DCF0C02DC0CC6CCAE2D1EAFF\ +0606002030F456D3FD86FBFF00002020F501E8FFC00000EC82D0CCC0C02EC0C73DEB2ADC46030020\ +2C4101E1FFC00000DC42C2DCF0C02DC056BCFEC602003C5C8601003C6C4600003C7C08312D0CD811\ +C821E80112C1100DF0000C180000140010400C0000607418000064180000801800008C1800008418\ +0000881800009018000018980040880F0040A80F0040349800404C4A0040740F0040800F0040980F\ +00400099004012C1E091F5FFC961CD0221EFFFE941F9310971D9519011C01A223902E2D1180C0222\ +6E1D21E4FF31E9FF2AF11A332D0F42630001EAFFC00000C030B43C2256A31621E1FF1A2228022030\ +B43C3256B31501ADFFC00000DD023C4256ED1431D6FF4D010C52D90E192E126E0101DDFFC0000021\ +D2FF32A101C020004802303420C0200039022C0201D7FFC00000463300000031CDFF1A333803D023\ +C03199FF27B31ADC7F31CBFF1A3328030198FFC0000056C20E2193FF2ADD060E000031C6FF1A3328\ +030191FFC0000056820DD2DD10460800000021BEFF1A2228029CE231BCFFC020F51A33290331BBFF\ +C02C411A332903C0F0F4222E1D22D204273D9332A3FFC02000280E27B3F721ABFF381E1A2242A400\ +01B5FFC00000381E2D0C42A40001B3FFC0000056120801B2FFC00000C02000280EC2DC0422D2FCC0\ +2000290E01ADFFC00000222E1D22D204226E1D281E22D204E7B204291E860000126E012198FF32A0\ +042A21C54C003198FF222E1D1A33380337B202C6D6FF2C02019FFFC000002191FF318CFF1A223A31\ +019CFFC00000218DFF1C031A22C549000C02060300003C528601003C624600003C72918BFF9A1108\ +71C861D851E841F83112C1200DF00010000068100000581000007010000074100000781000007C10\ +0000801000001C4B0040803C004091FDFF12C1E061F7FFC961E941F9310971D9519011C01A662906\ +21F3FFC2D1101A22390231F2FF0C0F1A33590331EAFFF26C1AED045C2247B3028636002D0C016DFF\ +C0000021E5FF41EAFF2A611A4469040622000021E4FF1A222802F0D2C0D7BE01DD0E31E0FF4D0D1A\ +3328033D0101E2FFC00000561209D03D2010212001DFFFC000004D0D2D0C3D01015DFFC0000041D5\ +FFDAFF1A444804D0648041D2FF1A4462640061D1FF106680622600673F1331D0FF10338028030C43\ +853A002642164613000041CAFF222C1A1A444804202FC047328006F6FF222C1A273F3861C2FF222C\ +1A1A6668066732B921BDFF3D0C1022800148FFC0000021BAFF1C031A2201BFFFC000000C02460300\ +5C3206020000005C424600005C5291B7FF9A110871C861D851E841F83112C1200DF0B0100000C010\ +0000D010000012C1E091FEFFC961D951E9410971F931CD039011C0ED02DD0431A1FF9C1422A06247\ +B302062D0021F4FF1A22490286010021F1FF1A223902219CFF2AF12D0F011FFFC00000461C0022D1\ +10011CFFC0000021E9FFFD0C1A222802C7B20621E6FF1A22F8022D0E3D014D0F0195FFC000008C52\ +22A063C6180000218BFF3D01102280F04F200111FFC00000AC7D22D1103D014D0F010DFFC0000021\ +D6FF32D110102280010EFFC0000021D3FF1C031A220185FFC00000FAEEF0CCC056ACF821CDFF317A\ +FF1A223A310105FFC0000021C9FF1C031A22017CFFC000002D0C91C8FF9A110871C861D851E841F8\ +3112C1200DF0000200600000001040020060FFFFFF0012C1E00C02290131FAFF21FAFF026107C961\ +C02000226300C02000C80320CC10564CFF21F5FFC02000380221F4FF20231029010C432D010163FF\ +C0000008712D0CC86112C1200DF00080FE3F8449004012C1D0C9A109B17CFC22C1110C13C51C0026\ +1202463000220111C24110B68202462B0031F5FF3022A02802A002002D011C03851A0066820A2801\ +32210105A6FF0607003C12C60500000010212032A01085180066A20F2221003811482105B3FF2241\ +10861A004C1206FDFF2D011C03C5160066B20E280138114821583185CFFF06F7FF005C1286F5FF00\ +10212032A01085140066A20D2221003811482105E1FF06EFFF0022A06146EDFF45F0FFC6EBFF0000\ +01D2FFC0000006E9FF000C022241100C1322C110C50F00220111060600000022C1100C13C50E0022\ +011132C2FA303074B6230206C8FF08B1C8A112C1300DF0000000000010404F484149007519031027\ +000000110040A8100040BC0F0040583F0040CC2E00401CE20040D83900408000004021F4FF12C1E0\ +C961C80221F2FF097129010C02D951C91101F4FFC0000001F3FFC00000AC2C22A3E801F2FFC00000\ +21EAFFC031412A233D0C01EFFFC000003D0222A00001EDFFC00000C1E4FF2D0C01E8FFC000002D01\ +32A004450400C5E7FFDD022D0C01E3FFC00000666D1F4B2131DCFF4600004B22C0200048023794F5\ +31D9FFC0200039023DF08601000001DCFFC000000871C861D85112C1200DF000000012C1F0026103\ +01EAFEC00000083112C1100DF000643B004012C1D0E98109B1C9A1D991F97129013911E2A0C001FA\ +FFC00000CD02E792F40C0DE2A0C0F2A0DB860D00000001F4FFC00000204220E71240F7921C226102\ +01EFFFC0000052A0DC482157120952A0DD571205460500004D0C3801DA234242001BDD3811379DC5\ +C6000000000C0DC2A0C001E3FFC00000C792F608B12D0DC8A1D891E881F87112C1300DF00000", "\ +entry": 1074792180, "num_params": 1, "params_start": 1074790400, "data": "FE0510\ +401A0610403B0610405A0610407A061040820610408C0610408C061040", "data_start": 10736\ +43520} +""" - 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() +if __name__ == '__main__': + try: + main() + except FatalError as e: + print '\nA fatal error occurred: %s' % e + sys.exit(2) diff --git a/os/flash.sh b/os/flash.sh index c303c23..e505669 100755 --- a/os/flash.sh +++ b/os/flash.sh @@ -31,6 +31,10 @@ if [ $? -ne 0 ]; then echo "Error reading the MAC -> set the device into the bootloader!" exit 1 fi - -#./esptool.py --port /dev/$DEVICE $BAUD write_flash 0x00000 nodemcu-master-enduser_setup,file,gpio,net,node,rtcfifo,rtcmem,rtctime,sntp,spi,tmr,uart,wifi,ws2812-integer.bin -./esptool.py --port /dev/ttyUSB0 $BAUD write_flash 0x00000 0x00000.bin 0x10000 0x10000.bin +echo "Flashing the old one" +./esptool.py --port /dev/$DEVICE $BAUD write_flash 0x00000 0x00000.bin 0x10000 0x10000.bin +sleep 1 +echo "Flashing the new" +./esptool.py --port /dev/$DEVICE $BAUD write_flash -fm dio 0x00000 nodemcu2.bin +# 0x3fc000 esp_init_data_default_v08.bin 0x07e000 blank.bin 0x3fe000 blank.bin +#./esptool.py --port /dev/$DEVICE $BAUD write_flash 0x00000 0x00000.bin 0x10000 0x10000.bin diff --git a/os/nodemcu-master-bit,enduser_setup,file,gpio,node,rtcfifo,rtcmem,rtctime,sntp,spi,tmr,uart,wifi,ws2812-integer.bin b/os/nodemcu-master-bit,enduser_setup,file,gpio,node,rtcfifo,rtcmem,rtctime,sntp,spi,tmr,uart,wifi,ws2812-integer.bin deleted file mode 100644 index c2d7814..0000000 Binary files a/os/nodemcu-master-bit,enduser_setup,file,gpio,node,rtcfifo,rtcmem,rtctime,sntp,spi,tmr,uart,wifi,ws2812-integer.bin and /dev/null differ diff --git a/os/nodemcu2.bin b/os/nodemcu2.bin new file mode 100644 index 0000000..1644bdf Binary files /dev/null and b/os/nodemcu2.bin differ diff --git a/simulation/.classpath b/simulation/.classpath new file mode 100644 index 0000000..33968ad --- /dev/null +++ b/simulation/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/simulation/.gitignore b/simulation/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/simulation/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/simulation/.project b/simulation/.project new file mode 100644 index 0000000..eaad9a1 --- /dev/null +++ b/simulation/.project @@ -0,0 +1,17 @@ + + + WS2812Emulation + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/simulation/Readme.md b/simulation/Readme.md new file mode 100644 index 0000000..1c1d3ff --- /dev/null +++ b/simulation/Readme.md @@ -0,0 +1,12 @@ +# Simulation + +The simualation should be started with the following arguments at this position: + `../init.lua ws28128ClockLayout.txt config.lua` + +# Use it without Eclipse + +Compiling: + `javac -d bin/ -cp libs/luaj-jme-3.0.1.jar:libs/luaj-jse-3.0.1.jar $(find src -name '*.java')` + +Running: + `java -cp libs/luaj-jme-3.0.1.jar:libs/luaj-jse-3.0.1.jar:bin de.c3ma.ollo.WS2812Simulation ../init.lua ws28128ClockLayout.txt config.lua` diff --git a/simulation/config.lua b/simulation/config.lua new file mode 100644 index 0000000..5b81003 --- /dev/null +++ b/simulation/config.lua @@ -0,0 +1,15 @@ +green=0 +green2=128 +red=128 +blue=0 + +color=string.char(0, 0, 128) +color1=string.char(128, 0, 0) +color2=string.char(tonumber(green2*0.8), 0, 0) +color3=string.char(tonumber(green2*0.4), 0, 0) +color4=string.char(tonumber(green2*0.2), 0, 0) + +colorBg=string.char(0,0,0) -- black is the default background color +sntpserverhostname="ptbtime1.ptb.de" +timezoneoffset=1 + diff --git a/simulation/libs/.gitignore b/simulation/libs/.gitignore new file mode 100644 index 0000000..44794ca --- /dev/null +++ b/simulation/libs/.gitignore @@ -0,0 +1,3 @@ +luaj-3.0.1.zip +luaj-3.0.1/ +luaj-sources-3.0.1.jar diff --git a/simulation/libs/Readme.md b/simulation/libs/Readme.md new file mode 100644 index 0000000..c76c4af --- /dev/null +++ b/simulation/libs/Readme.md @@ -0,0 +1,7 @@ +# Dependencies + +The following file is expected here: +`luaj-3.0.1.zip` + +It can be downloaded here: +https://sourceforge.net/projects/luaj/files/latest/download diff --git a/simulation/libs/luaj-jme-3.0.1.jar b/simulation/libs/luaj-jme-3.0.1.jar new file mode 100644 index 0000000..4541df3 Binary files /dev/null and b/simulation/libs/luaj-jme-3.0.1.jar differ diff --git a/simulation/libs/luaj-jse-3.0.1.jar b/simulation/libs/luaj-jse-3.0.1.jar new file mode 100644 index 0000000..e125860 Binary files /dev/null and b/simulation/libs/luaj-jse-3.0.1.jar differ diff --git a/simulation/src/de/c3ma/ollo/LuaSimulation.java b/simulation/src/de/c3ma/ollo/LuaSimulation.java new file mode 100644 index 0000000..5d3023d --- /dev/null +++ b/simulation/src/de/c3ma/ollo/LuaSimulation.java @@ -0,0 +1,15 @@ +package de.c3ma.ollo; + +/** + * created at 29.12.2017 - 18:29:07
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public interface LuaSimulation { + + public void rebootTriggered(); + + public void setSimulationTime(long timeInMillis); +} diff --git a/simulation/src/de/c3ma/ollo/LuaThreadTmr.java b/simulation/src/de/c3ma/ollo/LuaThreadTmr.java new file mode 100644 index 0000000..0a958fd --- /dev/null +++ b/simulation/src/de/c3ma/ollo/LuaThreadTmr.java @@ -0,0 +1,54 @@ +package de.c3ma.ollo; + +import org.luaj.vm2.LuaValue; + +/** + * created at 29.12.2017 - 18:48:27
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class LuaThreadTmr extends Thread { + + + private boolean running = true; + + private boolean stopped = false; + + private LuaValue code; + + private final int delay; + + private final int timerNumber; + + public LuaThreadTmr(int timerNumber, LuaValue code, boolean endlessloop, int delay) { + this.code = code; + this.running = endlessloop; + this.delay = delay; + this.timerNumber = timerNumber; + } + + @Override + public void run() { + try { + do { + Thread.sleep(delay); + if (code != null) { + code.call(); + } + } while(running); + } catch(InterruptedException ie) { + System.err.println("[TMR] Timer" + timerNumber + " interrupted"); + } + stopped = true; + } + + public boolean isStopped() { return stopped; } + + public void stopThread() { + running = false; + code = null; + } + +} diff --git a/simulation/src/de/c3ma/ollo/WS2812Simulation.java b/simulation/src/de/c3ma/ollo/WS2812Simulation.java new file mode 100644 index 0000000..08b80a2 --- /dev/null +++ b/simulation/src/de/c3ma/ollo/WS2812Simulation.java @@ -0,0 +1,173 @@ +package de.c3ma.ollo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import javax.swing.SwingUtilities; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.jse.JsePlatform; + +import de.c3ma.ollo.mockup.DoFileFunction; +import de.c3ma.ollo.mockup.ESP8266File; +import de.c3ma.ollo.mockup.ESP8266Net; +import de.c3ma.ollo.mockup.ESP8266Node; +import de.c3ma.ollo.mockup.ESP8266Time; +import de.c3ma.ollo.mockup.ESP8266Tmr; +import de.c3ma.ollo.mockup.ESP8266Uart; +import de.c3ma.ollo.mockup.ESP8266Wifi; +import de.c3ma.ollo.mockup.ESP8266Ws2812; + +/** + * created at 28.12.2017 - 13:19:32
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * + * @author ollo
+ */ +public class WS2812Simulation implements LuaSimulation { + + private Globals globals = JsePlatform.standardGlobals(); + private ESP8266Tmr espTmr = new ESP8266Tmr(); + private ESP8266File espFile = new ESP8266File(); + private ESP8266Node espNode = new ESP8266Node(this); + private DoFileFunction doFile = new DoFileFunction(globals); + private ESP8266Ws2812 ws2812 = new ESP8266Ws2812(); + private String scriptName; + + public WS2812Simulation(File sourceFolder) { + globals.load(new ESP8266Uart()); + globals.load(ws2812); + globals.load(espTmr); + globals.load(espFile); + globals.load(espNode); + globals.load(new ESP8266Wifi()); + globals.load(new ESP8266Net()); + globals.load(new ESP8266Time()); + globals.set("dofile", doFile); + + try { + File tempFile = File.createTempFile("NodemcuSimuFile", ""); + File tempDir = new File(tempFile.getParent() + File.separator + "Nodemcu" + System.currentTimeMillis()); + tempDir.mkdir(); + + System.out.println("[Nodemcu] Directory is " + tempDir.getAbsolutePath()); + + // Copy all files into the temporary folder + for (File f : sourceFolder.listFiles()) { + Files.copy(f.toPath(), new File(tempDir.getAbsolutePath() + File.separator + f.getName()).toPath()); + } + + espFile.setWorkingDirectory(tempDir); + espNode.setWorkingDirectory(tempDir); + doFile.setWorkingDirectory(tempDir); + } catch (IOException e) { + System.err.println("[Nodemcu] " + e.getMessage()); + espFile = null; + espNode = null; + } + } + + public static void main(final String[] args) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (args.length == 0) { + printUsage(); + return; + } + + if (args.length >= 1) { + File f = new File(args[0]); + if (f.exists()) { + WS2812Simulation simu = new WS2812Simulation(f.getParentFile()); + System.out.println("File : " + f.getAbsolutePath()); + + if (args.length >= 2) { + simu.setWS2812Layout(new File(args[1])); + } + try { + if (args.length >= 3) { + File additionalFile = new File(args[2]); + if (additionalFile.exists() && (simu.doFile != null)) { + + Files.copy(additionalFile.toPath(), new File(simu.doFile.getWorkingDirectory() + + File.separator + additionalFile.getName()).toPath()); + System.out.println("Integrate " + additionalFile.getName() + " into simulation"); + } else { + System.err.println("Script " + args[2] + " cannot be found"); + printUsage(); + System.exit(1); + } + } + if (args.length >= 4) { + try { + ESP8266Tmr.gTimingFactor = Integer.parseInt(args[3]); + } catch (NumberFormatException nfe) { + System.err.println("Timing factor not parsable: " + nfe.getMessage()); + printUsage(); + } + } + + simu.callScript(f.getName()); + } catch (IOException e) { + System.err.println("[Nodemcu] " + e.getMessage()); + } + } + } else { + printUsage(); + } + + } + }); + } + + private void setWS2812Layout(File file) { + if (file.exists()) { + ws2812.setLayout(file, this); + } else { + throw new RuntimeException("WS2812 Layout: " + file.getAbsolutePath() + " does not exists"); + } + } + + private static void printUsage() { + System.out.println("Usage:"); + System.out.println("one argument required: file to execute."); + System.out.println(".e.g: init.lua (ws2812 layout configuration) (additional LUA script) (timing speedup factor)"); + } + + @Override + public void rebootTriggered() { + System.out.println("=================== Reboot in Simulation -> call it again ================="); + this.espTmr.stopAllTimer(); + try { + Thread.sleep(200); + if (this.scriptName != null) { + System.out.println("Reexecuting..."); + callScript(this.scriptName); + } + } catch (InterruptedException e) { + + } + + } + + private void callScript(String filename) { + this.scriptName = filename; + + if ((espFile != null) && (espFile.getFileInWorkingDir(filename) != null)) { + LuaValue chunk = globals.loadfile(espFile.getFileInWorkingDir(filename).getAbsolutePath()); + chunk.call(); + } else { + throw new RuntimeException("Copy into temporary folder failed; script not available"); + } + } + + @Override + public void setSimulationTime(long timeInMillis) { + ESP8266Time.setOverwrittenTime(timeInMillis); + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java b/simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java new file mode 100644 index 0000000..c9e6250 --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java @@ -0,0 +1,54 @@ +package de.c3ma.ollo.mockup; + +import java.io.File; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; + +/** + * created at 29.12.2017 - 20:23:48
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class DoFileFunction extends OneArgFunction { + + private File workingDir = null; + private Globals globals; + + public DoFileFunction(Globals globals) { + this.globals = globals; + } + + @Override + public LuaValue call(LuaValue luaFilename) { + String filename = luaFilename.checkjstring(); + + System.out.println("[Nodemcu] dofile " + filename); + + File f = new File(workingDir.getAbsolutePath() + File.separator + filename); + + if (f.exists()) { + LuaValue chunk = this.globals.loadfile(f.getAbsolutePath()); + chunk.call(); + return LuaValue.valueOf(true); + } else { + return LuaValue.valueOf(false); + } + } + + public void setWorkingDirectory(File workingDir) { + this.workingDir = workingDir; + } + + public String getWorkingDirectory() { + if (workingDir != null) { + return workingDir.getAbsolutePath(); + } else { + return null; + } + } + +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266File.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266File.java new file mode 100644 index 0000000..27ad122 --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266File.java @@ -0,0 +1,101 @@ +package de.c3ma.ollo.mockup; + +import java.io.File; +import java.util.ArrayList; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; + +/** + * created at 29.12.2017 - 01:08:53
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class ESP8266File extends TwoArgFunction { + + private File workingDir = null; + + private File openedFile = null; + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable file = new LuaTable(); + file.set("open", new OpenFunction()); + file.set("list", new ListFunction()); + file.set("remove", new RemoveFunction()); + env.set("file", file); + env.get("package").get("loaded").set("file", file); + + return file; + } + + private class ListFunction extends TwoArgFunction { + + @Override + public LuaValue call(LuaValue arg1, LuaValue arg2) { + final LuaTable fileList = new LuaTable(); + + if ((workingDir != null) && (workingDir.exists())) { + File[] files = workingDir.listFiles(); + for (File file : files) { + fileList.set(file.getName(), file.getAbsolutePath()); + } + } + + return fileList; + } + + } + + private class OpenFunction extends OneArgFunction { + + @Override + public LuaValue call(LuaValue fileName) { + + final String codeFileName = fileName.checkjstring(); + final File f = new File( workingDir.getAbsolutePath() + File.separator + codeFileName); + //System.out.println("[FILE] Loading " + codeFileName); + if (f.exists()) { + ESP8266File.this.openedFile = f; + } + + return LuaValue.valueOf((f.exists())); + } + + } + + private class RemoveFunction extends OneArgFunction { + + @Override + public LuaValue call(LuaValue fileName) { + + final String luaFileName = fileName.checkjstring(); + System.out.println("[FILE] Removing " + luaFileName); + File f = new File(workingDir.getAbsolutePath() + File.separator + fileName); + if (f.exists()) { + return LuaValue.valueOf(f.delete()); + } else { + return LuaValue.valueOf(false); + } + } + + } + + public void setWorkingDirectory(File workingDir) { + this.workingDir = workingDir; + } + + public File getFileInWorkingDir(String filename) { + File f = new File (workingDir.getAbsolutePath() + File.separator + filename); + if (f.exists()) { + return f; + } else { + return null; + } + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Net.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Net.java new file mode 100644 index 0000000..6bf43c5 --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Net.java @@ -0,0 +1,83 @@ +package de.c3ma.ollo.mockup; + +import java.io.File; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.file.Files; + +import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.ZeroArgFunction; + +import de.c3ma.ollo.LuaSimulation; + +/** + * created at 29.12.2017 - 01:29:40
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class ESP8266Net extends TwoArgFunction { + + public static final int PORTNUMBER_OFFSET = 4000; + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable net = new LuaTable(); + net.set("createServer", new CreateServerFunction()); + + //FIXME net.set("send", new SendFunction()); + net.set("TCP", "TCP"); + env.set("net", net); + env.get("package").get("loaded").set("net", net); + return net; + } + + private class CreateServerFunction extends OneArgFunction { + + @Override + public LuaValue call(LuaValue arg) { + final LuaTable srv = new LuaTable(); + srv.set("listen", new ListenFunction()); + return srv; + } + + } + + private class ListenFunction extends TwoArgFunction { + + @Override + public LuaValue call(LuaValue port, LuaValue function) { + int portnumber = port.checkint(); + LuaFunction onListenFunction = function.checkfunction(); + + System.out.println("[Net] listening " + portnumber + "(simulating at " + (PORTNUMBER_OFFSET+ portnumber) + ")"); + + try + { + ServerSocket serverSocket = new ServerSocket(PORTNUMBER_OFFSET+portnumber); + serverSocket.setSoTimeout( 60000 ); // Timeout after one minute + + Socket client = serverSocket.accept(); + + } + catch ( InterruptedIOException iioe ) + { + System.err.println( "Timeout nach einer Minute!" ); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + System.out.println("[Net] server running"); + return LuaValue.valueOf(true); + } + + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Node.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Node.java new file mode 100644 index 0000000..1ff7824 --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Node.java @@ -0,0 +1,79 @@ +package de.c3ma.ollo.mockup; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.ZeroArgFunction; + +import de.c3ma.ollo.LuaSimulation; + +/** + * created at 29.12.2017 - 01:29:40
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class ESP8266Node extends TwoArgFunction { + + private File workingDir = null; + private LuaSimulation nodemcuSimu; + + public ESP8266Node(LuaSimulation nodemcuSimu) { + this.nodemcuSimu = nodemcuSimu; + } + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable node = new LuaTable(); + node.set("compile", new CompileFunction()); + node.set("restart", new RestartFunction()); + env.set("node", node); + env.get("package").get("loaded").set("node", node); + return node; + } + + private class CompileFunction extends OneArgFunction { + + @Override + public LuaValue call(LuaValue fileName) { + final String codeFileName = fileName.checkjstring(); + final String compiledFileName = fileName.checkjstring().replace(".lua", ".lc"); + final File f = new File( workingDir.getAbsolutePath() + File.separator + codeFileName); + System.out.println("[Node] Compiling " + compiledFileName); + final File outf = new File( workingDir.getAbsolutePath() + File.separator + compiledFileName); + if (f.exists()) { + //Simply copy the file as .lc file + try { + Files.copy(f.toPath(), outf.toPath()); + } catch (IOException e) { + return LuaValue.valueOf(false); + } + } + + return LuaValue.valueOf(f.exists()); + } + + } + + private class RestartFunction extends ZeroArgFunction { + + @Override + public LuaValue call() { + System.out.println("[Node] Restart"); + nodemcuSimu.rebootTriggered(); + return LuaValue.valueOf(false); + } + + } + + public void setWorkingDirectory(File workingDir) { + this.workingDir = workingDir; + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Time.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Time.java new file mode 100644 index 0000000..a8f48df --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Time.java @@ -0,0 +1,102 @@ +package de.c3ma.ollo.mockup; + +import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.ThreeArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.ZeroArgFunction; + +/** + * created at 29.12.2017 - 00:07:22
+ * creator: ollo
+ * project: Time Emulation
+ * + * Simulating the following modules: + * Sntp + * rtctime + * + * $Id: $
+ * @author ollo
+ */ +public class ESP8266Time extends TwoArgFunction { + + private static long gSimulationStartTime = 0; + + private static long gOverwrittenTime = 0; + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable sntp = new LuaTable(); + sntp.set("sync", new SyncFunction()); + env.set("sntp", sntp); + final LuaTable rtctime = new LuaTable(); + rtctime.set("get", new GetFunction()); + env.set("rtctime", rtctime); + env.get("package").get("loaded").set("sntp", sntp); + env.get("package").get("loaded").set("rtctime", rtctime); + + return sntp; + } + + /** + * Generate a time. If there is no speedup, it is simply the current system time. + * Otherwise the time is speedup by the given factor + * @return + */ + private long generateCurrenttime() { + if (gSimulationStartTime == 0) { + gSimulationStartTime = System.currentTimeMillis(); + } + + if (gOverwrittenTime == 0) { + /* Time simulation is disabled -> calculate something according to the speedup factor */ + long time = System.currentTimeMillis(); + if (ESP8266Tmr.gTimingFactor > 1) { + time = gSimulationStartTime + ((time - gSimulationStartTime) * ESP8266Tmr.gTimingFactor); + } + return time; + } else { + return gOverwrittenTime; + } + } + + private class SyncFunction extends ThreeArgFunction { + + @Override + public LuaValue call(LuaValue server, LuaValue callbackSuccess, LuaValue callbackFailure) { + String serverName = server.checkjstring(); + LuaFunction cb = callbackSuccess.checkfunction(); + System.out.println("[SNTP] sync " + serverName); + /*FIXME also make it possible to simulate the time */ + long time = generateCurrenttime(); + int seconds = (int) (time / 1000); + int useconds = (int) (time % 1000); + cb.call(LuaValue.valueOf(seconds), LuaValue.valueOf(useconds), LuaValue.valueOf(serverName)); + return LuaValue.valueOf(true); + } + + } + + private class GetFunction extends ZeroArgFunction { + + @Override + public LuaValue call() { + LuaValue[] v = new LuaValue[2]; + /*FIXME also make it possible to simulate the time */ + long time = generateCurrenttime(); + int seconds = (int) (time / 1000); + int useconds = (int) (time % 1000); + v[0] = LuaValue.valueOf(seconds); + v[1] = LuaValue.valueOf(useconds); + return LuaValue.varargsOf(v).arg1(); + } + + } + + public static void setOverwrittenTime(long timeInMillis) { + gOverwrittenTime = timeInMillis; + } + +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Tmr.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Tmr.java new file mode 100644 index 0000000..4c1782a --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Tmr.java @@ -0,0 +1,95 @@ +package de.c3ma.ollo.mockup; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.VarArgFunction; + +import de.c3ma.ollo.LuaThreadTmr; + +/** + * created at 29.12.2017 - 00:07:22
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class ESP8266Tmr extends TwoArgFunction { + + private static final int MAXTHREADS = 7; + + private static LuaThreadTmr[] allThreads = new LuaThreadTmr[MAXTHREADS]; + + public static int gTimingFactor = 1; + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable tmr = new LuaTable(); + tmr.set("stop", new stop()); + tmr.set("alarm", new alarm()); + env.set("tmr", tmr); + env.get("package").get("loaded").set("tmr", tmr); + + /* initialize the Threads */ + for (Thread t : allThreads) { + t = null; + } + + return tmr; + } + + private boolean stopTmr(int i) { + if (allThreads[i] != null) { + allThreads[i].stopThread(); + allThreads[i] = null; + return true; + } else { + return false; + } + } + + private class stop extends OneArgFunction { + + @Override + public LuaValue call(LuaValue arg) { + final int timerNumer = arg.toint(); + System.out.println("[TMR] Timer" + timerNumer + " stopped"); + return LuaValue.valueOf(stopTmr(timerNumer)); + } + + } + + private class alarm extends VarArgFunction { + public Varargs invoke(Varargs varargs) { + if (varargs.narg()== 4) { + final int timerNumer = varargs.arg(1).toint(); + final byte endlessloop = varargs.arg(3).tobyte(); + final int delay = varargs.arg(2).toint(); + final LuaValue code = varargs.arg(4); + System.out.println("[TMR] Timer" + timerNumer ); + + if ((timerNumer < 0) || (timerNumer > timerNumer)) { + return LuaValue.error("[TMR] Timer" + timerNumer + " is not available, choose 0 to 6"); + } + + if (stopTmr(timerNumer)) { + System.err.println("[TMR] Timer" + timerNumer + " stopped"); + } + + /* The cycletime is at least 1 ms */ + allThreads[timerNumer] = new LuaThreadTmr(timerNumer, code, (endlessloop == 1), Math.max(delay / gTimingFactor, 1)); + allThreads[timerNumer].start(); + } + return LuaValue.valueOf(true); + } + } + + public void stopAllTimer() { + for (int i = 0; i < allThreads.length; i++) { + stopTmr(i); + } + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Uart.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Uart.java new file mode 100644 index 0000000..9cb59c4 --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Uart.java @@ -0,0 +1,38 @@ +package de.c3ma.ollo.mockup; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.VarArgFunction; + +/** + * created at 28.12.2017 - 23:05:05
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class ESP8266Uart extends TwoArgFunction { + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable uart = new LuaTable(); + uart.set("setup", new setup()); + env.set("uart", uart); + env.get("package").get("loaded").set("uart", uart); + return uart; + } + + private class setup extends VarArgFunction { + public Varargs invoke(Varargs varargs) { + if (varargs.narg()== 6) { + System.out.println("[UART] " + varargs.arg(2) + " " + varargs.arg(3) + + ((varargs.arg(4).checkint() > 0) ? "Y" : "N") + + varargs.arg(5)); + } + return LuaValue.valueOf(true); + } + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Wifi.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Wifi.java new file mode 100644 index 0000000..3f0d41d --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Wifi.java @@ -0,0 +1,81 @@ +package de.c3ma.ollo.mockup; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.ZeroArgFunction; + +import de.c3ma.ollo.LuaSimulation; + +/** + * created at 29.12.2017 - 01:29:40
+ * creator: ollo
+ * project: WifiEmulation
+ * $Id: $
+ * @author ollo
+ */ +public class ESP8266Wifi extends TwoArgFunction { + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable wifi = new LuaTable(); + wifi.set("setmode", new SetModeFunction()); + final LuaTable ap = new LuaTable(); + ap.set("config", new ConfigFunction()); + wifi.set("ap", ap); + final LuaTable sta = new LuaTable(); + sta.set("status", new StatusFunction()); + sta.set("getip", new GetIpFunction()); + wifi.set("sta", sta); + wifi.set("SOFTAP", "SOFTAP"); + wifi.set("STATION", "STATION"); + env.set("wifi", wifi); + env.get("package").get("loaded").set("wifi", wifi); + return wifi; + } + + private class SetModeFunction extends OneArgFunction { + + @Override + public LuaValue call(LuaValue apmode) { + final String APmodeString = apmode.checkjstring(); + System.out.println("[Wifi] set mode " + APmodeString); + return LuaValue.valueOf(true); + } + + } + + private class ConfigFunction extends OneArgFunction { + + @Override + public LuaValue call(LuaValue arg) { + System.out.println("[Wifi] config"); + return LuaValue.valueOf(true); + } + + } + + private class StatusFunction extends ZeroArgFunction { + + @Override + public LuaValue call() { + return LuaValue.valueOf(5); + } + + } + + private class GetIpFunction extends ZeroArgFunction { + + @Override + public LuaValue call() { + return LuaValue.valueOf("127.0.0.1"); + } + + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Ws2812.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Ws2812.java new file mode 100644 index 0000000..ee5e04e --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Ws2812.java @@ -0,0 +1,88 @@ +package de.c3ma.ollo.mockup; + +import java.io.File; + +import javax.swing.SwingUtilities; + +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.ZeroArgFunction; + +import de.c3ma.ollo.LuaSimulation; +import de.c3ma.ollo.mockup.ui.WS2812Layout; + +/** + * created at 28.12.2017 - 23:34:04
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * + * @author ollo
+ */ +public class ESP8266Ws2812 extends TwoArgFunction { + + private static WS2812Layout layout = null; + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable ws2812 = new LuaTable(); + ws2812.set("init", new init()); + ws2812.set("write", new write()); + env.set("ws2812", ws2812); + env.get("package").get("loaded").set("ws2812", ws2812); + return ws2812; + } + + private class init extends ZeroArgFunction { + + @Override + public LuaValue call() { + System.out.println("[WS2812] init"); + return LuaValue.valueOf(true); + } + + } + + private class write extends OneArgFunction { + + @Override + public LuaValue call(LuaValue arg) { + if (arg.isstring()) { + LuaString jstring = arg.checkstring(); + final int length = jstring.rawlen(); + if ((length % 3) == 0) { + final byte[] array = jstring.m_bytes; + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + for (int i = 0; i < length; i += 3) { + if (ESP8266Ws2812.layout != null) { + int r = array[i + 0]+(Byte.MIN_VALUE*-1); + int b = array[i + 1]+(Byte.MIN_VALUE*-1); + int g = array[i + 2]+(Byte.MIN_VALUE*-1); + ESP8266Ws2812.layout.updateLED(i / 3, r, g, b); + } + } + } + }); + } + + if (ESP8266Ws2812.layout == null) { + System.out.println("[WS2812] write length:" + length); + } else { + } + } + return LuaValue.valueOf(true); + } + } + + public void setLayout(File file, LuaSimulation nodemcuSimu) { + if (ESP8266Ws2812.layout == null) { + ESP8266Ws2812.layout = WS2812Layout.parse(file, nodemcuSimu); + } + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java b/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java new file mode 100644 index 0000000..ba32513 --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java @@ -0,0 +1,301 @@ +package de.c3ma.ollo.mockup.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import de.c3ma.ollo.LuaSimulation; + +/** + * created at 02.01.2018 - 12:57:02
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * + * @author ollo
+ */ +public class WS2812Layout extends JFrame { + + /** + * + */ + private static final long serialVersionUID = -6815557232118826140L; + + private ArrayList mLines = new ArrayList(); + private int mColumn = 0; + private int mRow = 0; + private Element[][] mElements; + + private LuaSimulation nodemcuSimu; + + public WS2812Layout(LuaSimulation nodemcuSimu) { + this.nodemcuSimu = nodemcuSimu; + } + + public static WS2812Layout parse(File file, LuaSimulation nodemcuSimu) { + WS2812Layout layout = null; + try { + BufferedReader br = new BufferedReader(new FileReader(file)); + try { + String line = br.readLine(); + if (line != null) { + layout = new WS2812Layout(nodemcuSimu); + } + + while (line != null) { + if (!line.startsWith("#")) { + layout.mLines.add(line); + layout.mRow++; + layout.mColumn = Math.max(layout.mColumn, line.length()); + } + /* get the next line */ + line = br.readLine(); + } + + /* parse each line */ + layout.parse(); + layout.createAndDisplayGUI(); + } finally { + if (br != null) { + br.close(); + } + } + } catch (IOException ioe) { + + } + return layout; + } + + private void createAndDisplayGUI() { + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + JPanel contentPane = new JPanel(); + contentPane.setLayout(new BorderLayout()); + contentPane.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 2)); + + JPanel ledPanel = new JPanel(); + ledPanel.setBackground(Color.BLACK); + ledPanel.setLayout(new GridLayout(this.mRow, this.mColumn, 10, 10)); + for (int i = 0; i < this.mRow; i++) { + for (int j = 0; j < this.mColumn; j++) { + if (this.mElements[i][j] != null) { + ledPanel.add(this.mElements[i][j]); + } + } + } + contentPane.add(ledPanel, BorderLayout.CENTER); + + JPanel bottomPanel = new JPanel(); + + final JTextField dateTime = new JTextField("yyyy-mm-dd HH:MM:SS"); + dateTime.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { + warn(); + } + public void removeUpdate(DocumentEvent e) { + warn(); + } + public void insertUpdate(DocumentEvent e) { + warn(); + } + + public void warn() { + final String pattern = "(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})"; + final String current = dateTime.getText(); + + if (current.length() <=0) { + /* color "nothing" green */ + dateTime.setForeground(Color.GREEN); + /* disable the time simulation */ + nodemcuSimu.setSimulationTime(0); + return; + } + + if (!current.matches(pattern)) { + dateTime.setForeground(Color.RED); + } else { + dateTime.setForeground(Color.BLACK); + Pattern dateTimePattern = Pattern.compile(pattern); + Matcher matcher = dateTimePattern.matcher(current); + int year=0; + int month=0; + int day=0; + int hour=0; + int minute=0; + int second=0; + matcher.find(); + for (int g = 1; g <= matcher.groupCount(); g++) { + switch(g) { + case 1: /* Year */ + year = Integer.parseInt(matcher.group(g)); + break; + case 2: /* Month */ + month = Integer.parseInt(matcher.group(g)); + break; + case 3: /* Day */ + day = Integer.parseInt(matcher.group(g)); + break; + case 4: /* Hour */ + hour = Integer.parseInt(matcher.group(g)); + break; + case 5: /* Minute */ + minute = Integer.parseInt(matcher.group(g)); + break; + case 6: /* Second */ + second = Integer.parseInt(matcher.group(g)); + break; + } + } + System.out.println("[GUI] Set time to: " + year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second); + GregorianCalendar gc = new GregorianCalendar(year, month, day, hour, minute, second); + + nodemcuSimu.setSimulationTime(gc.getTimeInMillis()); + } + } + }); + bottomPanel.add(dateTime); + + final JButton btnSetCurrentTime = new JButton("Set time"); + btnSetCurrentTime.setActionCommand("Set time"); + btnSetCurrentTime.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + JButton but = (JButton) ae.getSource(); + if (but.equals(btnSetCurrentTime)) { + GregorianCalendar gc = new GregorianCalendar(); + dateTime.setText(String.format("%d-%02d-%02d %02d:%02d:%02d", + gc.get(Calendar.YEAR), gc.get(Calendar.MONTH), gc.get(Calendar.DAY_OF_MONTH), + gc.get(Calendar.HOUR_OF_DAY), gc.get(Calendar.MINUTE), gc.get(Calendar.SECOND))); + } + } + }); + bottomPanel.add(btnSetCurrentTime); + + final JButton btnReboot = new JButton("Reboot"); + btnReboot.setActionCommand("Reboot simulation"); + btnReboot.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + JButton but = (JButton) ae.getSource(); + if (but.equals(btnReboot)) { + System.out.println("[Node] Restart"); + nodemcuSimu.rebootTriggered(); + } + } + }); + bottomPanel.add(btnReboot); + + contentPane.add(bottomPanel, BorderLayout.SOUTH); + + setContentPane(contentPane); + pack(); + setLocationByPlatform(true); + setVisible(true); + } + + private void parse() { + this.mElements = new Element[this.mRow][this.mColumn]; + int row = 0; + for (String line : this.mLines) { + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if ((('A' <= c) && (c <= 'Z')) || (('0' <= c) && (c <= '9')) || (c == 'Ä') || (c == 'Ö') + || (c == 'Ü')) { + this.mElements[row][i] = new Element(c); + } else { + this.mElements[row][i] = new Element(); + } + this.mElements[row][i].setColor(0, 0, 0); + } + row++; + } + } + + public class Element extends JLabel { + + /** + * + */ + private static final long serialVersionUID = -3933903441113933637L; + + private boolean noText = false; + + /** + * Draw a simple rect + */ + public Element() { + super(); + this.noText = true; + this.setBackground(Color.BLACK); + } + + /** + * Draw a character + * + * @param character + * to show + */ + public Element(char character) { + super("" + character); + setFont(new Font("Dialog", Font.BOLD, 24)); + setHorizontalAlignment(CENTER); + // FIXME: Background color is not updated: + this.setBackground(Color.BLACK); + } + + public void setColor(int red, int green, int blue) { + this.setForeground(new Color(red, green, blue)); + this.repaint(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (noText) { + sb.append(" "); + } else { + sb.append("" + this.getText()); + } + sb.append("|" + Integer.toHexString(this.getForeground().getRed()) + " " + + Integer.toHexString(this.getForeground().getGreen()) + " " + + Integer.toHexString(this.getForeground().getBlue())); + + return sb.toString(); + } + } + + public void updateLED(int index, int r, int g, int b) { + if (mElements != null) { + int i = (index / mColumn); + int j = (index % mColumn); + // Swap each second row + if (i % 2 == 1) { + j = (mColumn-1) - j; + } + if ((i < mElements.length) && (j < mElements[i].length) && (mElements[i][j] != null)) { + Element curlbl = mElements[i][j]; + curlbl.setColor(r, g, b); + } + } + } + +} diff --git a/simulation/ws28128ClockLayout.txt b/simulation/ws28128ClockLayout.txt new file mode 100644 index 0000000..2fda3e4 --- /dev/null +++ b/simulation/ws28128ClockLayout.txt @@ -0,0 +1,17 @@ +# This file describes the layout of the WS2812 LEDs. +# _ will only draw a rect +# A-Z or 1-9 will draw the text or number +# each element will be updated with its color with the known commands of the nodemcu firmware +# +# Here the configuration for the wordclock: +ESKISTLFÜNF +ZEHNZWANZIG +DREIVIERTEL +TGNACHVORJM +HALBXZWÖLFP +ZWEINSIEBEN +KDREIRHFÜNF +ELFNEUNVIER +WACHTZEHNRS +BSECHSFMUHR +____ diff --git a/tools/initialFlash.sh b/tools/initialFlash.sh index ebcf25c..b54d309 100755 --- a/tools/initialFlash.sh +++ b/tools/initialFlash.sh @@ -18,10 +18,11 @@ if [ $# -ne 1 ]; then exit 1 fi -FILES="displayword.lua main.lua timecore.lua webpage.lua webserver.lua wordclock.lua init.lua" +FILES="displayword.lua main.lua timecore.lua webpage.html webserver.lua wordclock.lua init.lua" + # Format filesystem first echo "Format the complete ESP" -$LUATOOL -p $DEVICE -w +$LUATOOL -p $DEVICE -w -b 115200 if [ $? -ne 0 ]; then echo "STOOOOP" exit 1 @@ -36,7 +37,7 @@ for f in $FILES; do exit 1 fi echo "------------- $f ------------" - $LUATOOL -p $DEVICE -f $f -t $f + $LUATOOL -p $DEVICE -f $f -b 115200 -t $f if [ $? -ne 0 ]; then echo "STOOOOP" exit 1 @@ -44,6 +45,6 @@ for f in $FILES; do done echo "Reboot the ESP" -$LUATOOL -p $DEVICE -r +$LUATOOL -p $DEVICE -r -b 115200 exit 0 diff --git a/tools/tcpFlash.py b/tools/tcpFlash.py new file mode 100755 index 0000000..a0c92c7 --- /dev/null +++ b/tools/tcpFlash.py @@ -0,0 +1,139 @@ +#!/usr/bin/python + +import argparse +import socket +import os.path +import sys #for exit and flushing of stdout +import time + +def sendRecv(s, message, answer): + msg = message + "\n" + s.sendall(msg) + reply = s.recv(4096) + i=1 + while ((not (answer in reply)) and (i < 10)): + reply += s.recv(4096) + i = i + 1 + if answer not in reply: + return False + else: + return True + +def sendCmd(s, message, cleaningEnter=False): + msg = message + "\n" + s.sendall(msg) + time.sleep(0.050) + reply = s.recv(4096) + i=1 + while ((not (">" in reply)) and (i < 10)): + time.sleep((0.050) * i) + reply += s.recv(4096) + i = i + 1 + +# print "Send\t" + message +# print "Got\t" + reply + if (cleaningEnter): + s.sendall("\n") + if "stdin:1:" in reply: + print "ERROR, received : " + reply + return False + elif ">" in reply: + return True + else: + print "ERROR, received : " + reply + return False + +def main(nodeip, luafile, volatile=None): + if ( not os.path.isfile(luafile) ): + print "The file " + luafile + " is not available" + else: + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((nodeip, 80)) + time.sleep(0.050) + s.sendall("\n") + # Receive the hello Message of answer of the ESP + if (not sendRecv(s, "\n", "Welcome to ") ): + print "Cannot connect to the ESP" + s.close() + sys.exit(2) + + # Read all lines from the welcome message + i=0 + reply = s.recv(4096) + while ((reply is not None) and (not (">" in reply)) and (i < 100)): + reply = s.recv(4096) + i = i + 1 + + + # Communication tests + if ( not sendRecv(s, "print(12345)", "12345") ): + print "NOT communicating with an ESP8266 running LUA (nodemcu) firmware" + s.close() + sys.exit(3) + + if (volatile is None): + print "Flashing " + luafile + sendCmd(s, "file.remove(\"" + luafile+"\");", True) + sendCmd(s, "w= file.writeline", True) + sendCmd(s, "file.open(\"" + luafile + "\",\"w+\");", True) + else: + print "Executing " + luafile + " on nodemcu" + + with open(luafile) as f: + contents = f.readlines() + i=1 + for line in contents: + print "\rSending " + str(i) + "/" + str(len(contents)) + " ...", + sys.stdout.flush() + l = line.rstrip() + if (volatile is None): + if (not sendCmd(s, "w([[" + l + "]]);")): + print "Cannot write line " + str(i) + s.close() + sys.exit(4) + else: + if (not sendCmd(s, l)): + print "Cannot write line " + str(i) + s.close() + sys.exit(4) + i=i+1 + + if (volatile is None): + # Finished with updating the file in LUA + if (not sendCmd(s, "w([[" + "--EOF" + "]]);")): + print "Cannot write line " + "-- EOF" + if (not sendCmd(s, "file.close();")): + print "Cannot close the file" + sys.exit(4) + + # Check if the file exists: + if (not sendRecv(s, "=file.open(\"" + luafile + "\")", "true")): + print("Cannot send " + luafile + " to the ESP") + sys.exit(4) + else: + print("Updated " + luafile + " successfully") + else: + print("Send " + luafile + " successfully") + + # Cleaning the socket by closing it + s.close() + sys.exit(0) # Report that the flashing was succesfull + except socket.error, msg: + print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1] + sys.exit(1) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-t', '--target', help='IP address or dns of the ESP to flash') + parser.add_argument('-f', '--file', help='LUA file, that should be updated') + parser.add_argument('-v', '--volatile', help='File is executed at the commandline', action='store_const', const=1) + + args = parser.parse_args() + + if (args.target and args.file and args.volatile): + main(args.target, args.file, args.volatile) + elif (args.target and args.file): + main(args.target, args.file) + else: + parser.print_help() diff --git a/webpage.html b/webpage.html index 7e1dbdd..fad40f7 100644 --- a/webpage.html +++ b/webpage.html @@ -55,6 +55,8 @@ Please note that all settings are mandatory

4. Minute ColorFourth minute after Three quaterDreiviertel Joa/nei +Invert lines 4-6invert +
diff --git a/webserver.lua b/webserver.lua index c61ea10..9f18198 100644 --- a/webserver.lua +++ b/webserver.lua @@ -47,8 +47,8 @@ function sendPage(conn, nameOfFile, replaceMap) end buf = buf .. line - -- Sent after 1k data - if (string.len(buf) >= 700) then + -- Sent after 500 bytes data + if ( (string.len(buf) >= 500) or (node.heap() < 2000) ) then line=nil conn:send(buf) print("Sent part of " .. sentBytes .. "B") @@ -128,9 +128,10 @@ function startWebServer() if (payload:find("GET /") ~= nil) then --here is code for handling http request from a web-browser + collectgarbage() if (sendPage ~= nil) then - print("Sending webpage.html ...") + print("Sending webpage.html (" .. tostring(node.heap()) .. "B free) ...") -- Load the sendPagewebcontent replaceMap=fillDynamicMap() sendPage(conn, "webpage.html", replaceMap) @@ -165,7 +166,9 @@ function startWebServer() file.remove(configFile .. ".new") sec, _ = rtctime.get() file.open(configFile.. ".new", "w+") - file.write("-- Config\n" .. "wifi.sta.config(\"" .. _POST.ssid .. "\",[[" .. _POST.password .. "]])\n" .. "sntpserverhostname=\"" .. _POST.sntpserver .. "\"\n" .. "timezoneoffset=\"" .. _POST.timezoneoffset .. "\"\n") + file.write("-- Config\n" .. "station_cfg={}\nstation_cfg.ssid=\"" .. _POST.ssid .. "\"\nstation_cfg.pwd=\"" .. _POST.password .. "\"\nstation_cfg.save=false\nwifi.sta.config(station_cfg)\n") + file.write("sntpserverhostname=\"" .. _POST.sntpserver .. "\"\n" .. "timezoneoffset=\"" .. _POST.timezoneoffset .. "\"\n".. "inv46=\"" .. tostring(_POST.inv46) .. "\"\n") + if ( _POST.fcolor ~= nil) then -- color=string.char(_POST.green, _POST.red, _POST.blue) print ("Got fcolor: " .. _POST.fcolor) diff --git a/wordclock.lua b/wordclock.lua index f99d815..c537ad1 100755 --- a/wordclock.lua +++ b/wordclock.lua @@ -12,7 +12,7 @@ function display_timestat(hours, minutes, longmode) end -- generate an empty return type - local ret = { itis=0, fiveMin=0, tenMin=0, after=0, before=0, threeHour=0, quater=0, threequater=0, half=0, s=0, + local ret = { it=0, is=0, fiveMin=0, tenMin=0, after=0, before=0, threeHour=0, quater=0, threequater=0, half=0, s=0, one=0, oneLong=0, two=0, three=0, four=0, five=0, six=0, seven=0, eight=0, nine=0, ten=0, eleven=0, twelve=0, twenty=0, clock=0, sr_nc=0, min1=0, min2=0, min3=0, min4=0 } @@ -31,7 +31,8 @@ function display_timestat(hours, minutes, longmode) if ((longmode==1) or (minutes==0) or (minutes==6)) then - ret.itis=1 + ret.it=1 + ret.is=1 end -- Handle minutes @@ -104,10 +105,10 @@ function display_timestat(hours, minutes, longmode) end if (hours == 1) then - if (ret.before == 1) then - ret.oneLong = 1 - else + if (ret.it == 1) then ret.one=1 + else + ret.oneLong=1 end elseif (hours == 2) then ret.two=1 @@ -136,14 +137,17 @@ function display_timestat(hours, minutes, longmode) return ret end --- @fn display_countwords +-- @fn display_countcharacters_de -- Count the amount of characters, used to describe the current time in words -- @param words the same structure, as generated with the function @see display_timestat --- @return the amount of words, used to describe the time or 0 on errors -function display_countwords_de(words) +-- @return the amount of characters, used to describe the time or 0 on errors +function display_countcharacters_de(words) local amount=0 - if (words.itis == 1) then - amount = amount + 5 + if (words.it == 1) then + amount = amount + 2 + end + if (words.is == 1) then + amount = amount + 3 end if (words.fiveMin == 1) then amount = amount + 4 @@ -214,3 +218,20 @@ function display_countwords_de(words) return amount end + +-- @fn display_countcharacters_de +-- Count the amount of words, used to describe the current time in words! +-- (min1 to min4 are ignored) +-- @param words the same structure, as generated with the function @see display_timestat +-- @return the amount of words, used to describe the time or 0 on errors +function display_countwords_de(words) + local amount = 0 + for k,v in pairs(words) do + if (v ~= nil and v == 1) then + if (k ~= "min1" and k ~= "min2" and k ~= "min3" and k ~= "min4") then + amount = amount + 1 + end + end + end + return amount +end