From e5d7ec016be5a5f7f3925f67a4300d4eb2f215d0 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 19 Apr 2019 22:05:00 +0200 Subject: [PATCH 01/59] Button included --- main.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/main.lua b/main.lua index 060e438..a3241ff 100644 --- a/main.lua +++ b/main.lua @@ -160,3 +160,20 @@ else -- Logic for inital setup startSetupMode() end +----------- button --------- +gpio.mode(3, gpio.INPUT) +btnCounter=0 +-- Start the time Thread +tmr.alarm(4, 500, 1 ,function() + if (gpio.read(3) == 0) then + tmr.stop(1) -- stop the LED thread + print("Button pressed " .. tostring(btnCounter)) + btnCounter = btnCounter + 5 + local ledBuf= string.char(128,0,0):rep(btnCounter) .. string.char(0,0,0):rep(110 - btnCounter) + ws2812.write(ledBuf) + if (btnCounter >= 110) then + file.remove("config.lua") + node.restart() + end + end +end) From e33ad2639ea82f32101724c5baeb465d8a150fa9 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 19 Apr 2019 23:38:46 +0200 Subject: [PATCH 02/59] Added brightness dimming --- displayword.lua | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/displayword.lua b/displayword.lua index 113d205..8f6aa3a 100644 --- a/displayword.lua +++ b/displayword.lua @@ -42,7 +42,7 @@ function drawLEDs(data, numberNewChars, inverseRow) if (numberNewChars == nil) then numberNewChars=0 end - print(tostring(numberNewChars) .. " charactes " .. tostring(data.charsPerMinute) .. " per minute; " .. tonumber(data.drawnCharacters) .. " used characters") + --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 @@ -69,6 +69,9 @@ function round(num) end end +-- Initial value of percentage +briPercent=50 + -- Module displaying of the words function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4, characters) -- Set the local variables needed for the colored progress bar @@ -86,13 +89,14 @@ function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, co end data.charsPerMinute = round( (characters / minutes) ) if (adc ~= nil) then - briPercent=(100*adc.read(0)/900) - print("Minutes : " .. tostring(minutes) .. " Char minutes: " .. tostring(data.charsPerMinute) .. " bright: " .. tostring(briPercent) .. "%") - data.colorFg = colorForground - data.colorMin1 = colorMin1 - data.colorMin2 = colorMin2 - data.colorMin3 = colorMin3 - data.colorMin4 = colorMin4 + local per = (100*adc.read(0)/1024) + briPercent = ( ((briPercent * 4) + per) / 5) + print("Minutes : " .. tostring(minutes) .. " Char minutes: " .. tostring(data.charsPerMinute) .. " bright: " .. tostring(briPercent) .. "% " .. tostring(per) .. "%") + data.colorFg = string.char(string.byte(colorForground,1) * briPercent / 100, string.byte(colorForground,2) * briPercent / 100, string.byte(colorForground,3) * briPercent / 100) + data.colorMin1 = string.char(string.byte(colorMin1,1) * briPercent / 100, string.byte(colorMin1,2) * briPercent / 100, string.byte(colorMin1,3) * briPercent / 100) + data.colorMin2 = string.char(string.byte(colorMin2,1) * briPercent / 100, string.byte(colorMin2,2) * briPercent / 100, string.byte(colorMin2,3) * briPercent / 100) + data.colorMin3 = string.char(string.byte(colorMin3,1) * briPercent / 100, string.byte(colorMin3,2) * briPercent / 100, string.byte(colorMin3,3) * briPercent / 100) + data.colorMin4 = string.char(string.byte(colorMin4,1) * briPercent / 100, string.byte(colorMin4,2) * briPercent / 100, string.byte(colorMin4,3) * briPercent / 100) else -- devide by five (Minute 0, Minute 1 to Minute 4 takes the last chars) print("Minutes : " .. tostring(minutes) .. " Char minutes: " .. tostring(data.charsPerMinute) ) From 902ebdb85f23bf59f69c5081426cd4ad22d006ae Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 19 Apr 2019 23:54:32 +0200 Subject: [PATCH 03/59] Added garbage collection --- main.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/main.lua b/main.lua index a3241ff..7fc1fb6 100644 --- a/main.lua +++ b/main.lua @@ -136,6 +136,7 @@ function normalOperation() -- Start the time Thread tmr.alarm(1, 20000, 1 ,function() displayTime() + collectgarbage() end) end From 0aacfc2222cf02d8d30e48f4fa4a2d7cd3856a54 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 20 Apr 2019 21:39:50 +0200 Subject: [PATCH 04/59] Remove display_countcharacters_de and display_countwords_de --- displayword.lua | 51 +++------------------- main.lua | 7 +-- unit/testWordClock.lua | 20 --------- webserver.lua | 2 - wordclock.lua | 99 ------------------------------------------ 5 files changed, 9 insertions(+), 170 deletions(-) diff --git a/displayword.lua b/displayword.lua index 8f6aa3a..91e59a9 100644 --- a/displayword.lua +++ b/displayword.lua @@ -1,38 +1,8 @@ -- Module filling a buffer, sent to the LEDs -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 - elseif (data.drawnCharacters < data.charsPerMinute*3) then - return data.colorMin2 - elseif (data.drawnCharacters > data.charsPerMinute*4) then - return data.colorMin3 - elseif (data.drawnCharacters > data.charsPerMinute*5) then - return data.colorMin4 - else - return data.colorFg - end - 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 +function updateColor(data, inverseRow) + --FIXME magic missing to start on the left side + return data.colorFg end function drawLEDs(data, numberNewChars, inverseRow) @@ -42,17 +12,15 @@ function drawLEDs(data, numberNewChars, inverseRow) 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, inverseRow, numberNewChars) + tmpBuf = updateColor(data, inverseRow) else - tmpBuf=tmpBuf .. updateColor(data, inverseRow, numberNewChars) + tmpBuf=tmpBuf .. updateColor(data, inverseRow) end data.drawnCharacters=data.drawnCharacters+1 end - data.drawnWords=data.drawnWords+1 return tmpBuf end @@ -73,7 +41,7 @@ end briPercent=50 -- Module displaying of the words -function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4, characters) +function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) -- Set the local variables needed for the colored progress bar data={} @@ -87,11 +55,10 @@ function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, co elseif (words.min4 == 1) then minutes = minutes + 4 end - data.charsPerMinute = round( (characters / minutes) ) if (adc ~= nil) then local per = (100*adc.read(0)/1024) briPercent = ( ((briPercent * 4) + per) / 5) - print("Minutes : " .. tostring(minutes) .. " Char minutes: " .. tostring(data.charsPerMinute) .. " bright: " .. tostring(briPercent) .. "% " .. tostring(per) .. "%") + print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(briPercent) .. "% current: " .. tostring(per) .. "%") data.colorFg = string.char(string.byte(colorForground,1) * briPercent / 100, string.byte(colorForground,2) * briPercent / 100, string.byte(colorForground,3) * briPercent / 100) data.colorMin1 = string.char(string.byte(colorMin1,1) * briPercent / 100, string.byte(colorMin1,2) * briPercent / 100, string.byte(colorMin1,3) * briPercent / 100) data.colorMin2 = string.char(string.byte(colorMin2,1) * briPercent / 100, string.byte(colorMin2,2) * briPercent / 100, string.byte(colorMin2,3) * briPercent / 100) @@ -99,7 +66,6 @@ function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, co data.colorMin4 = string.char(string.byte(colorMin4,1) * briPercent / 100, string.byte(colorMin4,2) * briPercent / 100, string.byte(colorMin4,3) * briPercent / 100) else -- devide by five (Minute 0, Minute 1 to Minute 4 takes the last chars) - print("Minutes : " .. tostring(minutes) .. " Char minutes: " .. tostring(data.charsPerMinute) ) data.colorFg=colorForground data.colorMin1=colorMin1 data.colorMin2=colorMin2 @@ -109,7 +75,6 @@ function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, co data.words=words 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) @@ -275,7 +240,5 @@ if (words.fiveMin== 1) then buf= buf .. space:rep(1) end collectgarbage() - return buf end - diff --git a/main.lua b/main.lua index 7fc1fb6..5ed07d4 100644 --- a/main.lua +++ b/main.lua @@ -50,12 +50,9 @@ function displayTime() time = getTime(sec, timezoneoffset) words = display_timestat(time.hour, time.minute) - local charactersOfTime = display_countcharacters_de(words) - local wordsOfTime = display_countwords_de(words) - ledBuf = generateLEDs(words, color, color1, color2, color3, color4, - charactersOfTime) + ledBuf = generateLEDs(words, color, color1, color2, color3, color4) - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " in " .. charactersOfTime .. " chars " .. wordsOfTime .. " words") + print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second) --if lines 4 to 6 are inverted due to hardware-fuckup, unfuck it here if ((inv46 ~= nil) and (inv46 == "on")) then diff --git a/unit/testWordClock.lua b/unit/testWordClock.lua index e722669..ea33f55 100644 --- a/unit/testWordClock.lua +++ b/unit/testWordClock.lua @@ -56,7 +56,6 @@ expected.itis=1 expected.one=1 expected.clock=1 checkWords(leds, expected, 1, 0) -checkCharacter(display_countwords_de(leds), 11) leds=display_timestat(2,5) expected={} @@ -64,7 +63,6 @@ expected.two=1 expected.fiveMin=1 expected.after=1 checkWords(leds, expected, 2 , 5) -checkCharacter(display_countwords_de(leds), 12) leds=display_timestat(3,10) expected={} @@ -72,7 +70,6 @@ expected.three=1 expected.tenMin=1 expected.after=1 checkWords(leds, expected, 3 , 10) -checkCharacter(display_countwords_de(leds), 12) leds=display_timestat(4,15) expected={} @@ -80,7 +77,6 @@ expected.four=1 expected.after=1 expected.quater=1 checkWords(leds, expected, 4 , 15) -checkCharacter(display_countwords_de(leds), 15) leds=display_timestat(5,20) expected={} @@ -88,7 +84,6 @@ expected.five=1 expected.twenty=1 expected.after=1 checkWords(leds, expected, 5 , 20) -checkCharacter(display_countwords_de(leds), 15) leds=display_timestat(6,25) expected={} @@ -97,7 +92,6 @@ expected.fiveMin=1 expected.before=1 expected.half=1 checkWords(leds, expected, 6 , 25) -checkCharacter(display_countwords_de(leds), 17) leds=display_timestat(7,30) expected={} @@ -105,7 +99,6 @@ expected.itis=1 expected.eight=1 expected.half=1 checkWords(leds, expected, 7 , 30) -checkCharacter(display_countwords_de(leds), 13) leds=display_timestat(8,35) expected={} @@ -114,7 +107,6 @@ expected.half=1 expected.fiveMin=1 expected.after=1 checkWords(leds, expected, 8 , 35) -checkCharacter(display_countwords_de(leds), 16) leds=display_timestat(9,40) expected={} @@ -122,7 +114,6 @@ expected.ten=1 expected.twenty=1 expected.before=1 checkWords(leds, expected, 9 , 40) -checkCharacter(display_countwords_de(leds), 14) leds=display_timestat(10,45) expected={} @@ -130,7 +121,6 @@ expected.eleven=1 expected.quater=1 expected.before=1 checkWords(leds, expected, 10 , 45) -checkCharacter(display_countwords_de(leds), 13) leds=display_timestat(11,50) expected={} @@ -138,7 +128,6 @@ expected.twelve=1 expected.tenMin=1 expected.before=1 checkWords(leds, expected, 11 , 50) -checkCharacter(display_countwords_de(leds), 12) leds=display_timestat(12,55) expected={} @@ -146,7 +135,6 @@ expected.oneLong=1 expected.fiveMin=1 expected.before=1 checkWords(leds, expected, 12 , 55) -checkCharacter(display_countwords_de(leds), 11) leds=display_timestat(13,00) expected={} @@ -154,7 +142,6 @@ expected.itis=1 expected.one=1 expected.clock=1 checkWords(leds, expected, 13 , 00) -checkCharacter(display_countwords_de(leds), 11) -- test the minutes inbetween leds=display_timestat(14,01) @@ -164,7 +151,6 @@ expected.two=1 expected.min1=1 expected.clock=1 checkWords(leds, expected, 14 , 01) -checkCharacter(display_countwords_de(leds), 12) leds=display_timestat(15,02) expected={} @@ -173,7 +159,6 @@ expected.three=1 expected.min2=1 expected.clock=1 checkWords(leds, expected, 15 , 02) -checkCharacter(display_countwords_de(leds), 12) leds=display_timestat(16,03) expected={} @@ -182,7 +167,6 @@ expected.four=1 expected.min3=1 expected.clock=1 checkWords(leds, expected, 16 , 03) -checkCharacter(display_countwords_de(leds), 12) leds=display_timestat(17,04) expected={} @@ -191,7 +175,6 @@ expected.five=1 expected.min4=1 expected.clock=1 checkWords(leds, expected, 17 , 04) -checkCharacter(display_countwords_de(leds), 12) leds=display_timestat(18,06) expected={} @@ -200,7 +183,6 @@ expected.after=1 expected.min1=1 expected.six=1 checkWords(leds, expected, 18 , 06) -checkCharacter(display_countwords_de(leds), 13) leds=display_timestat(19,09) expected={} @@ -209,7 +191,6 @@ expected.after=1 expected.min4=1 expected.seven=1 checkWords(leds, expected, 19 , 09) -checkCharacter(display_countwords_de(leds), 14) leds=display_timestat(20,17) expected={} @@ -218,7 +199,6 @@ expected.after=1 expected.min2=1 expected.eight=1 checkWords(leds, expected, 20 , 17) -checkCharacter(display_countwords_de(leds), 15) diff --git a/webserver.lua b/webserver.lua index 70bcbf8..7162400 100644 --- a/webserver.lua +++ b/webserver.lua @@ -137,8 +137,6 @@ function stopWordclock() getUTCtime = nil getTime = nil display_timestat = nil - display_countcharacters_de = nil - display_countwords_de = nil collectgarbage() end diff --git a/wordclock.lua b/wordclock.lua index f95b4c0..ef3136e 100755 --- a/wordclock.lua +++ b/wordclock.lua @@ -136,102 +136,3 @@ function display_timestat(hours, minutes, longmode) collectgarbage() return ret end - --- @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 characters, used to describe the time or 0 on errors -function display_countcharacters_de(words) - local amount=0 - 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 - end - if (words.tenMin == 1) then - amount = amount + 4 - end - if (words.twenty == 1) then - amount = amount + 7 - end - if (words.threequater == 1) then - amount = amount + 11 - end - if (words.quater == 1) then - amount = amount + 7 - end - if (words.before == 1) then - amount = amount + 3 - end - if (words.after == 1) then - amount = amount + 4 - end - if (words.half == 1) then - amount = amount + 4 - end - if (words.twelve == 1) then - amount = amount + 5 - end - if (words.seven == 1) then - amount = amount + 6 - end - if (words.one == 1) then - amount = amount + 3 - end - if (words.oneLong == 1) then - amount = amount + 4 - end - if (words.two == 1) then - amount = amount + 4 - end - if (words.three == 1) then - amount = amount + 4 - end - if (words.five == 1) then - amount = amount + 4 - end - if (words.four == 1) then - amount = amount + 4 - end - if (words.nine == 1) then - amount = amount + 4 - end - if (words.eleven == 1) then - amount = amount + 3 - end - if (words.eight == 1) then - amount = amount + 4 - end - if (words.ten == 1) then - amount = amount + 4 - end - if (words.clock == 1) then - amount = amount + 3 - end - if (words.six == 1) then - amount = amount + 5 - end - - 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 From 8c73a1a8a08651b00dfececcd108823f7980bf8b Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 24 Apr 2019 21:00:12 +0200 Subject: [PATCH 05/59] Removed original config.lua file, before the simulation one is integrated --- simulation/config.lua | 1 - simulation/src/de/c3ma/ollo/WS2812Simulation.java | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/simulation/config.lua b/simulation/config.lua index 5b81003..7c38a41 100644 --- a/simulation/config.lua +++ b/simulation/config.lua @@ -12,4 +12,3 @@ 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/src/de/c3ma/ollo/WS2812Simulation.java b/simulation/src/de/c3ma/ollo/WS2812Simulation.java index 08b80a2..e88976c 100644 --- a/simulation/src/de/c3ma/ollo/WS2812Simulation.java +++ b/simulation/src/de/c3ma/ollo/WS2812Simulation.java @@ -93,9 +93,16 @@ public class WS2812Simulation implements LuaSimulation { 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()); + File targetFile = new File(simu.doFile.getWorkingDirectory() + + File.separator + additionalFile.getName()); + if (targetFile.exists()) { + if (targetFile.delete()) { + System.out.println("Removed original " + targetFile.getName() + ""); + } else { + System.err.println("Cannot removed original " + targetFile.getName() + ""); + } + } + Files.copy(additionalFile.toPath(), targetFile.toPath()); System.out.println("Integrate " + additionalFile.getName() + " into simulation"); } else { System.err.println("Script " + args[2] + " cannot be found"); From 417c93583d20adef7a0c1a3d9e5c03ba3b1c2441 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 24 Apr 2019 21:09:41 +0200 Subject: [PATCH 06/59] Added brightness scrollbar into the GUI --- .../de/c3ma/ollo/mockup/ui/WS2812Layout.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java b/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java index ba32513..f836c66 100644 --- a/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java +++ b/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java @@ -21,7 +21,10 @@ import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JSlider; import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -42,6 +45,13 @@ public class WS2812Layout extends JFrame { */ private static final long serialVersionUID = -6815557232118826140L; + /** + * Parameter for the ADC brightness control + */ + private static final int ADC_INIT = 512; + private static final int ADC_MIN = 0; + private static final int ADC_MAX = 1024; + private ArrayList mLines = new ArrayList(); private int mColumn = 0; private int mRow = 0; @@ -106,6 +116,19 @@ public class WS2812Layout extends JFrame { } contentPane.add(ledPanel, BorderLayout.CENTER); + JSlider adc = new JSlider(JSlider.VERTICAL, + ADC_MIN, ADC_MAX, ADC_INIT); + adc.addChangeListener(new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent e) { + // TODO Auto-generated method stub + + } + }); + + contentPane.add(adc, BorderLayout.EAST); + JPanel bottomPanel = new JPanel(); final JTextField dateTime = new JTextField("yyyy-mm-dd HH:MM:SS"); From aeb5dab9fd25d28260403507d32a0b0a615ef588 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 24 Apr 2019 21:21:19 +0200 Subject: [PATCH 07/59] Added ADC stimulation into the simulation --- .../src/de/c3ma/ollo/LuaSimulation.java | 4 +- .../src/de/c3ma/ollo/WS2812Simulation.java | 8 ++++ .../src/de/c3ma/ollo/mockup/ESP8266Adc.java | 47 +++++++++++++++++++ .../de/c3ma/ollo/mockup/ui/WS2812Layout.java | 3 +- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 simulation/src/de/c3ma/ollo/mockup/ESP8266Adc.java diff --git a/simulation/src/de/c3ma/ollo/LuaSimulation.java b/simulation/src/de/c3ma/ollo/LuaSimulation.java index 5d3023d..7c1223d 100644 --- a/simulation/src/de/c3ma/ollo/LuaSimulation.java +++ b/simulation/src/de/c3ma/ollo/LuaSimulation.java @@ -11,5 +11,7 @@ public interface LuaSimulation { public void rebootTriggered(); - public void setSimulationTime(long timeInMillis); + public void setSimulationTime(long timeInMillis); + + public void setADC(int value); } diff --git a/simulation/src/de/c3ma/ollo/WS2812Simulation.java b/simulation/src/de/c3ma/ollo/WS2812Simulation.java index e88976c..388d3f0 100644 --- a/simulation/src/de/c3ma/ollo/WS2812Simulation.java +++ b/simulation/src/de/c3ma/ollo/WS2812Simulation.java @@ -11,6 +11,7 @@ import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.JsePlatform; import de.c3ma.ollo.mockup.DoFileFunction; +import de.c3ma.ollo.mockup.ESP8266Adc; import de.c3ma.ollo.mockup.ESP8266File; import de.c3ma.ollo.mockup.ESP8266Net; import de.c3ma.ollo.mockup.ESP8266Node; @@ -36,6 +37,7 @@ public class WS2812Simulation implements LuaSimulation { private ESP8266Node espNode = new ESP8266Node(this); private DoFileFunction doFile = new DoFileFunction(globals); private ESP8266Ws2812 ws2812 = new ESP8266Ws2812(); + private ESP8266Adc adc = new ESP8266Adc(); private String scriptName; public WS2812Simulation(File sourceFolder) { @@ -44,6 +46,7 @@ public class WS2812Simulation implements LuaSimulation { globals.load(espTmr); globals.load(espFile); globals.load(espNode); + globals.load(adc); globals.load(new ESP8266Wifi()); globals.load(new ESP8266Net()); globals.load(new ESP8266Time()); @@ -177,4 +180,9 @@ public class WS2812Simulation implements LuaSimulation { public void setSimulationTime(long timeInMillis) { ESP8266Time.setOverwrittenTime(timeInMillis); } + + @Override + public void setADC(int value) { + adc.setADC(value); + } } diff --git a/simulation/src/de/c3ma/ollo/mockup/ESP8266Adc.java b/simulation/src/de/c3ma/ollo/mockup/ESP8266Adc.java new file mode 100644 index 0000000..4e8b45f --- /dev/null +++ b/simulation/src/de/c3ma/ollo/mockup/ESP8266Adc.java @@ -0,0 +1,47 @@ +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 24.04.2019 - 21:12:03
+ * creator: ollo
+ * project: WS2812Emulation
+ * $Id: $
+ * @author ollo
+ */ +public class ESP8266Adc extends TwoArgFunction { + + private int mAdc = 0; + + @Override + public LuaValue call(LuaValue modname, LuaValue env) { + env.checkglobals(); + final LuaTable adc = new LuaTable(); + adc.set("read", new Read(this)); + env.set("adc", adc); + env.get("package").get("loaded").set("adc", adc); + return adc; + } + + private class Read extends VarArgFunction { + + private ESP8266Adc adc; + + public Read(ESP8266Adc a) { + this.adc = a; + } + + public Varargs invoke(Varargs varargs) { + return LuaValue.valueOf(this.adc.mAdc); + } + } + + public void setADC(int newValue) { + this.mAdc = newValue; + System.out.println("[ADC] updated to " + this.mAdc); + } +} diff --git a/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java b/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java index f836c66..f59fa4b 100644 --- a/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java +++ b/simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java @@ -122,8 +122,7 @@ public class WS2812Layout extends JFrame { @Override public void stateChanged(ChangeEvent e) { - // TODO Auto-generated method stub - + nodemcuSimu.setADC(adc.getValue()); } }); From a11f756c4200f054ebba6c4a95433a6cabec3850 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 27 Apr 2019 00:14:27 +0200 Subject: [PATCH 08/59] Round percentage of adc --- displayword.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/displayword.lua b/displayword.lua index 91e59a9..3142b2c 100644 --- a/displayword.lua +++ b/displayword.lua @@ -57,7 +57,7 @@ function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, co end if (adc ~= nil) then local per = (100*adc.read(0)/1024) - briPercent = ( ((briPercent * 4) + per) / 5) + briPercent = math.floor( ((briPercent * 4) + per) / 5) print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(briPercent) .. "% current: " .. tostring(per) .. "%") data.colorFg = string.char(string.byte(colorForground,1) * briPercent / 100, string.byte(colorForground,2) * briPercent / 100, string.byte(colorForground,3) * briPercent / 100) data.colorMin1 = string.char(string.byte(colorMin1,1) * briPercent / 100, string.byte(colorMin1,2) * briPercent / 100, string.byte(colorMin1,3) * briPercent / 100) From aeae13642f63da6321f92d88968c8331c00cb691 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 27 Apr 2019 00:22:29 +0200 Subject: [PATCH 09/59] More math --- displayword.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/displayword.lua b/displayword.lua index 3142b2c..057aba7 100644 --- a/displayword.lua +++ b/displayword.lua @@ -43,7 +43,7 @@ briPercent=50 -- Module displaying of the words function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) -- Set the local variables needed for the colored progress bar - data={} + local data={} local minutes=1 if (words.min1 == 1) then @@ -56,7 +56,7 @@ function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, co minutes = minutes + 4 end if (adc ~= nil) then - local per = (100*adc.read(0)/1024) + local per = math.floor(100*adc.read(0)/1000) briPercent = math.floor( ((briPercent * 4) + per) / 5) print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(briPercent) .. "% current: " .. tostring(per) .. "%") data.colorFg = string.char(string.byte(colorForground,1) * briPercent / 100, string.byte(colorForground,2) * briPercent / 100, string.byte(colorForground,3) * briPercent / 100) From ab37d2890486492662aa7c4e39c564bc91414be6 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 27 Apr 2019 00:40:21 +0200 Subject: [PATCH 10/59] Count words with drawing function --- displayword.lua | 3 ++- main.lua | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/displayword.lua b/displayword.lua index 057aba7..d9acb16 100644 --- a/displayword.lua +++ b/displayword.lua @@ -39,11 +39,12 @@ end -- Initial value of percentage briPercent=50 +data={} -- Module displaying of the words function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) -- Set the local variables needed for the colored progress bar - local data={} + data={} local minutes=1 if (words.min1 == 1) then diff --git a/main.lua b/main.lua index 5ed07d4..a46bf4a 100644 --- a/main.lua +++ b/main.lua @@ -52,7 +52,7 @@ function displayTime() ledBuf = generateLEDs(words, color, color1, color2, color3, color4) - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second) + print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(data.drawnCharacters)) --if lines 4 to 6 are inverted due to hardware-fuckup, unfuck it here if ((inv46 ~= nil) and (inv46 == "on")) then From 97de5dbb78eacc9f0cab3ed258619afa42cfb214 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 27 Apr 2019 18:34:57 +0200 Subject: [PATCH 11/59] reworked displayword into module --- displayword.lua | 17 +++++++++++------ main.lua | 16 +++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/displayword.lua b/displayword.lua index d9acb16..63056ae 100644 --- a/displayword.lua +++ b/displayword.lua @@ -1,11 +1,16 @@ -- Module filling a buffer, sent to the LEDs -function updateColor(data, inverseRow) +local moduleName = ... +local M = {} -- public interface +_G[moduleName] = M + + +function M.updateColor(data, inverseRow) --FIXME magic missing to start on the left side return data.colorFg end -function drawLEDs(data, numberNewChars, inverseRow) +function M.drawLEDs(data, numberNewChars, inverseRow) if (inverseRow == nil) then inverseRow=false end @@ -25,7 +30,7 @@ function drawLEDs(data, numberNewChars, inverseRow) end -- Utility function for round -function round(num) +function M.round(num) under = math.floor(num) upper = math.floor(num) + 1 underV = -(under - num) @@ -38,11 +43,11 @@ function round(num) end -- Initial value of percentage -briPercent=50 -data={} +local briPercent=50 +local data={} -- Module displaying of the words -function generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) +function M.generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) -- Set the local variables needed for the colored progress bar data={} diff --git a/main.lua b/main.lua index a46bf4a..6f926aa 100644 --- a/main.lua +++ b/main.lua @@ -42,18 +42,20 @@ function syncTimeFromInternet() end function displayTime() - sec, usec = rtctime.get() + local sec, usec = rtctime.get() -- Handle lazy programmer: if (timezoneoffset == nil) then timezoneoffset=0 end - time = getTime(sec, timezoneoffset) - words = display_timestat(time.hour, time.minute) + local time = getTime(sec, timezoneoffset) + local words = display_timestat(time.hour, time.minute) + print(package.path) + dp = require("displayword") - ledBuf = generateLEDs(words, color, color1, color2, color3, color4) - - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(data.drawnCharacters)) + ledBuf = dp.generateLEDs(words, color, color1, color2, color3, color4) + print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(dp.data.drawnCharacters)) + dp = nil --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 @@ -115,7 +117,7 @@ function normalOperation() print('IP: ',wifi.sta.getip()) -- Here the WLAN is found, and something is done print("Solving dependencies") - local dependModules = { "timecore" , "wordclock", "displayword" } + local dependModules = { "timecore" , "wordclock" } for _,mod in pairs(dependModules) do print("Loading " .. mod) mydofile(mod) From c3e48fb17a7ec4ba563963efd412bad0471604ac Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 3 May 2019 20:19:12 +0200 Subject: [PATCH 12/59] Reworked require with dofile --- displayword.lua | 24 ++++++++++++++---------- main.lua | 17 ++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/displayword.lua b/displayword.lua index 63056ae..0b446ca 100644 --- a/displayword.lua +++ b/displayword.lua @@ -1,16 +1,12 @@ -- Module filling a buffer, sent to the LEDs - -local moduleName = ... -local M = {} -- public interface -_G[moduleName] = M - - -function M.updateColor(data, inverseRow) +local M +do +local updateColor = function (data, inverseRow) --FIXME magic missing to start on the left side return data.colorFg end -function M.drawLEDs(data, numberNewChars, inverseRow) +local drawLEDs = function(data, numberNewChars, inverseRow) if (inverseRow == nil) then inverseRow=false end @@ -30,7 +26,7 @@ function M.drawLEDs(data, numberNewChars, inverseRow) end -- Utility function for round -function M.round(num) +local round = function(num) under = math.floor(num) upper = math.floor(num) + 1 underV = -(under - num) @@ -47,7 +43,7 @@ local briPercent=50 local data={} -- Module displaying of the words -function M.generateLEDs(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) +local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) -- Set the local variables needed for the colored progress bar data={} @@ -248,3 +244,11 @@ if (words.fiveMin== 1) then collectgarbage() return buf end +M = { + generateLEDs = generateLEDs, + round = round, + drawLEDs = drawLEDs, + updateColor = updateColor, +} +end +return M \ No newline at end of file diff --git a/main.lua b/main.lua index 6f926aa..0cdeb43 100644 --- a/main.lua +++ b/main.lua @@ -50,12 +50,13 @@ function displayTime() local time = getTime(sec, timezoneoffset) local words = display_timestat(time.hour, time.minute) print(package.path) - dp = require("displayword") - - ledBuf = dp.generateLEDs(words, color, color1, color2, color3, color4) - - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(dp.data.drawnCharacters)) - dp = nil + dp = dofile("displayword.lc") + if (dp ~= nil) then + ledBuf = dp.generateLEDs(words, color, color1, color2, color3, color4) + print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(dp.data.drawnCharacters)) + end + dp = nil + if (ledBuf ~= nil) then --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 @@ -72,9 +73,7 @@ function displayTime() ws2812.write(ledBuf) ledBuf=nil end - - - + end -- Used for debugging if (clockdebug ~= nil) then for key,value in pairs(words) do From ba6734d5ad9ee19d971ac66fc74fbcb4788c22d6 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 3 May 2019 20:45:16 +0200 Subject: [PATCH 13/59] refactoring displayword to a module --- displayword.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/displayword.lua b/displayword.lua index 0b446ca..255efa5 100644 --- a/displayword.lua +++ b/displayword.lua @@ -46,6 +46,10 @@ local data={} local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) -- Set the local variables needed for the colored progress bar data={} + + if (words == nil) then + return nil + end local minutes=1 if (words.min1 == 1) then @@ -249,6 +253,7 @@ M = { round = round, drawLEDs = drawLEDs, updateColor = updateColor, + data = data, } end return M \ No newline at end of file From 467f525764c1f59a2887a76ec9f3d31cc240d7be Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 3 May 2019 21:09:36 +0200 Subject: [PATCH 14/59] increased update cycle to 10s --- main.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.lua b/main.lua index 0cdeb43..635d376 100644 --- a/main.lua +++ b/main.lua @@ -49,7 +49,6 @@ function displayTime() end local time = getTime(sec, timezoneoffset) local words = display_timestat(time.hour, time.minute) - print(package.path) dp = dofile("displayword.lc") if (dp ~= nil) then ledBuf = dp.generateLEDs(words, color, color1, color2, color3, color4) @@ -132,7 +131,7 @@ function normalOperation() end) displayTime() -- Start the time Thread - tmr.alarm(1, 20000, 1 ,function() + tmr.alarm(1, 10000, 1 ,function() displayTime() collectgarbage() end) From 13343fa645f0da3c116b5bb3f5aeae0d30307653 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 3 May 2019 21:26:10 +0200 Subject: [PATCH 15/59] Dim functionality is always activated --- displayword.lua | 11 +++++++---- main.lua | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/displayword.lua b/displayword.lua index 255efa5..ed67ac4 100644 --- a/displayword.lua +++ b/displayword.lua @@ -38,8 +38,6 @@ local round = function(num) end end --- Initial value of percentage -local briPercent=50 local data={} -- Module displaying of the words @@ -50,6 +48,11 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color if (words == nil) then return nil end + + -- Initial value of percentage + if (words.briPercent == nil) then + words.briPercent=50 + end local minutes=1 if (words.min1 == 1) then @@ -63,8 +66,8 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color end if (adc ~= nil) then local per = math.floor(100*adc.read(0)/1000) - briPercent = math.floor( ((briPercent * 4) + per) / 5) - print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(briPercent) .. "% current: " .. tostring(per) .. "%") + words.briPercent = tonumber( ((words.briPercent * 4) + per) / 5) + print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(words.briPercent) .. "% current: " .. tostring(per) .. "%") data.colorFg = string.char(string.byte(colorForground,1) * briPercent / 100, string.byte(colorForground,2) * briPercent / 100, string.byte(colorForground,3) * briPercent / 100) data.colorMin1 = string.char(string.byte(colorMin1,1) * briPercent / 100, string.byte(colorMin1,2) * briPercent / 100, string.byte(colorMin1,3) * briPercent / 100) data.colorMin2 = string.char(string.byte(colorMin2,1) * briPercent / 100, string.byte(colorMin2,2) * briPercent / 100, string.byte(colorMin2,3) * briPercent / 100) diff --git a/main.lua b/main.lua index 635d376..241c4a7 100644 --- a/main.lua +++ b/main.lua @@ -40,7 +40,7 @@ function syncTimeFromInternet() end ) end - +briPercent = 50 function displayTime() local sec, usec = rtctime.get() -- Handle lazy programmer: @@ -49,6 +49,7 @@ function displayTime() end local time = getTime(sec, timezoneoffset) local words = display_timestat(time.hour, time.minute) + words.briPercent=briPercent dp = dofile("displayword.lc") if (dp ~= nil) then ledBuf = dp.generateLEDs(words, color, color1, color2, color3, color4) @@ -82,7 +83,7 @@ function displayTime() end end -- cleanup - + briPercent=words.briPercent words=nil time=nil collectgarbage() From 69a2158ca6b7af5e828522e2750b9e76a99ecef5 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 3 May 2019 22:11:44 +0200 Subject: [PATCH 16/59] Brightness control can be activated and deactivated --- displayword.lua | 7 +------ main.lua | 6 +++++- webpage.html | 4 ++-- webserver.lua | 4 +++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/displayword.lua b/displayword.lua index ed67ac4..da094f2 100644 --- a/displayword.lua +++ b/displayword.lua @@ -49,11 +49,6 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color return nil end - -- Initial value of percentage - if (words.briPercent == nil) then - words.briPercent=50 - end - local minutes=1 if (words.min1 == 1) then minutes = minutes + 1 @@ -64,7 +59,7 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color elseif (words.min4 == 1) then minutes = minutes + 4 end - if (adc ~= nil) then + if ( (adc ~= nil) and (words.briPercent ~= nil) ) then local per = math.floor(100*adc.read(0)/1000) words.briPercent = tonumber( ((words.briPercent * 4) + per) / 5) print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(words.briPercent) .. "% current: " .. tostring(per) .. "%") diff --git a/main.lua b/main.lua index 241c4a7..3e75444 100644 --- a/main.lua +++ b/main.lua @@ -49,7 +49,11 @@ function displayTime() end local time = getTime(sec, timezoneoffset) local words = display_timestat(time.hour, time.minute) - words.briPercent=briPercent + if ((dim ~= nil) and (dim == "on")) then + words.briPercent=briPercent + else + words.briPercent=nil + end dp = dofile("displayword.lc") if (dp ~= nil) then ledBuf = dp.generateLEDs(words, color, color1, color2, color3, color4) diff --git a/webpage.html b/webpage.html index 2ba586a..25d1c80 100644 --- a/webpage.html +++ b/webpage.html @@ -45,7 +45,7 @@ Please note that all settings are mandatory

- + @@ -53,9 +53,9 @@ Please note that all settings are mandatory

- + diff --git a/webserver.lua b/webserver.lua index 7162400..5a7d178 100644 --- a/webserver.lua +++ b/webserver.lua @@ -120,6 +120,8 @@ function fillDynamicMap() replaceMap["$HEXCOLOR3"]=hexColor3 replaceMap["$HEXCOLOR4"]=hexColor4 replaceMap["$HEXCOLORBG"]=hexColorBg + replaceMap["$INV46"]=((inv46 ~= nil and inv46 == "on") and "checked" or "") + replaceMap["$AUTODIM"]=((dim ~= nil and dim == "on") and "checked" or "") return replaceMap end @@ -200,7 +202,7 @@ function startWebServer() sec, _ = rtctime.get() file.open(configFile.. ".new", "w+") 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") + file.write("sntpserverhostname=\"" .. _POST.sntpserver .. "\"\n" .. "timezoneoffset=\"" .. _POST.timezoneoffset .. "\"\n".. "inv46=\"" .. tostring(_POST.inv46) .. "\"\n" .. "dim=\"" .. tostring(_POST.dim) .. "\"\n") if ( _POST.fcolor ~= nil) then -- color=string.char(_POST.green, _POST.red, _POST.blue) From e14a7a576e0f86d3f6dee33b7fa6052c76927ec8 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 3 May 2019 23:18:27 +0200 Subject: [PATCH 17/59] Show WLAN when connecting --- main.lua | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/main.lua b/main.lua index 3e75444..bd88399 100644 --- a/main.lua +++ b/main.lua @@ -102,15 +102,37 @@ function normalOperation() connect_counter=0 -- Wait to be connect to the WiFi access point. - tmr.alarm(0, 1000, 1, function() + tmr.alarm(0, 500, 1, function() connect_counter=connect_counter+1 if wifi.sta.status() ~= 5 then print(connect_counter .. "/60 Connecting to AP...") - if (connect_counter % 2 == 0) then + if (connect_counter % 5 ~= 4) then local wlanColor=string.char((connect_counter % 6)*20,(connect_counter % 5)*20,(connect_counter % 3)*20) local space=string.char(0,0,0) - local buf=space:rep(6) .. wlanColor .. space:rep(4) - buf= buf .. space:rep(3) .. wlanColor:rep(3) + local buf=space:rep(6) + if ((connect_counter % 5) >= 1) then + buf = buf .. wlanColor + else + buf = buf .. space + end + buf = buf .. space:rep(4) + buf= buf .. space:rep(3) + if ((connect_counter % 5) >= 3) then + buf = buf .. wlanColor + else + buf = buf .. space + end + if ((connect_counter % 5) >= 2) then + buf = buf .. wlanColor + else + buf = buf .. space + end + if ((connect_counter % 5) >= 0) then + buf = buf .. wlanColor + else + buf = buf .. space + end + buf = buf .. space:rep(100) ws2812.write(buf) else ws2812.write(string.char(0,0,0):rep(114)) From b9e3812a72d2c5b30f218ba4a57890be4ad7e43c Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 8 May 2019 21:12:02 +0200 Subject: [PATCH 18/59] module display word loadable in the simulation --- displayword.lua | 2 +- main.lua | 12 +++++++----- simulation/src/de/c3ma/ollo/WS2812Simulation.java | 3 +-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/displayword.lua b/displayword.lua index da094f2..c1358e7 100644 --- a/displayword.lua +++ b/displayword.lua @@ -254,4 +254,4 @@ M = { data = data, } end -return M \ No newline at end of file +displayword = M diff --git a/main.lua b/main.lua index 3e75444..fa89b32 100644 --- a/main.lua +++ b/main.lua @@ -1,5 +1,7 @@ -- Main Module +displayword = {} + function startSetupMode() tmr.stop(0) tmr.stop(1) @@ -54,12 +56,12 @@ function displayTime() else words.briPercent=nil end - dp = dofile("displayword.lc") - if (dp ~= nil) then - ledBuf = dp.generateLEDs(words, color, color1, color2, color3, color4) - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(dp.data.drawnCharacters)) + dofile("displayword.lc") + if (displayword ~= nil) then + ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4) + print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(displayword.data.drawnCharacters)) end - dp = nil + displayword = nil if (ledBuf ~= nil) then --if lines 4 to 6 are inverted due to hardware-fuckup, unfuck it here if ((inv46 ~= nil) and (inv46 == "on")) then diff --git a/simulation/src/de/c3ma/ollo/WS2812Simulation.java b/simulation/src/de/c3ma/ollo/WS2812Simulation.java index 388d3f0..1c4cff9 100644 --- a/simulation/src/de/c3ma/ollo/WS2812Simulation.java +++ b/simulation/src/de/c3ma/ollo/WS2812Simulation.java @@ -160,9 +160,8 @@ public class WS2812Simulation implements LuaSimulation { callScript(this.scriptName); } } catch (InterruptedException e) { - + } - } private void callScript(String filename) { From 32106035c245ba9c70bf4dc90ba5f254c744445b Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 10 May 2019 22:58:42 +0200 Subject: [PATCH 19/59] Refactoring line generation --- displayword.lua | 170 +++++++++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 74 deletions(-) diff --git a/displayword.lua b/displayword.lua index c1358e7..eedf0e9 100644 --- a/displayword.lua +++ b/displayword.lua @@ -6,10 +6,7 @@ local updateColor = function (data, inverseRow) return data.colorFg end -local drawLEDs = function(data, numberNewChars, inverseRow) - if (inverseRow == nil) then - inverseRow=false - end +local drawLEDs = function(data, numberNewChars) if (numberNewChars == nil) then numberNewChars=0 end @@ -87,7 +84,7 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color -- Set the foreground color as the default color local buf=colorFg - + local line=space -- line 1---------------------------------------------- if (words.it==1) then buf=drawLEDs(data,2) -- ES @@ -109,120 +106,145 @@ if (words.fiveMin== 1) then buf= buf .. space:rep(4) end -- line 2-- even row (so inverted) -------------------- - if (words.twenty == 1) then - buf= buf .. drawLEDs(data,7,true) -- ZWANZIG - else - buf= buf .. space:rep(7) - end if (words.tenMin == 1) then - buf= buf .. drawLEDs(data,4,true) -- ZEHN + line= drawLEDs(data,4) -- ZEHN else - buf= buf .. space:rep(4) + line= space:rep(4) end + if (words.twenty == 1) then + line= line .. drawLEDs(data,7) -- ZWANZIG + else + line= line .. space:rep(7) + end + -- fill, the buffer + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) + end + -- line3---------------------------------------------- if (words.threequater == 1) then - buf= buf .. drawLEDs(data,11) -- Dreiviertel + line= drawLEDs(data,11) -- Dreiviertel elseif (words.quater == 1) then - buf= buf .. space:rep(4) - buf= buf .. drawLEDs(data,7) -- VIERTEL + line= space:rep(4) + line= line .. drawLEDs(data,7) -- VIERTEL else - buf= buf .. space:rep(11) + line= space:rep(11) end + -- fill, the buffer + buf = buf .. line --line 4-------- even row (so inverted) ------------- - if (words.before == 1) then - buf=buf .. space:rep(2) - buf= buf .. drawLEDs(data,3,true) -- VOR - else - buf= buf .. space:rep(5) - end if (words.after == 1) then - buf= buf .. drawLEDs(data,4,true) -- NACH - buf= buf .. space:rep(2) -- TG + line= line .. space:rep(2) -- TG + line= drawLEDs(data,4) -- NACH else - buf= buf .. space:rep(6) + line= space:rep(6) + end + if (words.before == 1) then + line= line .. drawLEDs(data,3) -- VOR + line= line .. space:rep(2) + else + line= line .. space:rep(5) + end + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) end ------------------------------------------------ if (words.half == 1) then - buf= buf .. drawLEDs(data,4) -- HALB - buf= buf .. space:rep(1) -- X + line= drawLEDs(data,4) -- HALB + line= line .. space:rep(1) -- X else - buf= buf .. space:rep(5) + line= space:rep(5) end if (words.twelve == 1) then - buf= buf .. drawLEDs(data,5) -- ZWOELF - buf= buf .. space:rep(1) -- P + line= line .. drawLEDs(data,5) -- ZWOELF + line= line .. space:rep(1) -- P else - buf= buf .. space:rep(6) + line= line .. space:rep(6) end + buf=buf .. line ------------even row (so inverted) --------------------- if (words.seven == 1) then - buf= buf .. drawLEDs(data,6,true) -- SIEBEN - buf= buf .. space:rep(5) + line= space:rep(5) + line= line .. drawLEDs(data,6) -- SIEBEN elseif (words.oneLong == 1) then - buf= buf .. space:rep(5) - buf= buf .. drawLEDs(data,4,true) -- EINS - buf= buf .. space:rep(2) + line= space:rep(2) + line= line .. drawLEDs(data,4) -- EINS + line= line .. space:rep(5) elseif (words.one == 1) then - buf= buf .. space:rep(6) - buf= buf .. drawLEDs(data,3,true) -- EIN - buf= buf .. space:rep(2) + line= space:rep(2) + line= line .. drawLEDs(data,3) -- EIN + line= line .. space:rep(6) elseif (words.two == 1) then - buf= buf .. space:rep(7) - buf= buf .. drawLEDs(data,4,true) -- ZWEI + line= drawLEDs(data,4) -- ZWEI + line= line .. space:rep(7) else - buf= buf .. space:rep(11) + line= space:rep(11) + end + + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) end ------------------------------------------------ if (words.three == 1) then - buf= buf .. space:rep(1) - buf= buf .. drawLEDs(data,4) -- DREI - buf= buf .. space:rep(6) - elseif (words.five == 1) then - buf= buf .. space:rep(7) - buf= buf .. drawLEDs(data,4) -- FUENF + line= space:rep(1) + line= line .. drawLEDs(data,4) -- DREI + line= line .. space:rep(6) + elseif (words.five == 1) then + line= space:rep(7) + line= line .. drawLEDs(data,4) -- FUENF else - buf= buf .. space:rep(11) + line= space:rep(11) end + buf = buf .. line ------------even row (so inverted) --------------------- if (words.four == 1) then - buf= buf .. drawLEDs(data,4,true) -- VIER - buf= buf .. space:rep(7) + line= space:rep(7) + line= line .. drawLEDs(data,4) -- VIER elseif (words.nine == 1) then - buf= buf .. space:rep(4) - buf= buf .. drawLEDs(data,4,true) -- NEUN - buf= buf .. space:rep(3) + line= space:rep(3) + line= line .. drawLEDs(data,4) -- NEUN + line= line .. space:rep(4) elseif (words.eleven == 1) then - buf= buf .. space:rep(8) - buf= buf .. drawLEDs(data,3,true) -- ELEVEN + line= drawLEDs(data,3) -- ELEVEN + line= line .. space:rep(8) else - buf= buf .. space:rep(11) + line= space:rep(11) + end + + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) end ------------------------------------------------ if (words.eight == 1) then - buf= buf .. space:rep(1) - buf= buf .. drawLEDs(data,4) -- ACHT - buf= buf .. space:rep(6) + line= space:rep(1) + line= line .. drawLEDs(data,4) -- ACHT + line= line .. space:rep(6) elseif (words.ten == 1) then - buf= buf .. space:rep(5) - buf= buf .. drawLEDs(data,4) -- ZEHN - buf= buf .. space:rep(2) + line= space:rep(5) + line= line .. drawLEDs(data,4) -- ZEHN + line= line .. space:rep(2) else - buf= buf .. space:rep(11) + line= space:rep(11) end + buf = buf .. line ------------even row (so inverted) --------------------- - if (words.clock == 1) then - 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,true) -- SECHS - buf= buf .. space:rep(1) + line= space:rep(1) + line= line .. drawLEDs(data,5) -- SECHS + line= line .. space:rep(2) else - buf= buf .. space:rep(8) + line= space:rep(8) end - + if (words.clock == 1) then + line= line .. drawLEDs(data,3) -- UHR + else + line= line .. space:rep(3) + end + + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) + end +------ Minutes ----------- if (words.min1 == 1) then buf= buf .. colorFg else From a81cf8f31da6a56851e1675008cd6623a912f7b3 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 10 May 2019 23:42:20 +0200 Subject: [PATCH 20/59] new algorithom works --- displayword.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/displayword.lua b/displayword.lua index eedf0e9..d03cd0d 100644 --- a/displayword.lua +++ b/displayword.lua @@ -1,7 +1,7 @@ -- Module filling a buffer, sent to the LEDs local M do -local updateColor = function (data, inverseRow) +local updateColor = function (data) --FIXME magic missing to start on the left side return data.colorFg end @@ -13,9 +13,9 @@ local drawLEDs = function(data, numberNewChars) local tmpBuf=nil for i=1,numberNewChars do if (tmpBuf == nil) then - tmpBuf = updateColor(data, inverseRow) + tmpBuf = updateColor(data) else - tmpBuf=tmpBuf .. updateColor(data, inverseRow) + tmpBuf=tmpBuf .. updateColor(data) end data.drawnCharacters=data.drawnCharacters+1 end @@ -134,8 +134,8 @@ if (words.fiveMin== 1) then buf = buf .. line --line 4-------- even row (so inverted) ------------- if (words.after == 1) then - line= line .. space:rep(2) -- TG - line= drawLEDs(data,4) -- NACH + line= space:rep(2) -- TG + line= line .. drawLEDs(data,4) -- NACH else line= space:rep(6) end From e68ab9f715846a5c38e361ffb7733a9aef8ba847 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 11 May 2019 12:09:44 +0200 Subject: [PATCH 21/59] Display amount of used characers --- displayword.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/displayword.lua b/displayword.lua index d03cd0d..2ec301b 100644 --- a/displayword.lua +++ b/displayword.lua @@ -40,8 +40,6 @@ local data={} -- Module displaying of the words local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) -- Set the local variables needed for the colored progress bar - data={} - if (words == nil) then return nil end @@ -73,9 +71,7 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color data.colorMin3=colorMin3 data.colorMin4=colorMin4 end - data.words=words data.drawnCharacters=0 - data.drawnWords=0 local charsPerLine=11 -- Space / background has no color by default local space=string.char(0,0,0) From 26b16a1e3f35138415e6832f67050469316badd8 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 11 May 2019 12:16:16 +0200 Subject: [PATCH 22/59] Fixed minute bug --- displayword.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/displayword.lua b/displayword.lua index 2ec301b..f1436f3 100644 --- a/displayword.lua +++ b/displayword.lua @@ -44,7 +44,7 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color return nil end - local minutes=1 + local minutes=0 if (words.min1 == 1) then minutes = minutes + 1 elseif (words.min2 == 1) then From a7f566f7061d061fd75db5b945dc34a1babda991 Mon Sep 17 00:00:00 2001 From: ollo Date: Sat, 11 May 2019 12:28:23 +0200 Subject: [PATCH 23/59] Refactored swapping of line 4,5 and 6 --- displayword.lua | 33 ++++++++++++++++++++++++++------- main.lua | 30 +++++++++++++++--------------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/displayword.lua b/displayword.lua index f1436f3..b5f02f9 100644 --- a/displayword.lua +++ b/displayword.lua @@ -38,11 +38,17 @@ end local data={} -- Module displaying of the words -local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4) +local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4, invertRows) -- Set the local variables needed for the colored progress bar if (words == nil) then return nil end + if (invertRows == nil) then + invertRows=false + end + + -- DEBUG code, to determine, if the color argmuments are necessary or not + print(tostring(color) .. " " .. tostring(color1)) local minutes=0 if (words.min1 == 1) then @@ -141,8 +147,12 @@ if (words.fiveMin== 1) then else line= line .. space:rep(5) end - for i = 0,10 do - buf = buf .. line:sub((11-i)*3-2,(11-i)*3) + if (invertRows == true) then + buf = buf .. line + else + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) + end end ------------------------------------------------ if (words.half == 1) then @@ -157,7 +167,13 @@ if (words.fiveMin== 1) then else line= line .. space:rep(6) end - buf=buf .. line + if (invertRows == true) then + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) + end + else + buf=buf .. line + end ------------even row (so inverted) --------------------- if (words.seven == 1) then line= space:rep(5) @@ -176,9 +192,12 @@ if (words.fiveMin== 1) then else line= space:rep(11) end - - for i = 0,10 do - buf = buf .. line:sub((11-i)*3-2,(11-i)*3) + if (invertRows == true) then + buf = buf .. line + else + for i = 0,10 do + buf = buf .. line:sub((11-i)*3-2,(11-i)*3) + end end ------------------------------------------------ if (words.three == 1) then diff --git a/main.lua b/main.lua index 29fc813..ca40d99 100644 --- a/main.lua +++ b/main.lua @@ -58,26 +58,26 @@ function displayTime() end dofile("displayword.lc") if (displayword ~= nil) then - ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4) + --if lines 4 to 6 are inverted due to hardware-fuckup, unfuck it here + local invertRows=false + if ((inv46 ~= nil) and (inv46 == "on")) then + invertRows=true + end + ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows) print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(displayword.data.drawnCharacters)) end displayword = nil if (ledBuf ~= nil) then - --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 + ws2812.write(ledBuf) else - ws2812.write(ledBuf) - ledBuf=nil + if ((colorBg ~= nil) and (color ~= nil)) then + ws2812.write(colorBg:rep(107) .. color:rep(3)) + else + local space=string.char(0,0,0) + -- set FG to fix value: + colorFg = string.char(255,0,0) + ws2812.write(space:rep(107) .. colorFg:rep(3)) + end end end -- Used for debugging From 70602f1095e19aa04edb51beefa3de48ec87bf85 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 15 May 2019 20:15:43 +0200 Subject: [PATCH 24/59] Fixed syntax --- main.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/main.lua b/main.lua index ca40d99..002bfa0 100644 --- a/main.lua +++ b/main.lua @@ -78,7 +78,6 @@ function displayTime() colorFg = string.char(255,0,0) ws2812.write(space:rep(107) .. colorFg:rep(3)) end - end end -- Used for debugging if (clockdebug ~= nil) then From 401c4fd293fc1d0270dc93f1808f21528cf95918 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 15 May 2019 20:17:48 +0200 Subject: [PATCH 25/59] Removed debugging for dofile from the simulation --- simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java b/simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java index c9e6250..0d3d741 100644 --- a/simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java +++ b/simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java @@ -26,8 +26,6 @@ public class DoFileFunction extends OneArgFunction { 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()) { From e7d0ce1c43d6443754fd8c1e4a103a643a1da24d Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 15 May 2019 20:40:41 +0200 Subject: [PATCH 26/59] Refactored color --- displayword.lua | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/displayword.lua b/displayword.lua index b5f02f9..0a9ed63 100644 --- a/displayword.lua +++ b/displayword.lua @@ -60,18 +60,25 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color elseif (words.min4 == 1) then minutes = minutes + 4 end + -- always set a foreground value + local colorFg = string.char(255,255,255) + + if (colorForground ~= nil) then + colorFg = colorForground + end + if ( (adc ~= nil) and (words.briPercent ~= nil) ) then local per = math.floor(100*adc.read(0)/1000) words.briPercent = tonumber( ((words.briPercent * 4) + per) / 5) print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(words.briPercent) .. "% current: " .. tostring(per) .. "%") - data.colorFg = string.char(string.byte(colorForground,1) * briPercent / 100, string.byte(colorForground,2) * briPercent / 100, string.byte(colorForground,3) * briPercent / 100) + data.colorFg = string.char(string.byte(colorFg,1) * briPercent / 100, string.byte(colorFg,2) * briPercent / 100, string.byte(colorFg,3) * briPercent / 100) data.colorMin1 = string.char(string.byte(colorMin1,1) * briPercent / 100, string.byte(colorMin1,2) * briPercent / 100, string.byte(colorMin1,3) * briPercent / 100) data.colorMin2 = string.char(string.byte(colorMin2,1) * briPercent / 100, string.byte(colorMin2,2) * briPercent / 100, string.byte(colorMin2,3) * briPercent / 100) data.colorMin3 = string.char(string.byte(colorMin3,1) * briPercent / 100, string.byte(colorMin3,2) * briPercent / 100, string.byte(colorMin3,3) * briPercent / 100) data.colorMin4 = string.char(string.byte(colorMin4,1) * briPercent / 100, string.byte(colorMin4,2) * briPercent / 100, string.byte(colorMin4,3) * briPercent / 100) else -- devide by five (Minute 0, Minute 1 to Minute 4 takes the last chars) - data.colorFg=colorForground + data.colorFg=colorFg data.colorMin1=colorMin1 data.colorMin2=colorMin2 data.colorMin3=colorMin3 @@ -81,11 +88,9 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color local charsPerLine=11 -- Space / background has no color by default local space=string.char(0,0,0) - -- set FG to fix value: - colorFg = string.char(255,255,255) -- Set the foreground color as the default color - local buf=colorFg + local buf=data.colorFg local line=space -- line 1---------------------------------------------- if (words.it==1) then From 7bc6ec1fbe897f78370b860f6e6341621372db35 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 15 May 2019 20:59:31 +0200 Subject: [PATCH 27/59] Added parameter with the amount of characters --- displayword.lua | 12 +++++++----- main.lua | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/displayword.lua b/displayword.lua index 0a9ed63..714b74c 100644 --- a/displayword.lua +++ b/displayword.lua @@ -38,7 +38,7 @@ end local data={} -- Module displaying of the words -local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4, invertRows) +local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4, invertRows, amountOfChars) -- Set the local variables needed for the colored progress bar if (words == nil) then return nil @@ -47,9 +47,6 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color invertRows=false end - -- DEBUG code, to determine, if the color argmuments are necessary or not - print(tostring(color) .. " " .. tostring(color1)) - local minutes=0 if (words.min1 == 1) then minutes = minutes + 1 @@ -62,11 +59,16 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color end -- always set a foreground value local colorFg = string.char(255,255,255) - if (colorForground ~= nil) then colorFg = colorForground end + if (amountOfChars ~= nil) then + data.amountOfChars = amountOfChars + else + data.amountOfChars = 0 + end + if ( (adc ~= nil) and (words.briPercent ~= nil) ) then local per = math.floor(100*adc.read(0)/1000) words.briPercent = tonumber( ((words.briPercent * 4) + per) / 5) diff --git a/main.lua b/main.lua index 002bfa0..b230fb8 100644 --- a/main.lua +++ b/main.lua @@ -63,7 +63,8 @@ function displayTime() if ((inv46 ~= nil) and (inv46 == "on")) then invertRows=true end - ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows) + displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows) + ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, displayword.data.drawnCharacters) print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(displayword.data.drawnCharacters)) end displayword = nil From 91c0a1521217c0d9f74bb4670f3c296b9b1f2824 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 22 May 2019 21:25:33 +0200 Subject: [PATCH 28/59] Display one color after the other --- displayword.lua | 37 +++++++++++++++++++++++++++++-------- main.lua | 7 +++++-- simulation/config.lua | 15 +++++++-------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/displayword.lua b/displayword.lua index 714b74c..93c8eaf 100644 --- a/displayword.lua +++ b/displayword.lua @@ -2,8 +2,30 @@ local M do local updateColor = function (data) - --FIXME magic missing to start on the left side - return data.colorFg + if (data.amountOfChars > 0) then + local div = tonumber(data.drawnCharacters/data.amountOfChars) + if (div < 1) then + print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " FG " .. tostring(string.byte(data.colorFg,1)) .. "x" .. tostring(string.byte(data.colorFg,2)) .. "x" .. tostring(string.byte(data.colorFg,3)) ) + return data.colorFg + elseif (div < 2) then + print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C1") + return data.colorMin1 + elseif (div < 3) then + print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C2") + return data.colorMin2 + elseif (div < 4) then + print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C3") + return data.colorMin3 + elseif (div < 5) then + print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C4") + return data.colorMin4 + else + print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " ELSE") + return data.colorFg + end + else + return data.colorFg + end end local drawLEDs = function(data, numberNewChars) @@ -38,7 +60,7 @@ end local data={} -- Module displaying of the words -local generateLEDs = function(words, colorForground, colorMin1, colorMin2, colorMin3, colorMin4, invertRows, amountOfChars) +local generateLEDs = function(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4, invertRows, amountOfChars) -- Set the local variables needed for the colored progress bar if (words == nil) then return nil @@ -47,7 +69,7 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color invertRows=false end - local minutes=0 + local minutes=1 if (words.min1 == 1) then minutes = minutes + 1 elseif (words.min2 == 1) then @@ -58,13 +80,12 @@ local generateLEDs = function(words, colorForground, colorMin1, colorMin2, color minutes = minutes + 4 end -- always set a foreground value - local colorFg = string.char(255,255,255) - if (colorForground ~= nil) then - colorFg = colorForground + if (colorFg == nil) then + colorFg = string.char(255,255,255) end if (amountOfChars ~= nil) then - data.amountOfChars = amountOfChars + data.amountOfChars = amountOfChars/minutes else data.amountOfChars = 0 end diff --git a/main.lua b/main.lua index b230fb8..928bd81 100644 --- a/main.lua +++ b/main.lua @@ -64,8 +64,10 @@ function displayTime() invertRows=true end displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows) - ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, displayword.data.drawnCharacters) - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(displayword.data.drawnCharacters)) + if (displayword.data.drawnCharacters ~= nil) then + ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, displayword.data.drawnCharacters) + print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(displayword.data.drawnCharacters)) + end end displayword = nil if (ledBuf ~= nil) then @@ -101,6 +103,7 @@ function normalOperation() -- Color is defined as GREEN, RED, BLUE color=string.char(0,0,250) end + print("Fg Color: " .. tostring(string.byte(color,1)) .. "x" .. tostring(string.byte(color,2)) .. "x" .. tostring(string.byte(color,3)) ) connect_counter=0 -- Wait to be connect to the WiFi access point. diff --git a/simulation/config.lua b/simulation/config.lua index 7c38a41..c6b8f5e 100644 --- a/simulation/config.lua +++ b/simulation/config.lua @@ -1,13 +1,12 @@ -green=0 -green2=128 +green2=200 red=128 -blue=0 +blue=200 -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) +color=string.char(0, 0, blue) +color1=string.char(red, 0, 0) +color2=string.char(tonumber(red*0.8), 0, 0) +color3=string.char(0, tonumber(green2*0.4), 0) +color4=string.char(0,0 ,tonumber(blue*0.2)) colorBg=string.char(0,0,0) -- black is the default background color sntpserverhostname="ptbtime1.ptb.de" From c9b248531a27d23c9d8b2bed0cd1404c76812ebd Mon Sep 17 00:00:00 2001 From: Ollo Date: Wed, 12 Feb 2020 20:14:01 +0100 Subject: [PATCH 29/59] Reduced html stuff --- index.html | 22 ---------------------- webpage.html | 9 --------- 2 files changed, 31 deletions(-) delete mode 100644 index.html diff --git a/index.html b/index.html deleted file mode 100644 index a938ea3..0000000 --- a/index.html +++ /dev/null @@ -1,22 +0,0 @@ - -WordClock Setup Page - -

Welcome to the WordClock

-
-
WIFI-SSIDSSID of the wireless network
WIFI-PasswordPassword of the wireless network
SNTP ServerServer to sync the time with. Only one ntp server is allowed.
SNTP ServerServer to sync the time with. Only one ntp server is allowed.
Offset to UTC timeDefine the offset to UTC time in hours. For example +1 hour for Germany
Foreground ColorLED Color for all minutes, divisible by five
Background ColorBackground LED Color
2. Minute ColorSecond minute after
3. Minute ColorThird minute after
4. Minute ColorFourth minute after
Three quaterDreiviertel Joa/nei
Invert lines 4-6invert
Adjust brightnesAdjust brightness of LEDs
- - - - - -" -" -" -" - - - - -
WIFI-SSID
WIFI-Password
SNTP Serverntp server to sync the time
Offset to UTC timeDefine the offset to UTC time in hours. E.g +1
Color
1. Minute Color
2. Minute Color
3. Minute Color
4. Minute Color
Three quaterDreiviertel Joa/nei
ColorModeIf checked, words are dark, rest is colored
-$ADDITIONAL_LINE - diff --git a/webpage.html b/webpage.html index 25d1c80..7266fa7 100644 --- a/webpage.html +++ b/webpage.html @@ -48,15 +48,6 @@ Please note that all settings are mandatory

SNTP ServerServer to sync the time with. Only one ntp server is allowed. Offset to UTC timeDefine the offset to UTC time in hours. For example +1 hour for Germany Foreground ColorLED Color for all minutes, divisible by five -Background ColorBackground LED Color -1. Minute ColorFirst minute after -2. Minute ColorSecond minute after -3. Minute ColorThird minute after -4. Minute ColorFourth minute after -Three quaterDreiviertel Joa/nei -Invert lines 4-6invert -Adjust brightnesAdjust brightness of LEDs -
From c85e3b34e01be3df65b8a72fa70153d979081c99 Mon Sep 17 00:00:00 2001 From: Ollo Date: Wed, 12 Feb 2020 20:52:31 +0100 Subject: [PATCH 30/59] Disable Webserver during runtime --- main.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.lua b/main.lua index 928bd81..3a03567 100644 --- a/main.lua +++ b/main.lua @@ -157,9 +157,8 @@ function normalOperation() syncTimeFromInternet() end) tmr.alarm(3, 2000, 0 ,function() - print("Start webserver...") - mydofile("webserver") - startWebServer() + -- FIXME start telent server + print("Load telnet") end) displayTime() -- Start the time Thread From ba6193ff5b10d350938248c47752ed137f6c6afd Mon Sep 17 00:00:00 2001 From: Ollo Date: Fri, 21 Feb 2020 20:21:54 +0100 Subject: [PATCH 31/59] Telnet included --- main.lua | 9 ++++++--- telnet.lua | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 telnet.lua diff --git a/main.lua b/main.lua index 3a03567..8c85f37 100644 --- a/main.lua +++ b/main.lua @@ -147,7 +147,7 @@ function normalOperation() print('IP: ',wifi.sta.getip()) -- Here the WLAN is found, and something is done print("Solving dependencies") - local dependModules = { "timecore" , "wordclock" } + local dependModules = { "timecore" , "wordclock", "telnet" } for _,mod in pairs(dependModules) do print("Loading " .. mod) mydofile(mod) @@ -157,8 +157,11 @@ function normalOperation() syncTimeFromInternet() end) tmr.alarm(3, 2000, 0 ,function() - -- FIXME start telent server - print("Load telnet") + if (startTelnetServer ~= nil) then + startTelnetServer() + else + print("NO Telent found") + end end) displayTime() -- Start the time Thread diff --git a/telnet.lua b/telnet.lua new file mode 100644 index 0000000..ab55c21 --- /dev/null +++ b/telnet.lua @@ -0,0 +1,35 @@ +-- Telnet Server +function startTelnetServer() + s=net.createServer(net.TCP, 180) + s:listen(23,function(c) + global_c=c + printlines = {} + function s_output(str) + if(global_c~=nil) then + if #printlines > 0 then + printlines[ #printlines + 1] = str + else + printlines[ #printlines + 1] = str + global_c:send("\r") -- Send something, so the queue is read after sending + end + end + end + node.output(s_output, 0) + c:on("receive",function(c,l) + node.input(l) + end) + c:on("disconnection",function(c) + node.output(nil) + global_c=nil + end) + c:on("sent", function() + if #printlines > 0 then + global_c:send(table.remove(printlines, 1)) + end + end) + print("Welcome to the Wordclock.") + print("Visite https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_en for further commands") + end) + print("Telnetserver is up") +end + From 3bd4fc8cb6afca06b537dfe92c9708952bcd493c Mon Sep 17 00:00:00 2001 From: Ollo Date: Fri, 21 Feb 2020 23:31:05 +0100 Subject: [PATCH 32/59] configuration parameter added --- telnet.lua | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/telnet.lua b/telnet.lua index ab55c21..57e0902 100644 --- a/telnet.lua +++ b/telnet.lua @@ -28,8 +28,161 @@ function startTelnetServer() end end) print("Welcome to the Wordclock.") + print("- storeConfig()") print("Visite https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_en for further commands") end) print("Telnetserver is up") end +function storeConfig(_ssid, _password, _timezoneoffset, _inv46, _dim, _fcolor, _colorMin1, _colorMin2, _colorMin3, _colorMin4, _bcolor, _threequater) + +if ( (_ssid == nil) and + (_password == nil) and + (_timezoneoffset == nil) and + (_inv46 == nil) and + (_dim == nil) and + (_fcolor == nil) and + (_colorMin1 == nil) and + (_colorMin2 == nil) and + (_colorMin3 == nil) and + (_colorMin4 == nil) and + (_bcolor == nil) and + (_threequater == nil) ) then + print("one parameter is mandatory:") + print("storeConfig(ssid, password, timezoneoffset, inv46, dim, fcolor, colorMin1, colorMin2, colorMin3, colorMin4, bcolor, threequater)") +end + +if (_password==nil) then + _, password, _, _ = wifi.sta.getconfig() + print("Restore password") +else + password = _password +end +if (_ssid==nil) then + ssid, _, _, _ = wifi.sta.getconfig() +else + ssid = _ssid +end + +if (_timezoneoffset==nil) then +timezoneoffset = _timezoneoffset +end +if (_inv46 == nil) then +inv46 = _inv46 +end +if (_dim == nil) then +dim = _dim +end +if (_fcolor == nil) then +fcolor = _fcolor +end +if (_bcolor == nil) then +bcolor = _bcolor +end +if (_colorMin1 == nil) then +colorMin1 = _colorMin1 +end +if (_colorMin2 == nil) then +colorMin2 = _colorMin2 +end +if (_colorMin3 == nil) then +colorMin3 = _colorMin3 +end +if (_colorMin4 == nil) then +colorMin4 = _colorMin4 +end +if (_threequater == nil) then +threequater = _threequater +end + +print("SSID = " .. tostring(ssid)) +print("TZNE = " .. tostring(timezoneoffset)) +print("INVT = " .. tostring(inv46)) +print("DIM = " .. tostring(dim)) +print("FCOL = " .. tostring(fcolor)) +print("BCOL = " .. tostring(bcolor)) +print("MIN1 = " .. tostring(colorMin1)) +print("MIN2 = " .. tostring(colorMin2)) +print("MIN3 = " .. tostring(colorMin3)) +print("MIN4 = " .. tostring(colorMin4)) +print("3QRT = " .. tostring(threequater)) + +-- Safe configuration: +file.remove(configFile .. ".new") +sec, _ = rtctime.get() +file.open(configFile.. ".new", "w+") +file.write("-- Config\n" .. "station_cfg={}\nstation_cfg.ssid=\"" .. ssid .. "\"\nstation_cfg.pwd=\"" .. password .. "\"\nstation_cfg.save=false\nwifi.sta.config(station_cfg)\n") +file.write("sntpserverhostname=\"" .. sntpserver .. "\"\n" .. "timezoneoffset=\"" .. timezoneoffset .. "\"\n".. "inv46=\"" .. tostring(inv46) .. "\"\n" .. "dim=\"" .. tostring(dim) .. "\"\n") + +if (fcolor ~= nil) then + local hexColor=string.sub(fcolor, 1) + local red = tonumber(string.sub(hexColor, 1, 2), 16) + local green = tonumber(string.sub(hexColor, 3, 4), 16) + local blue = tonumber(string.sub(hexColor, 5, 6), 16) + file.write("color=string.char(" .. green .. "," .. red .. "," .. blue .. ")\n") + -- fill the current values + color=string.char(green, red, blue) +end +if (colorMin1 ~= nil) then + local hexColor=string.sub(colorMin1, 1) + local red = tonumber(string.sub(hexColor, 1, 2), 16) + local green = tonumber(string.sub(hexColor, 3, 4), 16) + local blue = tonumber(string.sub(hexColor, 5, 6), 16) + file.write("color1=string.char(" .. green .. "," .. red .. "," .. blue .. ")\n") + color1=string.char(green, red, blue) +end +if ( colorMin2 ~= nil) then + local hexColor=string.sub(colorMin2, 1) + local red = tonumber(string.sub(hexColor, 1, 2), 16) + local green = tonumber(string.sub(hexColor, 3, 4), 16) + local blue = tonumber(string.sub(hexColor, 5, 6), 16) + file.write("color2=string.char(" .. green .. "," .. red .. "," .. blue .. ")\n") + color2=string.char(green, red, blue) +end +if ( colorMin3 ~= nil) then + local hexColor=string.sub(colorMin3, 1) + local red = tonumber(string.sub(hexColor, 1, 2), 16) + local green = tonumber(string.sub(hexColor, 3, 4), 16) + local blue = tonumber(string.sub(hexColor, 5, 6), 16) + file.write("color3=string.char(" .. green .. "," .. red .. "," .. blue .. ")\n") + color3=string.char(green, red, blue) +end +if ( colorMin4 ~= nil) then + local hexColor=string.sub(colorMin4, 1) + local red = tonumber(string.sub(hexColor, 1, 2), 16) + local green = tonumber(string.sub(hexColor, 3, 4), 16) + local blue = tonumber(string.sub(hexColor, 5, 6), 16) + file.write("color4=string.char(" .. green .. "," .. red .. "," .. blue .. ")\n") + color4=string.char(green, red, blue) +end +if ( bcolor ~= nil) then + local hexColor=string.sub(bcolor, 1) + local red = tonumber(string.sub(hexColor, 1, 2), 16) + local green = tonumber(string.sub(hexColor, 3, 4), 16) + local blue = tonumber(string.sub(hexColor, 5, 6), 16) + file.write("colorBg=string.char(" .. green .. "," .. red .. "," .. blue .. ")\n") + -- fill the current values + colorBg=string.char(green, red, blue) +end +if (getTime ~= nil) then + time = getTime(sec, timezoneoffset) + file.write("print(\"Config from " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. "\")\n") +end +if ( threequater ~= nil) then + file.write("threequater=true\n") + -- fill the current values + threequater=true +else + file.write("threequater=nil\n") -- unset threequater + -- fill the current values + threequater=nil +end +file.close() +collectgarbage() +sec=nil +file.remove(configFile) +if (file.rename(configFile .. ".new", configFile)) then + print("Rename Successfully") +end + +end From 77ebc55dbb5d89bdbdb3576684a54d6d2b483526 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 4 Mar 2020 19:10:37 +0100 Subject: [PATCH 33/59] Remove debugging --- displayword.lua | 6 ------ main.lua | 1 - 2 files changed, 7 deletions(-) diff --git a/displayword.lua b/displayword.lua index 93c8eaf..58752c9 100644 --- a/displayword.lua +++ b/displayword.lua @@ -5,22 +5,16 @@ local updateColor = function (data) if (data.amountOfChars > 0) then local div = tonumber(data.drawnCharacters/data.amountOfChars) if (div < 1) then - print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " FG " .. tostring(string.byte(data.colorFg,1)) .. "x" .. tostring(string.byte(data.colorFg,2)) .. "x" .. tostring(string.byte(data.colorFg,3)) ) return data.colorFg elseif (div < 2) then - print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C1") return data.colorMin1 elseif (div < 3) then - print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C2") return data.colorMin2 elseif (div < 4) then - print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C3") return data.colorMin3 elseif (div < 5) then - print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " C4") return data.colorMin4 else - print(tostring(data.drawnCharacters) .. " blocks: " .. tostring(data.amountOfChars) .. " ELSE") return data.colorFg end else diff --git a/main.lua b/main.lua index 8c85f37..59aca40 100644 --- a/main.lua +++ b/main.lua @@ -66,7 +66,6 @@ function displayTime() displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows) if (displayword.data.drawnCharacters ~= nil) then ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, displayword.data.drawnCharacters) - print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " char: " .. tostring(displayword.data.drawnCharacters)) end end displayword = nil From edfb9f2803da37a73f1f0b113c7b9f99906bd320 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 4 Mar 2020 20:54:00 +0100 Subject: [PATCH 34/59] Not forced to format ESP, when flashing serial --- tools/initialFlash.sh | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tools/initialFlash.sh b/tools/initialFlash.sh index b54d309..14e28ee 100755 --- a/tools/initialFlash.sh +++ b/tools/initialFlash.sh @@ -12,21 +12,35 @@ if [ ! -c $DEVICE ]; then fi -if [ $# -ne 1 ]; then +if [ $# -eq 0 ]; then echo "" - echo "e.g. usage $0 " + echo "e.g. usage $0 []" exit 1 fi -FILES="displayword.lua main.lua timecore.lua webpage.html webserver.lua wordclock.lua init.lua" +if [ $# -eq 1 ]; then + FILES="displayword.lua main.lua timecore.lua webpage.html webserver.lua wordclock.lua init.lua" +else + FILES=$2 +fi + # Format filesystem first -echo "Format the complete ESP" -$LUATOOL -p $DEVICE -w -b 115200 -if [ $? -ne 0 ]; then - echo "STOOOOP" - exit 1 -fi +#echo "Format the complete ESP" +#$LUATOOL -p $DEVICE -w -b 115200 +#if [ $? -ne 0 ]; then +# echo "STOOOOP" +# exit 1 +#fi + +echo "Reboot the ESP" +echo "node.restart()" >> $DEVICE +sleep 1 +for i in $(seq 0 5); do + echo "Stop TMR $i" + echo "tmr.stop($i)" >> $DEVICE + sleep 1 +done echo echo "Start Flasing ..." From 65d2de68018a1284fc11dab5de09b64375c34391 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 4 Mar 2020 21:01:13 +0100 Subject: [PATCH 35/59] Specify config file --- telnet.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/telnet.lua b/telnet.lua index 57e0902..6cd3812 100644 --- a/telnet.lua +++ b/telnet.lua @@ -107,6 +107,7 @@ print("MIN3 = " .. tostring(colorMin3)) print("MIN4 = " .. tostring(colorMin4)) print("3QRT = " .. tostring(threequater)) +local configFile="config.lua" -- Safe configuration: file.remove(configFile .. ".new") sec, _ = rtctime.get() From 17a9416607199019cd8610e2f3e09de9ab7e96dd Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 4 Mar 2020 21:01:30 +0100 Subject: [PATCH 36/59] Set telnet IP address --- tools/tcpFlash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tcpFlash.py b/tools/tcpFlash.py index 2c32002..8ff527e 100755 --- a/tools/tcpFlash.py +++ b/tools/tcpFlash.py @@ -49,7 +49,7 @@ def main(nodeip, luafile, volatile=None): else: try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((nodeip, 80)) + s.connect((nodeip, 23)) time.sleep(0.050) s.sendall("\n") # Receive the hello Message of answer of the ESP From 2f9f9fdc6852676dd5c5d10b41648fb13b5c1d2a Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 4 Mar 2020 22:00:15 +0100 Subject: [PATCH 37/59] Restore old parameter, if not set --- telnet.lua | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/telnet.lua b/telnet.lua index 6cd3812..8b81bbf 100644 --- a/telnet.lua +++ b/telnet.lua @@ -34,11 +34,12 @@ function startTelnetServer() print("Telnetserver is up") end -function storeConfig(_ssid, _password, _timezoneoffset, _inv46, _dim, _fcolor, _colorMin1, _colorMin2, _colorMin3, _colorMin4, _bcolor, _threequater) +function storeConfig(_ssid, _password, _timezoneoffset, _sntpserver, _inv46, _dim, _fcolor, _colorMin1, _colorMin2, _colorMin3, _colorMin4, _bcolor, _threequater) if ( (_ssid == nil) and (_password == nil) and (_timezoneoffset == nil) and + (_sntpserver == nil) and (_inv46 == nil) and (_dim == nil) and (_fcolor == nil) and @@ -49,7 +50,8 @@ if ( (_ssid == nil) and (_bcolor == nil) and (_threequater == nil) ) then print("one parameter is mandatory:") - print("storeConfig(ssid, password, timezoneoffset, inv46, dim, fcolor, colorMin1, colorMin2, colorMin3, colorMin4, bcolor, threequater)") + print("storeConfig(ssid, password, timezoneoffset, sntpserver, inv46, dim, fcolor, colorMin1, colorMin2, colorMin3, colorMin4, bcolor, threequater)") + return end if (_password==nil) then @@ -64,39 +66,53 @@ else ssid = _ssid end -if (_timezoneoffset==nil) then +if (_sntpserver == nil) then + sntpserver = sntpserverhostname + print("Restore SNTP: " .. tostring(sntpserver)) +else + sntpserver = _sntpserver +end + +if (_timezoneoffset ~= nil) then timezoneoffset = _timezoneoffset end -if (_inv46 == nil) then -inv46 = _inv46 +if (_inv46 ~= nil) then +if ((_inv46 == true) or (_inv == "on")) then + inv46 = "on" +elseif ((_inv46 == false) or (_inv == "off")) then + inv46 = "off" +else + inv46 = "off" end -if (_dim == nil) then +end +if (_dim ~= nil) then dim = _dim end -if (_fcolor == nil) then +if (_fcolor ~= nil) then fcolor = _fcolor end -if (_bcolor == nil) then +if (_bcolor ~= nil) then bcolor = _bcolor end -if (_colorMin1 == nil) then +if (_colorMin1 ~= nil) then colorMin1 = _colorMin1 end -if (_colorMin2 == nil) then +if (_colorMin2 ~= nil) then colorMin2 = _colorMin2 end -if (_colorMin3 == nil) then +if (_colorMin3 ~= nil) then colorMin3 = _colorMin3 end -if (_colorMin4 == nil) then +if (_colorMin4 ~= nil) then colorMin4 = _colorMin4 end -if (_threequater == nil) then +if (_threequater ~= nil) then threequater = _threequater end print("SSID = " .. tostring(ssid)) print("TZNE = " .. tostring(timezoneoffset)) +print("NTP = " .. tostring(sntpserver)) print("INVT = " .. tostring(inv46)) print("DIM = " .. tostring(dim)) print("FCOL = " .. tostring(fcolor)) @@ -184,6 +200,8 @@ sec=nil file.remove(configFile) if (file.rename(configFile .. ".new", configFile)) then print("Rename Successfully") +else + print("Cannot rename " .. configFile .. ".new") end end From 344fb0ab9a93c1331bc937ae92ab36fe7510c8b8 Mon Sep 17 00:00:00 2001 From: ollo Date: Wed, 4 Mar 2020 22:24:13 +0100 Subject: [PATCH 38/59] Added example for telnet command --- init.lua | 2 +- telnet.lua | 17 ++++++++++++++++- tools/initialFlash.sh | 2 +- tools/tcpFlash.py | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/init.lua b/init.lua index 5a107f2..1b60740 100644 --- a/init.lua +++ b/init.lua @@ -49,7 +49,7 @@ tmr.alarm(1, 5000, 0, function() (file.open("timecore.lua")) or (file.open("wordclock.lua")) or (file.open("displayword.lua")) or - (file.open("webserver.lua")) + (file.open("telnet.lua")) ) then c = string.char(0,128,0) w = string.char(0,0,0) diff --git a/telnet.lua b/telnet.lua index 8b81bbf..fa6fb09 100644 --- a/telnet.lua +++ b/telnet.lua @@ -50,7 +50,22 @@ if ( (_ssid == nil) and (_bcolor == nil) and (_threequater == nil) ) then print("one parameter is mandatory:") - print("storeConfig(ssid, password, timezoneoffset, sntpserver, inv46, dim, fcolor, colorMin1, colorMin2, colorMin3, colorMin4, bcolor, threequater)") + print("storeConfig(ssid, ") + print(" password,") + print(" timezoneoffset,") + print(" sntpserver,") + print(" inv46,") + print(" dim,") + print(" fcolor,") + print(" colorMin1,") + print(" colorMin2,") + print(" colorMin3,") + print(" colorMin4,") + print(" bcolor,") + print(" threequater)") + print(" ") + print("e.g.:") + print('storeConfig(nil, nil, 1, nil, "on", true, "00FF00", "00FF88", "008888", "00FF44", "004488", "000000", true)') return end diff --git a/tools/initialFlash.sh b/tools/initialFlash.sh index 14e28ee..a3bfdb9 100755 --- a/tools/initialFlash.sh +++ b/tools/initialFlash.sh @@ -59,6 +59,6 @@ for f in $FILES; do done echo "Reboot the ESP" -$LUATOOL -p $DEVICE -r -b 115200 +echo "node.restart()" >> $DEVICE exit 0 diff --git a/tools/tcpFlash.py b/tools/tcpFlash.py index 8ff527e..4f7526b 100755 --- a/tools/tcpFlash.py +++ b/tools/tcpFlash.py @@ -94,7 +94,7 @@ def main(nodeip, luafile, volatile=None): print "add a space at the end" if (volatile is None): - if (not sendCmd(s, "w([[" + l + "]]);")): + if (not sendCmd(s, "w([==[" + l + "]==]);")): print "Cannot write line " + str(i) s.close() sys.exit(4) From b5f46a14d2745cb219e4ec93fca62ebb8c6d1ba8 Mon Sep 17 00:00:00 2001 From: ollo Date: Fri, 6 Mar 2020 23:55:14 +0100 Subject: [PATCH 39/59] Indention improved --- telnet.lua | 18 +++++++++--------- tools/initialFlash.sh | 36 +++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/telnet.lua b/telnet.lua index fa6fb09..a2ad090 100644 --- a/telnet.lua +++ b/telnet.lua @@ -100,29 +100,29 @@ else inv46 = "off" end end -if (_dim ~= nil) then -dim = _dim +if ( _dim ~= nil) then + dim = _dim end if (_fcolor ~= nil) then -fcolor = _fcolor + fcolor = _fcolor end if (_bcolor ~= nil) then -bcolor = _bcolor + bcolor = _bcolor end if (_colorMin1 ~= nil) then -colorMin1 = _colorMin1 + colorMin1 = _colorMin1 end if (_colorMin2 ~= nil) then -colorMin2 = _colorMin2 + colorMin2 = _colorMin2 end if (_colorMin3 ~= nil) then -colorMin3 = _colorMin3 + colorMin3 = _colorMin3 end if (_colorMin4 ~= nil) then -colorMin4 = _colorMin4 + colorMin4 = _colorMin4 end if (_threequater ~= nil) then -threequater = _threequater + threequater = _threequater end print("SSID = " .. tostring(ssid)) diff --git a/tools/initialFlash.sh b/tools/initialFlash.sh index a3bfdb9..a6270ad 100755 --- a/tools/initialFlash.sh +++ b/tools/initialFlash.sh @@ -3,6 +3,7 @@ LUATOOL=./tools/luatool.py DEVICE=$1 +BAUD=115200 # check the serial connection @@ -19,30 +20,31 @@ if [ $# -eq 0 ]; then fi if [ $# -eq 1 ]; then - FILES="displayword.lua main.lua timecore.lua webpage.html webserver.lua wordclock.lua init.lua" + FILES="displayword.lua main.lua timecore.lua webpage.html webserver.lua telnet.lua wordclock.lua init.lua" else FILES=$2 fi # Format filesystem first -#echo "Format the complete ESP" -#$LUATOOL -p $DEVICE -w -b 115200 -#if [ $? -ne 0 ]; then -# echo "STOOOOP" -# exit 1 -#fi +echo "Format the complete ESP" +$LUATOOL -p $DEVICE -w -b $BAUD +if [ $? -ne 0 ]; then + echo "STOOOOP" + exit 1 +fi -echo "Reboot the ESP" -echo "node.restart()" >> $DEVICE -sleep 1 -for i in $(seq 0 5); do - echo "Stop TMR $i" - echo "tmr.stop($i)" >> $DEVICE - sleep 1 -done +#stty -F $DEVICE $BAUD +#echo "Reboot the ESP" +#echo "node.restart()" >> $DEVICE +#sleep 1 +#for i in $(seq 0 5); do +# echo "Stop TMR $i" +# echo "tmr.stop($i)" >> $DEVICE +# sleep 1 +#done -echo +#echo echo "Start Flasing ..." for f in $FILES; do if [ ! -f $f ]; then @@ -51,7 +53,7 @@ for f in $FILES; do exit 1 fi echo "------------- $f ------------" - $LUATOOL -p $DEVICE -f $f -b 115200 -t $f + $LUATOOL -p $DEVICE -f $f -b $BAUD -t $f if [ $? -ne 0 ]; then echo "STOOOOP" exit 1 From ebacc86f01a3ec254d787a674cee2b84c8a54335 Mon Sep 17 00:00:00 2001 From: Ollo Date: Mon, 7 Dec 2020 20:02:01 +0100 Subject: [PATCH 40/59] Upgraded esptool to python3 --- os/esptool.py | 4166 +++++++++++++++++++++++++++++++++++++++++-------- os/flash.sh | 13 +- 2 files changed, 3480 insertions(+), 699 deletions(-) diff --git a/os/esptool.py b/os/esptool.py index 38ffb72..6a9a97f 100755 --- a/os/esptool.py +++ b/os/esptool.py @@ -1,10 +1,8 @@ #!/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-2016 Fredrik Ahlberg, Angus Gratton, other contributors as noted. +# ESP8266 & ESP32 family ROM Bootloader Utility +# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) PTE LTD, other contributors as noted. +# https://github.com/espressif/esptool # # 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 @@ -18,24 +16,179 @@ # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin # Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import division, print_function + import argparse +import base64 +import binascii +import copy import hashlib import inspect -import json +import io +import itertools import os -import serial +import shlex +import string import struct -import subprocess import sys -import tempfile import time +import zlib + +try: + import serial +except ImportError: + print("Pyserial is not installed for %s. Check the README for installation instructions." % (sys.executable)) + raise + +# check 'serial' is 'pyserial' and not 'serial' https://github.com/espressif/esptool/issues/269 +try: + if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__: + raise ImportError(""" +esptool.py depends on pyserial, but there is a conflict with a currently installed package named 'serial'. + +You may be able to work around this by 'pip uninstall serial; pip install pyserial' \ +but this may break other installed Python software that depends on 'serial'. + +There is no good fix for this right now, apart from configuring virtualenvs. \ +See https://github.com/espressif/esptool/issues/269#issuecomment-385298196 for discussion of the underlying issue(s).""") +except TypeError: + pass # __doc__ returns None for pyserial + +try: + import serial.tools.list_ports as list_ports +except ImportError: + print("The installed version (%s) of pyserial appears to be too old for esptool.py (Python interpreter %s). " + "Check the README for installation instructions." % (sys.VERSION, sys.executable)) + raise +except Exception: + if sys.platform == "darwin": + # swallow the exception, this is a known issue in pyserial+macOS Big Sur preview ref https://github.com/espressif/esptool/issues/540 + list_ports = None + else: + raise -__version__ = "1.2-dev" +__version__ = "3.1-dev" + +MAX_UINT32 = 0xffffffff +MAX_UINT24 = 0xffffff + +DEFAULT_TIMEOUT = 3 # timeout for most flash operations +START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase) +CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase +MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2 # longest any command can run +SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader +MD5_TIMEOUT_PER_MB = 8 # timeout (per megabyte) for calculating md5sum +ERASE_REGION_TIMEOUT_PER_MB = 30 # timeout (per megabyte) for erasing a region +COMP_BLOCK_WRITE_TIMEOUT_PER_MB = 3 # timeout (per megabyte) for writing compressed data +MEM_END_ROM_TIMEOUT = 0.05 # special short timeout for ESP_MEM_END, as it may never respond +DEFAULT_SERIAL_WRITE_TIMEOUT = 10 # timeout for serial port write +DEFAULT_CONNECT_ATTEMPTS = 7 # default number of times to try connection -class ESPROM(object): - # These are the currently known commands supported by the ROM +def timeout_per_mb(seconds_per_mb, size_bytes): + """ Scales timeouts which are size-specific """ + result = seconds_per_mb * (size_bytes / 1e6) + if result < DEFAULT_TIMEOUT: + return DEFAULT_TIMEOUT + return result + + +DETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB', + 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB'} + + +def check_supported_function(func, check_func): + """ + Decorator implementation that wraps a check around an ESPLoader + bootloader function to check if it's supported. + + This is used to capture the multidimensional differences in + functionality between the ESP8266 & ESP32/32S2/32S3/32C3 ROM loaders, and the + software stub that runs on both. Not possible to do this cleanly + via inheritance alone. + """ + def inner(*args, **kwargs): + obj = args[0] + if check_func(obj): + return func(*args, **kwargs) + else: + raise NotImplementedInROMError(obj, func) + return inner + + +def stub_function_only(func): + """ Attribute for a function only supported in the software stub loader """ + return check_supported_function(func, lambda o: o.IS_STUB) + + +def stub_and_esp32_function_only(func): + """ Attribute for a function only supported by software stubs or ESP32/32S2/32S3/32C3 ROM """ + return check_supported_function(func, lambda o: o.IS_STUB or isinstance(o, ESP32ROM)) + + +PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3 + +# Function to return nth byte of a bitstring +# Different behaviour on Python 2 vs 3 +if PYTHON2: + def byte(bitstr, index): + return ord(bitstr[index]) +else: + def byte(bitstr, index): + return bitstr[index] + +# Provide a 'basestring' class on Python 3 +try: + basestring +except NameError: + basestring = str + + +def print_overwrite(message, last_line=False): + """ Print a message, overwriting the currently printed line. + + If last_line is False, don't append a newline at the end (expecting another subsequent call will overwrite this one.) + + After a sequence of calls with last_line=False, call once with last_line=True. + + If output is not a TTY (for example redirected a pipe), no overwriting happens and this function is the same as print(). + """ + if sys.stdout.isatty(): + print("\r%s" % message, end='\n' if last_line else '') + else: + print(message) + + +def _mask_to_shift(mask): + """ Return the index of the least significant bit in the mask """ + shift = 0 + while mask & 0x1 == 0: + shift += 1 + mask >>= 1 + return shift + + +def esp8266_function_only(func): + """ Attribute for a function only supported on ESP8266 """ + return check_supported_function(func, lambda o: o.CHIP_NAME == "ESP8266") + + +class ESPLoader(object): + """ Base class providing access to ESP ROM & software stub bootloaders. + Subclasses provide ESP8266 & ESP32 specific functionality. + + Don't instantiate this base class directly, either instantiate a subclass or + call ESPLoader.detect_chip() which will interrogate the chip and return the + appropriate subclass instance. + + """ + CHIP_NAME = "Espressif device" + IS_STUB = False + + DEFAULT_PORT = "/dev/ttyUSB0" + + # Commands supported by ESP8266 ROM bootloader ESP_FLASH_BEGIN = 0x02 ESP_FLASH_DATA = 0x03 ESP_FLASH_END = 0x04 @@ -46,9 +199,35 @@ class ESPROM(object): ESP_WRITE_REG = 0x09 ESP_READ_REG = 0x0a + # Some comands supported by ESP32 ROM bootloader (or -8266 w/ stub) + ESP_SPI_SET_PARAMS = 0x0B + ESP_SPI_ATTACH = 0x0D + ESP_READ_FLASH_SLOW = 0x0e # ROM only, much slower than the stub flash read + ESP_CHANGE_BAUDRATE = 0x0F + ESP_FLASH_DEFL_BEGIN = 0x10 + ESP_FLASH_DEFL_DATA = 0x11 + ESP_FLASH_DEFL_END = 0x12 + ESP_SPI_FLASH_MD5 = 0x13 + + # Commands supported by ESP32-S2/S3/C3 ROM bootloader only + ESP_GET_SECURITY_INFO = 0x14 + + # Some commands supported by stub only + ESP_ERASE_FLASH = 0xD0 + ESP_ERASE_REGION = 0xD1 + ESP_READ_FLASH = 0xD2 + ESP_RUN_USER_CODE = 0xD3 + + # Flash encryption encrypted data command + ESP_FLASH_ENCRYPT_DATA = 0xD4 + + # Response code(s) sent by ROM + ROM_INVALID_RECV_MSG = 0x05 # response if an invalid message is received + # Maximum block sized for RAM and Flash writes, respectively. ESP_RAM_BLOCK = 0x1800 - ESP_FLASH_BLOCK = 0x400 + + FLASH_WRITE_SIZE = 0x400 # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. ESP_ROM_BAUD = 115200 @@ -59,174 +238,444 @@ class ESPROM(object): # Initial state for the checksum routine ESP_CHECKSUM_MAGIC = 0xef - # OTP ROM addresses - ESP_OTP_MAC0 = 0x3ff00050 - ESP_OTP_MAC1 = 0x3ff00054 - ESP_OTP_MAC3 = 0x3ff0005c - # Flash sector size, minimum unit of erase. - ESP_FLASH_SECTOR = 0x1000 + FLASH_SECTOR_SIZE = 0x1000 - def __init__(self, port=0, baud=ESP_ROM_BAUD): - self._port = serial.Serial(port) - self._slip_reader = slip_reader(port) + UART_DATE_REG_ADDR = 0x60000078 + + CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000 # This ROM address has a different value on each chip model + + UART_CLKDIV_MASK = 0xFFFFF + + # Memory addresses + IROM_MAP_START = 0x40200000 + IROM_MAP_END = 0x40300000 + + # The number of bytes in the UART response that signify command status + STATUS_BYTES_LENGTH = 2 + + def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False): + """Base constructor for ESPLoader bootloader interaction + + Don't call this constructor, either instantiate ESP8266ROM + or ESP32ROM, or use ESPLoader.detect_chip(). + + This base class has all of the instance methods for bootloader + functionality supported across various chips & stub + loaders. Subclasses replace the functions they don't support + with ones which throw NotImplementedInROMError(). + + """ + self.secure_download_mode = False # flag is set to True if esptool detects the ROM is in Secure Download Mode + + if isinstance(port, basestring): + self._port = serial.serial_for_url(port) + else: + self._port = port + self._slip_reader = slip_reader(self._port, self.trace) # 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 + # https://github.com/espressif/esptool/issues/44#issuecomment-107094446 + self._set_port_baudrate(baud) + self._trace_enabled = trace_enabled + # set write timeout, to prevent esptool blocked at write forever. + try: + self._port.write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT + except NotImplementedError: + # no write timeout for RFC2217 ports + # need to set the property back to None or it will continue to fail + self._port.write_timeout = None + + def _set_port_baudrate(self, baud): + try: + self._port.baudrate = baud + except IOError: + raise FatalError("Failed to set baud rate %d. The driver may not support this rate." % baud) + + @staticmethod + def detect_chip(port=DEFAULT_PORT, baud=ESP_ROM_BAUD, connect_mode='default_reset', trace_enabled=False, + connect_attempts=DEFAULT_CONNECT_ATTEMPTS): + """ Use serial access to detect the chip type. + + We use the UART's datecode register for this, it's mapped at + the same address on ESP8266 & ESP32 so we can use one + memory read and compare to the datecode register for each chip + type. + + This routine automatically performs ESPLoader.connect() (passing + connect_mode parameter) as part of querying the chip. + """ + detect_port = ESPLoader(port, baud, trace_enabled=trace_enabled) + detect_port.connect(connect_mode, connect_attempts, detecting=True) + try: + print('Detecting chip type...', end='') + sys.stdout.flush() + chip_magic_value = detect_port.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR) + + for cls in [ESP8266ROM, ESP32ROM, ESP32S2ROM, ESP32S3BETA2ROM, ESP32C3ROM]: + if chip_magic_value == cls.CHIP_DETECT_MAGIC_VALUE: + # don't connect a second time + inst = cls(detect_port._port, baud, trace_enabled=trace_enabled) + inst._post_connect() + print(' %s' % inst.CHIP_NAME, end='') + return inst + except UnsupportedCommandError: + raise FatalError("Unsupported Command Error received. Probably this means Secure Download Mode is enabled, " + "autodetection will not work. Need to manually specify the chip.") + finally: + print('') # end line + raise FatalError("Unexpected CHIP magic value 0x%08x. Failed to autodetect chip type." % (chip_magic_value)) """ Read a SLIP packet from the serial port """ def read(self): - return self._slip_reader.next() + return next(self._slip_reader) """ 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 = b'\xc0' \ + + (packet.replace(b'\xdb', b'\xdb\xdd').replace(b'\xc0', b'\xdb\xdc')) \ + + b'\xc0' + self.trace("Write %d bytes: %s", len(buf), HexFormatter(buf)) self._port.write(buf) + def trace(self, message, *format_args): + if self._trace_enabled: + now = time.time() + try: + + delta = now - self._last_trace + except AttributeError: + delta = 0.0 + self._last_trace = now + prefix = "TRACE +%.3f " % delta + print(prefix + (message % format_args)) + """ 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) + if type(b) is int: # python 2/3 compat + state ^= b + else: + state ^= ord(b) + return state """ Send a request and read the response """ - def command(self, op=None, data=None, chk=0): - if op is not None: - pkt = struct.pack(' self.STATUS_BYTES_LENGTH: + return data[:-self.STATUS_BYTES_LENGTH] + else: # otherwise, just return the 'val' field which comes from the reply header (this is used by read_reg) + return val + + def flush_input(self): + self._port.flushInput() + self._slip_reader = slip_reader(self._port, self.trace) + def sync(self): - self.command(ESPROM.ESP_SYNC, '\x07\x07\x12\x20' + 32 * '\x55') - for i in xrange(7): + self.command(self.ESP_SYNC, b'\x07\x07\x12\x20' + 32 * b'\x55', + timeout=SYNC_TIMEOUT) + for i in range(7): self.command() - """ Try connecting repeatedly until successful, or giving up """ - def connect(self): - print 'Connecting...' + def _setDTR(self, state): + self._port.setDTR(state) - for _ in xrange(4): - # issue reset-to-bootloader: - # RTS = either CH_PD or nRESET (both active low = chip in reset) - # DTR = GPIO0 (active low = boot to flasher) - self._port.setDTR(False) - self._port.setRTS(True) + def _setRTS(self, state): + self._port.setRTS(state) + # Work-around for adapters on Windows using the usbser.sys driver: + # generate a dummy change to DTR so that the set-control-line-state + # request is sent with the updated RTS state and the same DTR state + self._port.setDTR(self._port.dtr) + + def _connect_attempt(self, mode='default_reset', esp32r0_delay=False): + """ A single connection attempt, with esp32r0 workaround options """ + # esp32r0_delay is a workaround for bugs with the most common auto reset + # circuit and Windows, if the EN pin on the dev board does not have + # enough capacitance. + # + # Newer dev boards shouldn't have this problem (higher value capacitor + # on the EN pin), and ESP32 revision 1 can't use this workaround as it + # relies on a silicon bug. + # + # Details: https://github.com/espressif/esptool/issues/136 + last_error = None + + # If we're doing no_sync, we're likely communicating as a pass through + # with an intermediate device to the ESP32 + if mode == "no_reset_no_sync": + return last_error + + # issue reset-to-bootloader: + # RTS = either CH_PD/EN or nRESET (both active low = chip in reset + # DTR = GPIO0 (active low = boot to flasher) + # + # DTR & RTS are active low signals, + # ie True = pin @ 0V, False = pin @ VCC. + if mode != 'no_reset': + self._setDTR(False) # IO0=HIGH + self._setRTS(True) # EN=LOW, chip in reset + time.sleep(0.1) + if esp32r0_delay: + # Some chips are more likely to trigger the esp32r0 + # watchdog reset silicon bug if they're held with EN=LOW + # for a longer period + time.sleep(1.2) + self._setDTR(True) # IO0=LOW + self._setRTS(False) # EN=HIGH, chip out of reset + if esp32r0_delay: + # Sleep longer after reset. + # This workaround only works on revision 0 ESP32 chips, + # it exploits a silicon bug spurious watchdog reset. + time.sleep(0.4) # allow watchdog reset to occur time.sleep(0.05) - self._port.setDTR(True) - self._port.setRTS(False) - time.sleep(0.05) - self._port.setDTR(False) + self._setDTR(False) # IO0=HIGH, done - # worst-case latency timer should be 255ms (probably <20ms) - self._port.timeout = 0.3 - for _ in xrange(4): - try: - self._port.flushInput() - self._slip_reader = slip_reader(self._port) - self._port.flushOutput() - self.sync() - self._port.timeout = 5 - return - except: - time.sleep(0.05) - raise FatalError('Failed to connect to ESP8266') + for _ in range(5): + try: + self.flush_input() + self._port.flushOutput() + self.sync() + return None + except FatalError as e: + if esp32r0_delay: + print('_', end='') + else: + print('.', end='') + sys.stdout.flush() + time.sleep(0.05) + last_error = e + return last_error + + def connect(self, mode='default_reset', attempts=DEFAULT_CONNECT_ATTEMPTS, detecting=False): + """ Try connecting repeatedly until successful, or giving up """ + print('Connecting...', end='') + sys.stdout.flush() + last_error = None + + try: + for _ in range(attempts) if attempts > 0 else itertools.count(): + last_error = self._connect_attempt(mode=mode, esp32r0_delay=False) + if last_error is None: + break + last_error = self._connect_attempt(mode=mode, esp32r0_delay=True) + if last_error is None: + break + finally: + print('') # end 'Connecting...' line + + if last_error is not None: + raise FatalError('Failed to connect to %s: %s' % (self.CHIP_NAME, last_error)) + + if not detecting: + try: + # check the date code registers match what we expect to see + chip_magic_value = self.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR) + if chip_magic_value != self.CHIP_DETECT_MAGIC_VALUE: + actually = None + for cls in [ESP8266ROM, ESP32ROM, ESP32S2ROM, ESP32S3BETA2ROM, ESP32C3ROM]: + if chip_magic_value == cls.CHIP_DETECT_MAGIC_VALUE: + actually = cls + break + if actually is None: + print(("WARNING: This chip doesn't appear to be a %s (chip magic value 0x%08x). " + "Probably it is unsupported by this version of esptool.") % (self.CHIP_NAME, chip_magic_value)) + else: + raise FatalError("This chip is %s not %s. Wrong --chip argument?" % (actually.CHIP_NAME, self.CHIP_NAME)) + except UnsupportedCommandError: + self.secure_download_mode = True + self._post_connect() + + def _post_connect(self): + """ + Additional initialization hook, may be overridden by the chip-specific class. + Gets called after connect, and after auto-detection. + """ + pass - """ Read memory address in target """ def read_reg(self, addr): - res = self.command(ESPROM.ESP_READ_REG, struct.pack(' 0: + # add a dummy write to a date register as an excuse to have a delay + command += struct.pack(' start: + raise FatalError(("Software loader is resident at 0x%08x-0x%08x. " + "Can't load binary at overlapping address range 0x%08x-0x%08x. " + "Either change binary loading address, or use the --no-stub " + "option to disable the software loader.") % (start, end, load_start, load_end)) + + return self.check_command("enter RAM download mode", self.ESP_MEM_BEGIN, + struct.pack(' length: + raise FatalError('Read more than expected') + + digest_frame = self.read() + if len(digest_frame) != 16: + raise FatalError('Expected digest, got: %s' % hexify(digest_frame)) + expected_digest = hexify(digest_frame).upper() + digest = hashlib.md5(data).hexdigest().upper() + if digest != expected_digest: + raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) + return data + + def flash_spi_attach(self, hspi_arg): + """Send SPI attach command to enable the SPI flash pins + + ESP8266 ROM does this when you send flash_begin, ESP32 ROM + has it as a SPI command. + """ + # last 3 bytes in ESP_SPI_ATTACH argument are reserved values + arg = struct.pack(' 0: + self.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1) + if miso_bits > 0: + self.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1) + else: + + def set_data_lengths(mosi_bits, miso_bits): + SPI_DATA_LEN_REG = SPI_USR1_REG + SPI_MOSI_BITLEN_S = 17 + SPI_MISO_BITLEN_S = 8 + mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1) + miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1) + self.write_reg(SPI_DATA_LEN_REG, + (miso_mask << SPI_MISO_BITLEN_S) | ( + mosi_mask << SPI_MOSI_BITLEN_S)) + + # SPI peripheral "command" bitmasks for SPI_CMD_REG + SPI_CMD_USR = (1 << 18) + + # shift values + SPI_USR2_COMMAND_LEN_SHIFT = 28 + + if read_bits > 32: + raise FatalError("Reading more than 32 bits back from a SPI flash operation is unsupported") + if len(data) > 64: + raise FatalError("Writing more than 64 bytes of data with one SPI command is unsupported") + + data_bits = len(data) * 8 + old_spi_usr = self.read_reg(SPI_USR_REG) + old_spi_usr2 = self.read_reg(SPI_USR2_REG) + flags = SPI_USR_COMMAND + if read_bits > 0: + flags |= SPI_USR_MISO + if data_bits > 0: + flags |= SPI_USR_MOSI + set_data_lengths(data_bits, read_bits) + self.write_reg(SPI_USR_REG, flags) + self.write_reg(SPI_USR2_REG, + (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command) + if data_bits == 0: + self.write_reg(SPI_W0_REG, 0) # clear data register before we read it + else: + data = pad_to(data, 4, b'\00') # pad to 32-bit multiple + words = struct.unpack("I" * (len(data) // 4), data) + next_reg = SPI_W0_REG + for word in words: + self.write_reg(next_reg, word) + next_reg += 4 + self.write_reg(SPI_CMD_REG, SPI_CMD_USR) + + def wait_done(): + for _ in range(10): + if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0: + return + raise FatalError("SPI command did not complete in time") + wait_done() + + status = self.read_reg(SPI_W0_REG) + # restore some SPI controller registers + self.write_reg(SPI_USR_REG, old_spi_usr) + self.write_reg(SPI_USR2_REG, old_spi_usr2) + return status + + def read_status(self, num_bytes=2): + """Read up to 24 bits (num_bytes) of SPI flash status register contents + via RDSR, RDSR2, RDSR3 commands + + Not all SPI flash supports all three commands. The upper 1 or 2 + bytes may be 0xFF. + """ + SPIFLASH_RDSR = 0x05 + SPIFLASH_RDSR2 = 0x35 + SPIFLASH_RDSR3 = 0x15 + + status = 0 + shift = 0 + for cmd in [SPIFLASH_RDSR, SPIFLASH_RDSR2, SPIFLASH_RDSR3][0:num_bytes]: + status += self.run_spiflash_command(cmd, read_bits=8) << shift + shift += 8 + return status + + def write_status(self, new_status, num_bytes=2, set_non_volatile=False): + """Write up to 24 bits (num_bytes) of new status register + + num_bytes can be 1, 2 or 3. + + Not all flash supports the additional commands to write the + second and third byte of the status register. When writing 2 + bytes, esptool also sends a 16-byte WRSR command (as some + flash types use this instead of WRSR2.) + + If the set_non_volatile flag is set, non-volatile bits will + be set as well as volatile ones (WREN used instead of WEVSR). + + """ + SPIFLASH_WRSR = 0x01 + SPIFLASH_WRSR2 = 0x31 + SPIFLASH_WRSR3 = 0x11 + SPIFLASH_WEVSR = 0x50 + SPIFLASH_WREN = 0x06 + SPIFLASH_WRDI = 0x04 + + enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR + + # try using a 16-bit WRSR (not supported by all chips) + # this may be redundant, but shouldn't hurt + if num_bytes == 2: + self.run_spiflash_command(enable_cmd) + self.run_spiflash_command(SPIFLASH_WRSR, struct.pack(">= 8 + + self.run_spiflash_command(SPIFLASH_WRDI) + + def get_crystal_freq(self): + # Figure out the crystal frequency from the UART clock divider + # Returns a normalized value in integer MHz (40 or 26 are the only supported values) + # + # The logic here is: + # - We know that our baud rate and the ESP UART baud rate are roughly the same, or we couldn't communicate + # - We can read the UART clock divider register to know how the ESP derives this from the APB bus frequency + # - Multiplying these two together gives us the bus frequency which is either the crystal frequency (ESP32) + # or double the crystal frequency (ESP8266). See the self.XTAL_CLK_DIVIDER parameter for this factor. + uart_div = self.read_reg(self.UART_CLKDIV_REG) & self.UART_CLKDIV_MASK + est_xtal = (self._port.baudrate * uart_div) / 1e6 / self.XTAL_CLK_DIVIDER + norm_xtal = 40 if est_xtal > 33 else 26 + if abs(norm_xtal - est_xtal) > 1: + print("WARNING: Detected crystal freq %.2fMHz is quite different to normalized freq %dMHz. Unsupported crystal in use?" % (est_xtal, norm_xtal)) + return norm_xtal + + def hard_reset(self): + self._setRTS(True) # EN->LOW + time.sleep(0.1) + self._setRTS(False) + + def soft_reset(self, stay_in_bootloader): + if not self.IS_STUB: + if stay_in_bootloader: + return # ROM bootloader is already in bootloader! + else: + # 'run user code' is as close to a soft reset as we can do + self.flash_begin(0, 0) + self.flash_finish(False) + else: + if stay_in_bootloader: + # soft resetting from the stub loader + # will re-load the ROM bootloader + self.flash_begin(0, 0) + self.flash_finish(True) + elif self.CHIP_NAME != "ESP8266": + raise FatalError("Soft resetting is currently only supported on ESP8266") + else: + # running user code from stub loader requires some hacks + # in the stub loader + self.command(self.ESP_RUN_USER_CODE, wait_response=False) + + +class ESP8266ROM(ESPLoader): + """ Access class for ESP8266 ROM bootloader + """ + CHIP_NAME = "ESP8266" + IS_STUB = False + + CHIP_DETECT_MAGIC_VALUE = 0xfff0c101 + + # OTP ROM addresses + ESP_OTP_MAC0 = 0x3ff00050 + ESP_OTP_MAC1 = 0x3ff00054 + ESP_OTP_MAC3 = 0x3ff0005c + + SPI_REG_BASE = 0x60000200 + SPI_USR_OFFS = 0x1c + SPI_USR1_OFFS = 0x20 + SPI_USR2_OFFS = 0x24 + SPI_MOSI_DLEN_OFFS = None + SPI_MISO_DLEN_OFFS = None + SPI_W0_OFFS = 0x40 + + UART_CLKDIV_REG = 0x60000014 + + XTAL_CLK_DIVIDER = 2 + + FLASH_SIZES = { + '512KB': 0x00, + '256KB': 0x10, + '1MB': 0x20, + '2MB': 0x30, + '4MB': 0x40, + '2MB-c1': 0x50, + '4MB-c1': 0x60, + '8MB': 0x80, + '16MB': 0x90, + } + + BOOTLOADER_FLASH_OFFSET = 0 + + MEMORY_MAP = [[0x3FF00000, 0x3FF00010, "DPORT"], + [0x3FFE8000, 0x40000000, "DRAM"], + [0x40100000, 0x40108000, "IRAM"], + [0x40201010, 0x402E1010, "IROM"]] + + def get_efuses(self): + # Return the 128 bits of ESP8266 efuse as a single Python integer + result = self.read_reg(0x3ff0005c) << 96 + result |= self.read_reg(0x3ff00058) << 64 + result |= self.read_reg(0x3ff00054) << 32 + result |= self.read_reg(0x3ff00050) + return result + + def get_chip_description(self): + efuses = self.get_efuses() + is_8285 = (efuses & ((1 << 4) | 1 << 80)) != 0 # One or the other efuse bit is set for ESP8285 + return "ESP8285" if is_8285 else "ESP8266EX" + + def get_chip_features(self): + features = ["WiFi"] + if self.get_chip_description() == "ESP8285": + features += ["Embedded Flash"] + return features + + def flash_spi_attach(self, hspi_arg): + if self.IS_STUB: + super(ESP8266ROM, self).flash_spi_attach(hspi_arg) + else: + # ESP8266 ROM has no flash_spi_attach command in serial protocol, + # but flash_begin will do it + self.flash_begin(0, 0) + + def flash_set_parameters(self, size): + # not implemented in ROM, but OK to silently skip for ROM + if self.IS_STUB: + super(ESP8266ROM, self).flash_set_parameters(size) + + def chip_id(self): + """ Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function """ + id0 = self.read_reg(self.ESP_OTP_MAC0) + id1 = self.read_reg(self.ESP_OTP_MAC1) + return (id0 >> 24) | ((id1 & MAX_UINT24) << 8) + def read_mac(self): + """ Read MAC from OTP ROM """ mac0 = self.read_reg(self.ESP_OTP_MAC0) mac1 = self.read_reg(self.ESP_OTP_MAC1) mac3 = self.read_reg(self.ESP_OTP_MAC3) @@ -249,72 +1176,740 @@ class ESPROM(object): 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 get_erase_size(self, offset, size): + """ Calculate an erase size given a specific size in bytes. + + Provides a workaround for the bootloader erase bug.""" + + sectors_per_block = 16 + sector_size = self.FLASH_SECTOR_SIZE + num_sectors = (size + sector_size - 1) // sector_size + start_sector = offset // sector_size + + head_sectors = sectors_per_block - (start_sector % sectors_per_block) + if num_sectors < head_sectors: + head_sectors = num_sectors + + if num_sectors < 2 * head_sectors: + return (num_sectors + 1) // 2 * sector_size + else: + return (num_sectors - head_sectors) * sector_size + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError("Overriding VDDSDIO setting only applies to ESP32") + + +class ESP8266StubLoader(ESP8266ROM): + """ Access class for ESP8266 stub loader, runs on top of ROM. + """ + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + def get_erase_size(self, offset, size): + return size # stub doesn't have same size bug as ROM loader + + +ESP8266ROM.STUB_CLASS = ESP8266StubLoader + + +class ESP32ROM(ESPLoader): + """Access class for ESP32 ROM bootloader + + """ + CHIP_NAME = "ESP32" + IMAGE_CHIP_ID = 0 + IS_STUB = False + + CHIP_DETECT_MAGIC_VALUE = 0x00f01d83 + + IROM_MAP_START = 0x400d0000 + IROM_MAP_END = 0x40400000 + + DROM_MAP_START = 0x3F400000 + DROM_MAP_END = 0x3F800000 + + # ESP32 uses a 4 byte status reply + STATUS_BYTES_LENGTH = 4 + + SPI_REG_BASE = 0x3ff42000 + SPI_USR_OFFS = 0x1c + SPI_USR1_OFFS = 0x20 + SPI_USR2_OFFS = 0x24 + SPI_MOSI_DLEN_OFFS = 0x28 + SPI_MISO_DLEN_OFFS = 0x2c + EFUSE_RD_REG_BASE = 0x3ff5a000 + + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + 0x18 + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = (1 << 7) # EFUSE_RD_DISABLE_DL_ENCRYPT + + DR_REG_SYSCON_BASE = 0x3ff66000 + + SPI_W0_OFFS = 0x80 + + UART_CLKDIV_REG = 0x3ff40014 + + XTAL_CLK_DIVIDER = 1 + + FLASH_SIZES = { + '1MB': 0x00, + '2MB': 0x10, + '4MB': 0x20, + '8MB': 0x30, + '16MB': 0x40 + } + + BOOTLOADER_FLASH_OFFSET = 0x1000 + + OVERRIDE_VDDSDIO_CHOICES = ["1.8V", "1.9V", "OFF"] + + MEMORY_MAP = [[0x00000000, 0x00010000, "PADDING"], + [0x3F400000, 0x3F800000, "DROM"], + [0x3F800000, 0x3FC00000, "EXTRAM_DATA"], + [0x3FF80000, 0x3FF82000, "RTC_DRAM"], + [0x3FF90000, 0x40000000, "BYTE_ACCESSIBLE"], + [0x3FFAE000, 0x40000000, "DRAM"], + [0x3FFE0000, 0x3FFFFFFC, "DIRAM_DRAM"], + [0x40000000, 0x40070000, "IROM"], + [0x40070000, 0x40078000, "CACHE_PRO"], + [0x40078000, 0x40080000, "CACHE_APP"], + [0x40080000, 0x400A0000, "IRAM"], + [0x400A0000, 0x400BFFFC, "DIRAM_IRAM"], + [0x400C0000, 0x400C2000, "RTC_IRAM"], + [0x400D0000, 0x40400000, "IROM"], + [0x50000000, 0x50002000, "RTC_DATA"]] + + FLASH_ENCRYPTED_WRITE_ALIGN = 32 + + """ Try to read the BLOCK1 (encryption key) and check if it is valid """ + + def is_flash_encryption_key_valid(self): + + """ Bit 0 of efuse_rd_disable[3:0] is mapped to BLOCK1 + this bit is at position 16 in EFUSE_BLK0_RDATA0_REG """ + word0 = self.read_efuse(0) + rd_disable = (word0 >> 16) & 0x1 + + # reading of BLOCK1 is NOT ALLOWED so we assume valid key is programmed + if rd_disable: + return True + else: + # reading of BLOCK1 is ALLOWED so we will read and verify for non-zero. + # When ESP32 has not generated AES/encryption key in BLOCK1, the contents will be readable and 0. + # If the flash encryption is enabled it is expected to have a valid non-zero key. We break out on + # first occurance of non-zero value + key_word = [0] * 7 + for i in range(len(key_word)): + key_word[i] = self.read_efuse(14 + i) + # key is non-zero so break & return + if key_word[i] != 0: + return True + return False + + def get_flash_crypt_config(self): + """ For flash encryption related commands we need to make sure + user has programmed all the relevant efuse correctly so before + writing encrypted write_flash_encrypt esptool will verify the values + of flash_crypt_config to be non zero if they are not read + protected. If the values are zero a warning will be printed + + bit 3 in efuse_rd_disable[3:0] is mapped to flash_crypt_config + this bit is at position 19 in EFUSE_BLK0_RDATA0_REG """ + word0 = self.read_efuse(0) + rd_disable = (word0 >> 19) & 0x1 + + if rd_disable == 0: + """ we can read the flash_crypt_config efuse value + so go & read it (EFUSE_BLK0_RDATA5_REG[31:28]) """ + word5 = self.read_efuse(5) + word5 = (word5 >> 28) & 0xF + return word5 + else: + # if read of the efuse is disabled we assume it is set correctly + return 0xF + + def get_encrypted_download_disabled(self): + if self.read_reg(self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG) & self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT: + return True + else: + return False + + def get_pkg_version(self): + word3 = self.read_efuse(3) + pkg_version = (word3 >> 9) & 0x07 + pkg_version += ((word3 >> 2) & 0x1) << 3 + return pkg_version + + def get_chip_revision(self): + word3 = self.read_efuse(3) + word5 = self.read_efuse(5) + apb_ctl_date = self.read_reg(self.DR_REG_SYSCON_BASE + 0x7C) + + rev_bit0 = (word3 >> 15) & 0x1 + rev_bit1 = (word5 >> 20) & 0x1 + rev_bit2 = (apb_ctl_date >> 31) & 0x1 + if rev_bit0: + if rev_bit1: + if rev_bit2: + return 3 + else: + return 2 + else: + return 1 + return 0 + + def get_chip_description(self): + pkg_version = self.get_pkg_version() + chip_revision = self.get_chip_revision() + rev3 = (chip_revision == 3) + single_core = self.read_efuse(3) & (1 << 0) # CHIP_VER DIS_APP_CPU + + chip_name = { + 0: "ESP32-S0WDQ6" if single_core else "ESP32-D0WDQ6", + 1: "ESP32-S0WD" if single_core else "ESP32-D0WD", + 2: "ESP32-D2WD", + 4: "ESP32-U4WDH", + 5: "ESP32-PICO-V3" if rev3 else "ESP32-PICO-D4", + 6: "ESP32-PICO-V3-02", + }.get(pkg_version, "unknown ESP32") + + # ESP32-D0WD-V3, ESP32-D0WDQ6-V3 + if chip_name.startswith("ESP32-D0WD") and rev3: + chip_name += "-V3" + + return "%s (revision %d)" % (chip_name, chip_revision) + + def get_chip_features(self): + features = ["WiFi"] + word3 = self.read_efuse(3) + + # names of variables in this section are lowercase + # versions of EFUSE names as documented in TRM and + # ESP-IDF efuse_reg.h + + chip_ver_dis_bt = word3 & (1 << 1) + if chip_ver_dis_bt == 0: + features += ["BT"] + + chip_ver_dis_app_cpu = word3 & (1 << 0) + if chip_ver_dis_app_cpu: + features += ["Single Core"] + else: + features += ["Dual Core"] + + chip_cpu_freq_rated = word3 & (1 << 13) + if chip_cpu_freq_rated: + chip_cpu_freq_low = word3 & (1 << 12) + if chip_cpu_freq_low: + features += ["160MHz"] + else: + features += ["240MHz"] + + pkg_version = self.get_pkg_version() + if pkg_version in [2, 4, 5, 6]: + features += ["Embedded Flash"] + + if pkg_version == 6: + features += ["Embedded PSRAM"] + + word4 = self.read_efuse(4) + adc_vref = (word4 >> 8) & 0x1F + if adc_vref: + features += ["VRef calibration in efuse"] + + blk3_part_res = word3 >> 14 & 0x1 + if blk3_part_res: + features += ["BLK3 partially reserved"] + + word6 = self.read_efuse(6) + coding_scheme = word6 & 0x3 + features += ["Coding Scheme %s" % { + 0: "None", + 1: "3/4", + 2: "Repeat (UNSUPPORTED)", + 3: "Invalid"}[coding_scheme]] + + return features + + def read_efuse(self, n): + """ Read the nth word of the ESP3x EFUSE region. """ + return self.read_reg(self.EFUSE_RD_REG_BASE + (4 * n)) + 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) + raise NotSupportedError(self, "chip_id") - """ 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 = self.read_reg(0x60000240) - self.flash_finish(False) - return flash_id + def read_mac(self): + """ Read MAC from EFUSE region """ + words = [self.read_efuse(2), self.read_efuse(1)] + bitstring = struct.pack(">II", *words) + bitstring = bitstring[2:8] # trim the 2 byte CRC + try: + return tuple(ord(b) for b in bitstring) + except TypeError: # Python 3, bitstring elements are already bytes + return tuple(bitstring) - """ Abuse the loader protocol to force flash to be left in write mode """ - def flash_unlock_dio(self): - # Enable flash write mode - self.flash_begin(0, 0) - # Reset the chip rather than call flash_finish(), which would have - # write protected the chip again (why oh why does it do that?!) - self.mem_begin(0,0,0,0x40100000) - self.mem_finish(0x40000080) + def get_erase_size(self, offset, size): + return size - """ Perform a chip erase of SPI flash """ - def flash_erase(self): - # Trick ROM to initialize SFlash - self.flash_begin(0, 0) + def override_vddsdio(self, new_voltage): + new_voltage = new_voltage.upper() + if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES: + raise FatalError("The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'") + RTC_CNTL_SDIO_CONF_REG = 0x3ff48074 + RTC_CNTL_XPD_SDIO_REG = (1 << 31) + RTC_CNTL_DREFH_SDIO_M = (3 << 29) + RTC_CNTL_DREFM_SDIO_M = (3 << 27) + RTC_CNTL_DREFL_SDIO_M = (3 << 25) + # RTC_CNTL_SDIO_TIEH = (1 << 23) # not used here, setting TIEH=1 would set 3.3V output, not safe for esptool.py to do + RTC_CNTL_SDIO_FORCE = (1 << 22) + RTC_CNTL_SDIO_PD_EN = (1 << 21) - # This is hacky: we don't have a custom stub, instead we trick - # the bootloader to jump to the SPIEraseChip() routine and then halt/crash - # when it tries to boot an unconfigured system. - self.mem_begin(0,0,0,0x40100000) - self.mem_finish(0x40004984) + reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting + reg_val |= RTC_CNTL_SDIO_PD_EN + if new_voltage != "OFF": + reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO + if new_voltage == "1.9V": + reg_val |= (RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M) # boost voltage + self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val) + print("VDDSDIO regulator set to %s" % new_voltage) - # Yup - there's no good way to detect if we succeeded. - # It it on the other hand unlikely to fail. + def read_flash_slow(self, offset, length, progress_fn): + BLOCK_LEN = 64 # ROM read limit per command (this limit is why it's so slow) - def run_stub(self, stub, params, read_output=True): - stub = dict(stub) - stub['code'] = unhexify(stub['code']) - if 'data' in stub: - stub['data'] = unhexify(stub['data']) + data = b'' + while len(data) < length: + block_len = min(BLOCK_LEN, length - len(data)) + r = self.check_command("read flash block", self.ESP_READ_FLASH_SLOW, + struct.pack('> 21) & 0x0F + return pkg_version + + def get_chip_description(self): + chip_name = { + 0: "ESP32-S2", + 1: "ESP32-S2FH16", + 2: "ESP32-S2FH32", + }.get(self.get_pkg_version(), "unknown ESP32-S2") + + return "%s" % (chip_name) + + def get_chip_features(self): + features = ["WiFi"] + + if self.secure_download_mode: + features += ["Secure Download Mode Enabled"] + + pkg_version = self.get_pkg_version() + + if pkg_version in [1, 2]: + if pkg_version == 1: + features += ["Embedded 2MB Flash"] + elif pkg_version == 2: + features += ["Embedded 4MB Flash"] + features += ["105C temp rating"] + + num_word = 4 + block2_addr = self.EFUSE_BASE + 0x05C + word4 = self.read_reg(block2_addr + (4 * num_word)) + block2_version = (word4 >> 4) & 0x07 + + if block2_version == 1: + features += ["ADC and temperature sensor calibration in BLK2 of efuse"] + return features + + def get_crystal_freq(self): + # ESP32-S2 XTAL is fixed to 40MHz + return 40 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError("VDD_SDIO overrides are not supported for ESP32-S2") + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + try: + return tuple(ord(b) for b in bitstring) + except TypeError: # Python 3, bitstring elements are already bytes + return tuple(bitstring) + + def get_flash_crypt_config(self): + return None # doesn't exist on ESP32-S2 + + def get_key_block_purpose(self, key_block): + if key_block < 0 or key_block > 5: + raise FatalError("Valid key block numbers must be in range 0-5") + + reg, shift = [(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT), + (self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT), + (self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT), + (self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT), + (self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT), + (self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT)][key_block] + return (self.read_reg(reg) >> shift) & 0xF + + def is_flash_encryption_key_valid(self): + # Need to see either an AES-128 key or two AES-256 keys + purposes = [self.get_key_block_purpose(b) for b in range(6)] + + if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes): + return True + + return any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) \ + and any(p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes) + + def uses_usb(self, _cache=[]): + if self.secure_download_mode: + return False # can't detect native USB in secure download mode + if not _cache: + buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff + _cache.append(buf_no == self.UARTDEV_BUF_NO_USB) + return _cache[0] + + def _post_connect(self): + if self.uses_usb(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + + def _check_if_can_reset(self): + """ + Check the strapping register to see if we can reset out of download mode. + """ + if os.getenv("ESPTOOL_TESTING") is not None: + print("ESPTOOL_TESTING is set, ignoring strapping mode check") + # Esptool tests over USB CDC run with GPIO0 strapped low, don't complain in this case. + return + strap_reg = self.read_reg(self.GPIO_STRAP_REG) + force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) + if strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0: + print("ERROR: {} chip was placed into download mode using GPIO0.\n" + "esptool.py can not exit the download mode over USB. " + "To run the app, reset the chip manually.\n" + "To suppress this error, set --after option to 'no_reset'.".format(self.get_chip_description())) + raise SystemExit(1) + + def hard_reset(self): + if self.uses_usb(): + self._check_if_can_reset() + + self._setRTS(True) # EN->LOW + if self.uses_usb(): + # Give the chip some time to come out of reset, to be able to handle further DTR/RTS transitions + time.sleep(0.2) + self._setRTS(False) + time.sleep(0.2) + else: + self._setRTS(False) + + +class ESP32S3BETA2ROM(ESP32ROM): + CHIP_NAME = "ESP32-S3(beta2)" + IMAGE_CHIP_ID = 4 + + IROM_MAP_START = 0x42000000 + IROM_MAP_END = 0x44000000 + DROM_MAP_START = 0x3c000000 + DROM_MAP_END = 0x3e000000 + + UART_DATE_REG_ADDR = 0x60000080 + + CHIP_DETECT_MAGIC_VALUE = 0xeb004136 + + SPI_REG_BASE = 0x60002000 + SPI_USR_OFFS = 0x18 + SPI_USR1_OFFS = 0x1c + SPI_USR2_OFFS = 0x20 + SPI_MOSI_DLEN_OFFS = 0x24 + SPI_MISO_DLEN_OFFS = 0x28 + SPI_W0_OFFS = 0x58 + + EFUSE_REG_BASE = 0x6001A030 # BLOCK0 read base address + + MAC_EFUSE_REG = 0x6001A000 # ESP32S3 has special block for MAC efuses + + UART_CLKDIV_REG = 0x60000014 + + GPIO_STRAP_REG = 0x60004038 + + MEMORY_MAP = [[0x00000000, 0x00010000, "PADDING"], + [0x3C000000, 0x3D000000, "DROM"], + [0x3D000000, 0x3E000000, "EXTRAM_DATA"], + [0x600FE000, 0x60100000, "RTC_DRAM"], + [0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"], + [0x3FC88000, 0x403E2000, "MEM_INTERNAL"], + [0x3FC88000, 0x3FD00000, "DRAM"], + [0x40000000, 0x4001A100, "IROM_MASK"], + [0x40370000, 0x403E0000, "IRAM"], + [0x600FE000, 0x60100000, "RTC_IRAM"], + [0x42000000, 0x42800000, "IROM"], + [0x50000000, 0x50002000, "RTC_DATA"]] + + def get_chip_description(self): + return "ESP32-S3(beta2)" + + def get_chip_features(self): + return ["WiFi", "BLE"] + + def get_crystal_freq(self): + # ESP32S3 XTAL is fixed to 40MHz + return 40 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError("VDD_SDIO overrides are not supported for ESP32-S3") + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + try: + return tuple(ord(b) for b in bitstring) + except TypeError: # Python 3, bitstring elements are already bytes + return tuple(bitstring) + + +class ESP32C3ROM(ESP32ROM): + CHIP_NAME = "ESP32-C3" + IMAGE_CHIP_ID = 5 + + IROM_MAP_START = 0x42000000 + IROM_MAP_END = 0x42800000 + DROM_MAP_START = 0x3c000000 + DROM_MAP_END = 0x3c800000 + + SPI_REG_BASE = 0x3f402000 + SPI_USR_OFFS = 0x18 + SPI_USR1_OFFS = 0x1c + SPI_USR2_OFFS = 0x20 + SPI_MOSI_DLEN_OFFS = 0x24 + SPI_MISO_DLEN_OFFS = 0x28 + SPI_W0_OFFS = 0xA8 + + BOOTLOADER_FLASH_OFFSET = 0x0 + + CHIP_DETECT_MAGIC_VALUE = 0x6921506f + + UART_DATE_REG_ADDR = 0x60000000 + 0x7c + + EFUSE_BASE = 0x60008800 + MAC_EFUSE_REG = EFUSE_BASE + 0x044 + + GPIO_STRAP_REG = 0x3f404038 + + FLASH_ENCRYPTED_WRITE_ALIGN = 16 + + MEMORY_MAP = [[0x00000000, 0x00010000, "PADDING"], + [0x3C000000, 0x3C800000, "DROM"], + [0x3FC80000, 0x3FCE0000, "DRAM"], + [0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"], + [0x3FF00000, 0x3FF20000, "DROM_MASK"], + [0x40000000, 0x40060000, "IROM_MASK"], + [0x42000000, 0x42800000, "IROM"], + [0x4037C000, 0x403E0000, "IRAM"], + [0x50000000, 0x50002000, "RTC_IRAM"], + [0x50000000, 0x50002000, "RTC_DRAM"], + [0x600FE000, 0x60100000, "MEM_INTERNAL2"]] + + def get_pkg_version(self): + num_word = 3 + block1_addr = self.EFUSE_BASE + 0x044 + word3 = self.read_reg(block1_addr + (4 * num_word)) + pkg_version = (word3 >> 21) & 0x0F + return pkg_version + + def get_chip_description(self): + chip_name = { + 0: "ESP32-C3", + }.get(self.get_pkg_version(), "unknown ESP32-C3") + + return "%s" % (chip_name) + + def get_chip_features(self): + return ["Wi-Fi"] + + def get_crystal_freq(self): + # ESP32C3 XTAL is fixed to 40MHz + return 40 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError("VDD_SDIO overrides are not supported for ESP32-C3") + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + try: + return tuple(ord(b) for b in bitstring) + except TypeError: # Python 3, bitstring elements are already bytes + return tuple(bitstring) + + +class ESP32StubLoader(ESP32ROM): + """ Access class for ESP32 stub loader, runs on top of ROM. + """ + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + +ESP32ROM.STUB_CLASS = ESP32StubLoader + + +class ESP32S2StubLoader(ESP32S2ROM): + """ Access class for ESP32-S2 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + if rom_loader.uses_usb(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK + + +ESP32S2ROM.STUB_CLASS = ESP32S2StubLoader + + +class ESP32S3BETA2StubLoader(ESP32S3BETA2ROM): + """ Access class for ESP32S3 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + +ESP32S3BETA2ROM.STUB_CLASS = ESP32S3BETA2StubLoader + + +class ESP32C3StubLoader(ESP32C3ROM): + """ Access class for ESP32C3 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + +ESP32C3ROM.STUB_CLASS = ESP32C3StubLoader class ESPBOOTLOADER(object): @@ -327,125 +1922,274 @@ class ESPBOOTLOADER(object): IMAGE_V2_SEGMENT = 4 -def LoadFirmwareImage(filename): - """ Load a firmware image, without knowing what kind of file (v1 or v2) it is. +def LoadFirmwareImage(chip, filename): + """ Load a firmware image. Can be for any supported SoC. - Returns a BaseFirmwareImage subclass, either ESPFirmwareImage (v1) or OTAFirmwareImage (v2). + ESP8266 images will be examined to determine if they are original ROM firmware images (ESP8266ROMFirmwareImage) + or "v2" OTA bootloader images. + + Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1) or ESP8266V2FirmwareImage (v2). """ + chip = chip.lower().replace("-", "") with open(filename, 'rb') as f: - magic = ord(f.read(1)) - f.seek(0) - if magic == ESPROM.ESP_IMAGE_MAGIC: - return ESPFirmwareImage(f) - elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC: - return OTAFirmwareImage(f) - else: - raise FatalError("Invalid image magic number: %d" % magic) + if chip == 'esp32': + return ESP32FirmwareImage(f) + elif chip == "esp32s2": + return ESP32S2FirmwareImage(f) + elif chip == "esp32s3beta2": + return ESP32S3BETA2FirmwareImage(f) + elif chip == 'esp32c3': + return ESP32C3FirmwareImage(f) + else: # Otherwise, ESP8266 so look at magic to determine the image type + magic = ord(f.read(1)) + f.seek(0) + if magic == ESPLoader.ESP_IMAGE_MAGIC: + return ESP8266ROMFirmwareImage(f) + elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC: + return ESP8266V2FirmwareImage(f) + else: + raise FatalError("Invalid image magic number: %d" % magic) + + +class ImageSegment(object): + """ Wrapper class for a segment in an ESP image + (very similar to a section in an ELFImage also) """ + def __init__(self, addr, data, file_offs=None): + self.addr = addr + self.data = data + self.file_offs = file_offs + self.include_in_checksum = True + if self.addr != 0: + self.pad_to_alignment(4) # pad all "real" ImageSegments 4 byte aligned length + + def copy_with_new_addr(self, new_addr): + """ Return a new ImageSegment with same data, but mapped at + a new address. """ + return ImageSegment(new_addr, self.data, 0) + + def split_image(self, split_len): + """ Return a new ImageSegment which splits "split_len" bytes + from the beginning of the data. Remaining bytes are kept in + this segment object (and the start address is adjusted to match.) """ + result = copy.copy(self) + result.data = self.data[:split_len] + self.data = self.data[split_len:] + self.addr += split_len + self.file_offs = None + result.file_offs = None + return result + + def __repr__(self): + r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr) + if self.file_offs is not None: + r += " file_offs 0x%08x" % (self.file_offs) + return r + + def pad_to_alignment(self, alignment): + self.data = pad_to(self.data, alignment, b'\x00') + + +class ELFSection(ImageSegment): + """ Wrapper class for a section in an ELF image, has a section + name as well as the common properties of an ImageSegment. """ + def __init__(self, name, addr, data): + super(ELFSection, self).__init__(addr, data) + self.name = name.decode("utf-8") + + def __repr__(self): + return "%s %s" % (self.name, super(ELFSection, self).__repr__()) class BaseFirmwareImage(object): + SEG_HEADER_LEN = 8 + SHA256_DIGEST_LEN = 32 + """ Base class with common firmware image functions """ def __init__(self): self.segments = [] self.entrypoint = 0 + self.elf_sha256 = None + self.elf_sha256_offset = 0 - 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 % pad_to: - data += b"\x00" * (pad_to - l % pad_to) - if l > 0: - self.segments.append((addr, len(data), data)) + def load_common_header(self, load_file, expected_magic): + (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack(' 16: + raise FatalError('Invalid segment count %d (max 16). Usually this indicates a linker script problem.' % len(self.segments)) def load_segment(self, f, is_irom_segment=False): """ Load the next segment from the image file """ + file_offs = f.tell() (offset, size) = struct.unpack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536: - raise FatalError('Suspicious segment 0x%x, length %d' % (offset, size)) + self.warn_if_unusual_segment(offset, size, is_irom_segment) 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) + segment = ImageSegment(offset, segment_data, file_offs) self.segments.append(segment) return segment + def warn_if_unusual_segment(self, offset, size, is_irom_segment): + if not is_irom_segment: + if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536: + print('WARNING: Suspicious segment 0x%x, length %d' % (offset, size)) + + def maybe_patch_segment_data(self, f, segment_data): + """If SHA256 digest of the ELF file needs to be inserted into this segment, do so. Returns segment data.""" + segment_len = len(segment_data) + file_pos = f.tell() # file_pos is position in the .bin file + if self.elf_sha256_offset >= file_pos and self.elf_sha256_offset < file_pos + segment_len: + # SHA256 digest needs to be patched into this binary segment, + # calculate offset of the digest inside the binary segment. + patch_offset = self.elf_sha256_offset - file_pos + # Sanity checks + if patch_offset < self.SEG_HEADER_LEN or patch_offset + self.SHA256_DIGEST_LEN > segment_len: + raise FatalError('Cannot place SHA256 digest on segment boundary' + '(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)' % + (self.elf_sha256_offset, file_pos, segment_len)) + # offset relative to the data part + patch_offset -= self.SEG_HEADER_LEN + if segment_data[patch_offset:patch_offset + self.SHA256_DIGEST_LEN] != b'\x00' * self.SHA256_DIGEST_LEN: + raise FatalError('Contents of segment at SHA256 digest offset 0x%x are not all zero. Refusing to overwrite.' % + self.elf_sha256_offset) + assert(len(self.elf_sha256) == self.SHA256_DIGEST_LEN) + segment_data = segment_data[0:patch_offset] + self.elf_sha256 + \ + segment_data[patch_offset + self.SHA256_DIGEST_LEN:] + return segment_data + def save_segment(self, f, segment, checksum=None): """ Save the next segment to the image file, return next checksum value if provided """ - (offset, size, data) = segment - f.write(struct.pack(' 0: + if len(irom_segments) != 1: + raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments)) + return irom_segments[0] + return None + + def get_non_irom_segments(self): + irom_segment = self.get_irom_segment() + return [s for s in self.segments if s != irom_segment] + + +class ESP8266ROMFirmwareImage(BaseFirmwareImage): """ 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """ + + ROM_LOADER = ESP8266ROM + def __init__(self, load_file=None): - super(ESPFirmwareImage, self).__init__() + super(ESP8266ROMFirmwareImage, self).__init__() self.flash_mode = 0 self.flash_size_freq = 0 self.version = 1 if load_file is not None: - (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack(' 16: - raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments)) - - for i in xrange(segments): + for _ in range(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: + self.verify() + + def default_output_name(self, input_file): + """ Derive a default output name from the ELF name. """ + return input_file + '-' + + def save(self, basename): + """ Save a set of V1 images for flashing. Parameter is a base filename. """ + # IROM data goes in its own plain binary file + irom_segment = self.get_irom_segment() + if irom_segment is not None: + with open("%s0x%05x.bin" % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START), "wb") as f: + f.write(irom_segment.data) + + # everything but IROM goes at 0x00000 in an image file + normal_segments = self.get_non_irom_segments() + with open("%s0x00000.bin" % basename, 'wb') as f: + self.write_common_header(f, normal_segments) + checksum = ESPLoader.ESP_CHECKSUM_MAGIC + for segment in normal_segments: checksum = self.save_segment(f, segment, checksum) self.append_checksum(f, checksum) -class OTAFirmwareImage(BaseFirmwareImage): +ESP8266ROM.BOOTLOADER_IMAGE = ESP8266ROMFirmwareImage + + +class ESP8266V2FirmwareImage(BaseFirmwareImage): """ 'Version 2' firmware image, segments loaded by software bootloader stub (ie Espressif bootloader or rboot) """ + + ROM_LOADER = ESP8266ROM + def __init__(self, load_file=None): - super(OTAFirmwareImage, self).__init__() + super(ESP8266V2FirmwareImage, 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): + for _ in range(segments): self.load_segment(load_file) self.checksum = self.read_checksum(load_file) + self.verify() + + def default_output_name(self, input_file): + """ Derive a default output name from the ELF name. """ + irom_segment = self.get_irom_segment() + if irom_segment is not None: + irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START + else: + irom_offs = 0 + return "%s-0x%05x.bin" % (os.path.splitext(input_file)[0], + irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) + def save(self, filename): with open(filename, 'wb') as f: # Save first header for irom0 segment - f.write(struct.pack(' 0: + last_addr = flash_segments[0].addr + for segment in flash_segments[1:]: + if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN: + raise FatalError(("Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " + "Can't generate binary. Suggest changing linker script or ELF to merge sections.") % + (segment.addr, last_addr)) + last_addr = segment.addr + + def get_alignment_data_needed(segment): + # Actual alignment (in data bytes) required for a segment header: positioned so that + # after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN + # + # (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned + # IROM_ALIGN+0x18 to account for the binary file header + align_past = (segment.addr % self.IROM_ALIGN) - self.SEG_HEADER_LEN + pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past + if pad_len == 0 or pad_len == self.IROM_ALIGN: + return 0 # already aligned + + # subtract SEG_HEADER_LEN a second time, as the padding block has a header as well + pad_len -= self.SEG_HEADER_LEN + if pad_len < 0: + pad_len += self.IROM_ALIGN + return pad_len + + # try to fit each flash segment on a 64kB aligned boundary + # by padding with parts of the non-flash segments... + while len(flash_segments) > 0: + segment = flash_segments[0] + pad_len = get_alignment_data_needed(segment) + if pad_len > 0: # need to pad + if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN: + pad_segment = ram_segments[0].split_image(pad_len) + if len(ram_segments[0].data) == 0: + ram_segments.pop(0) + else: + pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell()) + checksum = self.save_segment(f, pad_segment, checksum) + total_segments += 1 + else: + # write the flash segment + assert (f.tell() + 8) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN + checksum = self.save_flash_segment(f, segment, checksum) + flash_segments.pop(0) + total_segments += 1 + + # flash segments all written, so write any remaining RAM segments + for segment in ram_segments: + checksum = self.save_segment(f, segment, checksum) + total_segments += 1 + + if self.secure_pad: + # pad the image so that after signing it will end on a a 64KB boundary. + # This ensures all mapped flash content will be verified. + if not self.append_digest: + raise FatalError("secure_pad only applies if a SHA-256 digest is also appended to the image") + align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN + # 16 byte aligned checksum (force the alignment to simplify calculations) + checksum_space = 16 + if self.secure_pad == '1': + # after checksum: SHA-256 digest + (to be added by signing process) version, signature + 12 trailing bytes due to alignment + space_after_checksum = 32 + 4 + 64 + 12 + elif self.secure_pad == '2': # Secure Boot V2 + # after checksum: SHA-256 digest + signature sector, but we place signature sector after the 64KB boundary + space_after_checksum = 32 + pad_len = (self.IROM_ALIGN - align_past - checksum_space - space_after_checksum) % self.IROM_ALIGN + pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell()) + + checksum = self.save_segment(f, pad_segment, checksum) + total_segments += 1 + + # done writing segments + self.append_checksum(f, checksum) + image_length = f.tell() + + if self.secure_pad: + assert ((image_length + space_after_checksum) % self.IROM_ALIGN) == 0 + + # kinda hacky: go back to the initial header and write the new segment count + # that includes padding segments. This header is not checksummed + f.seek(1) + try: + f.write(chr(total_segments)) + except TypeError: # Python 3 + f.write(bytes([total_segments])) + + if self.append_digest: + # calculate the SHA256 of the whole file and append it + f.seek(0) + digest = hashlib.sha256() + digest.update(f.read(image_length)) + f.write(digest.digest()) + + with open(filename, 'wb') as real_file: + real_file.write(f.getvalue()) + + def save_flash_segment(self, f, segment, checksum=None): + """ Save the next segment to the image file, return next checksum value if provided """ + segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN + segment_len_remainder = segment_end_pos % self.IROM_ALIGN + if segment_len_remainder < 0x24: + # Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the + # last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page boundary. + segment.data += b'\x00' * (0x24 - segment_len_remainder) + return self.save_segment(f, segment, checksum) + + def load_extended_header(self, load_file): + def split_byte(n): + return (n & 0x0F, (n >> 4) & 0x0F) + + fields = list(struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16))) + + self.wp_pin = fields[0] + + # SPI pin drive stengths are two per byte + self.clk_drv, self.q_drv = split_byte(fields[1]) + self.d_drv, self.cs_drv = split_byte(fields[2]) + self.hd_drv, self.wp_drv = split_byte(fields[3]) + + chip_id = fields[4] + if chip_id != self.ROM_LOADER.IMAGE_CHIP_ID: + print(("Unexpected chip id in image. Expected %d but value was %d. " + "Is this image for a different chip model?") % (self.ROM_LOADER.IMAGE_CHIP_ID, chip_id)) + + # reserved fields in the middle should all be zero + if any(f for f in fields[6:-1] if f != 0): + print("Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?") + + append_digest = fields[-1] # last byte is append_digest + if append_digest in [0, 1]: + self.append_digest = (append_digest == 1) + else: + raise RuntimeError("Invalid value for append_digest field (0x%02x). Should be 0 or 1.", append_digest) + + def save_extended_header(self, save_file): + def join_byte(ln, hn): + return (ln & 0x0F) + ((hn & 0x0F) << 4) + + append_digest = 1 if self.append_digest else 0 + + fields = [self.wp_pin, + join_byte(self.clk_drv, self.q_drv), + join_byte(self.d_drv, self.cs_drv), + join_byte(self.hd_drv, self.wp_drv), + self.ROM_LOADER.IMAGE_CHIP_ID, + self.min_rev] + fields += [0] * 8 # padding + fields += [append_digest] + + packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields) + save_file.write(packed) + + +ESP32ROM.BOOTLOADER_IMAGE = ESP32FirmwareImage + + +class ESP32S2FirmwareImage(ESP32FirmwareImage): + """ ESP32S2 Firmware Image almost exactly the same as ESP32FirmwareImage """ + ROM_LOADER = ESP32S2ROM + + +ESP32S2ROM.BOOTLOADER_IMAGE = ESP32S2FirmwareImage + + +class ESP32S3BETA2FirmwareImage(ESP32FirmwareImage): + """ ESP32S3 Firmware Image almost exactly the same as ESP32FirmwareImage """ + ROM_LOADER = ESP32S3BETA2ROM + + +ESP32S3BETA2ROM.BOOTLOADER_IMAGE = ESP32S3BETA2FirmwareImage + + +class ESP32C3FirmwareImage(ESP32FirmwareImage): + """ ESP32C3 Firmware Image almost exactly the same as ESP32FirmwareImage """ + ROM_LOADER = ESP32C3ROM + + +ESP32C3ROM.BOOTLOADER_IMAGE = ESP32C3FirmwareImage + class ELFFile(object): + SEC_TYPE_PROGBITS = 0x01 + SEC_TYPE_STRTAB = 0x03 + + LEN_SEC_HEADER = 0x28 + def __init__(self, name): - self.name = binutils_safe_path(name) - self.symbols = None + # Load sections from the ELF file + self.name = name + with open(self.name, 'rb') as f: + self._read_elf_file(f) - def _fetch_symbols(self): - if self.symbols is not None: - return - self.symbols = {} + def get_section(self, section_name): + for s in self.sections: + if s.name == section_name: + return s + raise ValueError("No section %s in ELF file" % section_name) + + def _read_elf_file(self, f): + # read the ELF file header + LEN_FILE_HEADER = 0x34 try: - tool_nm = "xtensa-lx106-elf-nm" - if os.getenv('XTENSA_CORE') == 'lx106': - tool_nm = "xt-nm" - proc = subprocess.Popen([tool_nm, self.name], stdout=subprocess.PIPE) - except OSError: - print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_nm - sys.exit(1) - for l in proc.stdout: - fields = l.strip().split() - try: - if fields[0] == "U": - print "Warning: ELF binary has undefined symbol %s" % fields[1] - continue - if fields[0] == "w": - continue # can skip weak symbols - self.symbols[fields[2]] = int(fields[0], 16) - except ValueError: - raise FatalError("Failed to strip symbol output from nm: %s" % fields) + (ident, _type, machine, _version, + self.entrypoint, _phoff, shoff, _flags, + _ehsize, _phentsize, _phnum, shentsize, + shnum, shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER)) + except struct.error as e: + raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e)) - def get_symbol_addr(self, sym): - self._fetch_symbols() - return self.symbols[sym] + if byte(ident, 0) != 0x7f or ident[1:4] != b'ELF': + raise FatalError("%s has invalid ELF magic header" % self.name) + if machine not in [0x5e, 0xf3]: + raise FatalError("%s does not appear to be an Xtensa or an RISCV ELF file. e_machine=%04x" % (self.name, machine)) + if shentsize != self.LEN_SEC_HEADER: + raise FatalError("%s has unexpected section header entry size 0x%x (not 0x%x)" % (self.name, shentsize, self.LEN_SEC_HEADER)) + if shnum == 0: + raise FatalError("%s has 0 section headers" % (self.name)) + self._read_sections(f, shoff, shnum, shstrndx) - def get_entry_point(self): - tool_readelf = "xtensa-lx106-elf-readelf" - if os.getenv('XTENSA_CORE') == 'lx106': - tool_readelf = "xt-readelf" - try: - proc = subprocess.Popen([tool_readelf, "-h", self.name], stdout=subprocess.PIPE) - except OSError: - print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_readelf - sys.exit(1) - for l in proc.stdout: - fields = l.strip().split() - if fields[0] == "Entry": - return int(fields[3], 0) + def _read_sections(self, f, section_header_offs, section_header_count, shstrndx): + f.seek(section_header_offs) + len_bytes = section_header_count * self.LEN_SEC_HEADER + section_header = f.read(len_bytes) + if len(section_header) == 0: + raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs) + if len(section_header) != (len_bytes): + raise FatalError("Only read 0x%x bytes from section header (expected 0x%x.) Truncated ELF file?" % (len(section_header), len_bytes)) - def load_section(self, section): - tool_objcopy = "xtensa-lx106-elf-objcopy" - if os.getenv('XTENSA_CORE') == 'lx106': - tool_objcopy = "xt-objcopy" - tmpsection = binutils_safe_path(tempfile.mktemp(suffix=".section")) - try: - subprocess.check_call([tool_objcopy, "--only-section", section, "-Obinary", self.name, tmpsection]) - with open(tmpsection, "rb") as f: - data = f.read() - finally: - os.remove(tmpsection) - return data + # walk through the section header and extract all sections + section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER) + + def read_section_header(offs): + name_offs, sec_type, _flags, lma, sec_offs, size = struct.unpack_from(" 0] + self.sections = prog_sections + + def sha256(self): + # return SHA256 hash of the input ELF file + sha256 = hashlib.sha256() + with open(self.name, 'rb') as f: + sha256.update(f.read()) + return sha256.digest() -class CesantaFlasher(object): - - # From stub_flasher.h - CMD_FLASH_WRITE = 1 - CMD_FLASH_READ = 2 - CMD_FLASH_DIGEST = 3 - CMD_BOOT_FW = 6 - - def __init__(self, esp, baud_rate=0): - print 'Running Cesanta flasher stub...' - if baud_rate <= ESPROM.ESP_ROM_BAUD: # don't change baud rates if we already synced at that rate - baud_rate = 0 - self._esp = esp - esp.run_stub(json.loads(_CESANTA_FLASHER_STUB), [baud_rate], read_output=False) - if baud_rate > 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(' 16 bytes) will be + printed as separately indented lines, with ASCII decoding at the end + of each line. + """ + def __init__(self, binary_string, auto_split=True): + self._s = binary_string + self._auto_split = auto_split + + def __str__(self): + if self._auto_split and len(self._s) > 16: + result = "" + s = self._s + while len(s) > 0: + line = s[:16] + ascii_line = "".join(c if (c == ' ' or (c in string.printable and c not in string.whitespace)) + else '.' for c in line.decode('ascii', 'replace')) + s = s[16:] + result += "\n %-16s %-16s | %s" % (hexify(line[:8], False), hexify(line[8:], False), ascii_line) + return result + else: + return hexify(self._s, False) + + +def pad_to(data, alignment, pad_character=b'\xFF'): + """ Pad to the next alignment boundary """ + pad_mod = len(data) % alignment + if pad_mod != 0: + data += pad_character * (alignment - pad_mod) + return data class FatalError(RuntimeError): @@ -770,170 +2759,439 @@ class FatalError(RuntimeError): @staticmethod def WithResult(message, result): """ - Return a fatal error object that includes the hex values of + Return a fatal error object that appends the hex values of 'result' as a string formatted argument. """ - return FatalError(message % ", ".join(hex(ord(x)) for x in result)) + message += " (result was %s)" % hexify(result) + return FatalError(message) +class NotImplementedInROMError(FatalError): + """ + Wrapper class for the error thrown when a particular ESP bootloader function + is not implemented in the ROM bootloader. + """ + def __init__(self, bootloader, func): + FatalError.__init__(self, "%s ROM does not support function %s." % (bootloader.CHIP_NAME, func.__name__)) + + +class NotSupportedError(FatalError): + def __init__(self, esp, function_name): + FatalError.__init__(self, "Function %s is not supported for %s." % (function_name, esp.CHIP_NAME)) + # "Operation" commands, executable at command line. One function each # -# Each function takes either two args (, ) or a single +# Each function takes either two args (, ) 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), +class UnsupportedCommandError(RuntimeError): + """ + Wrapper class for when ROM loader returns an invalid command response. + + Usually this indicates the loader is running in Secure Download Mode. + """ + def __init__(self, esp, op): + if esp.secure_download_mode: + msg = "This command (0x%x) is not supported in Secure Download Mode" % op + else: + msg = "Invalid (unsupported) command 0x%x" % op + RuntimeError.__init__(self, msg) + + +def load_ram(esp, args): + image = LoadFirmwareImage(esp.CHIP_NAME, args.filename) + + print('RAM boot...') + for seg in image.segments: + size = len(seg.data) + print('Downloading %d bytes at %08x...' % (size, seg.addr), end=' ') sys.stdout.flush() - esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, offset) + esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr) seq = 0 - while len(data) > 0: - esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq) - data = data[esp.ESP_RAM_BLOCK:] + while len(seg.data) > 0: + esp.mem_block(seg.data[0:esp.ESP_RAM_BLOCK], seq) + seg.data = seg.data[esp.ESP_RAM_BLOCK:] seq += 1 - print 'done!' + print('done!') - print 'All segments done, executing at %08x' % image.entrypoint + 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)) + 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) + 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('> 16 + args.flash_size = DETECTED_FLASH_SIZES.get(size_id) + if args.flash_size is None: + print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4MB' % (flash_id, size_id)) + args.flash_size = '4MB' + else: + print('Auto-detected Flash size:', args.flash_size) + + +def _update_image_flash_params(esp, address, args, image): + """ Modify the flash mode & size bytes if this looks like an executable bootloader image """ + if len(image) < 8: + return image # not long enough to be a bootloader image + + # unpack the (potential) image header + magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4]) + if address != esp.BOOTLOADER_FLASH_OFFSET: + return image # not flashing bootloader offset, so don't modify this + + if (args.flash_mode, args.flash_freq, args.flash_size) == ('keep',) * 3: + return image # all settings are 'keep', not modifying anything + + # easy check if this is an image: does it start with a magic byte? + if magic != esp.ESP_IMAGE_MAGIC: + print("Warning: Image file at 0x%x doesn't look like an image file, so not changing any flash settings." % address) + return image + + # make sure this really is an image, and not just data that + # starts with esp.ESP_IMAGE_MAGIC (mostly a problem for encrypted + # images that happen to start with a magic byte + try: + test_image = esp.BOOTLOADER_IMAGE(io.BytesIO(image)) + test_image.verify() + except Exception: + print("Warning: Image file at 0x%x is not a valid %s image, so not changing any flash settings." % (address, esp.CHIP_NAME)) + return image + + if args.flash_mode != 'keep': + flash_mode = {'qio': 0, 'qout': 1, 'dio': 2, 'dout': 3}[args.flash_mode] + + flash_freq = flash_size_freq & 0x0F + if args.flash_freq != 'keep': + flash_freq = {'40m': 0, '26m': 1, '20m': 2, '80m': 0xf}[args.flash_freq] + + flash_size = flash_size_freq & 0xF0 + if args.flash_size != 'keep': + flash_size = esp.parse_flash_size_arg(args.flash_size) + + flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq) + if flash_params != image[2:4]: + print('Flash params set to 0x%04x' % struct.unpack(">H", flash_params)) + image = image[0:2] + flash_params + image[4:] + return image def write_flash(esp, args): - flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] - flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70, '64m':0x80, '128m':0x90}[args.flash_size] - flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] - flash_params = struct.pack('BB', flash_mode, flash_size_freq) + # set args.compress based on default behaviour: + # -> if either --compress or --no-compress is set, honour that + # -> otherwise, set --compress unless --no-stub is set + if args.compress is None and not args.no_compress: + args.compress = not args.no_stub - flasher = CesantaFlasher(esp, args.baud) + # In case we have encrypted files to write, we first do few sanity checks before actual flash + if args.encrypt or args.encrypt_files is not None: + do_write = True - for address, argfile in args.addr_filename: - image = argfile.read() - argfile.seek(0) # rewind in case we need it again - # Fix sflash config data. - if address == 0 and image[0] == '\xe9': - print 'Flash params set to 0x%02x%02x' % (flash_mode, flash_size_freq) - image = image[0:2] + flash_params + image[4:] - # Pad to sector size, which is the minimum unit of writing (erasing really). - if len(image) % esp.ESP_FLASH_SECTOR != 0: - image += '\xff' * (esp.ESP_FLASH_SECTOR - (len(image) % esp.ESP_FLASH_SECTOR)) + if not esp.secure_download_mode: + if esp.get_encrypted_download_disabled(): + raise FatalError("This chip has encrypt functionality in UART download mode disabled. " + "This is the Flash Encryption configuration for Production mode instead of Development mode.") + + crypt_cfg_efuse = esp.get_flash_crypt_config() + + if crypt_cfg_efuse is not None and crypt_cfg_efuse != 0xF: + print('Unexpected FLASH_CRYPT_CONFIG value: 0x%x' % (crypt_cfg_efuse)) + do_write = False + + enc_key_valid = esp.is_flash_encryption_key_valid() + + if not enc_key_valid: + print('Flash encryption key is not programmed') + do_write = False + + # Determine which files list contain the ones to encrypt + files_to_encrypt = args.addr_filename if args.encrypt else args.encrypt_files + + for address, argfile in files_to_encrypt: + if address % esp.FLASH_ENCRYPTED_WRITE_ALIGN: + print("File %s address 0x%x is not %d byte aligned, can't flash encrypted" % + (argfile.name, address, esp.FLASH_ENCRYPTED_WRITE_ALIGN)) + do_write = False + + if not do_write and not args.ignore_flash_encryption_efuse_setting: + raise FatalError("Can't perform encrypted flash write, consult Flash Encryption documentation for more information") + + # verify file sizes fit in flash + if args.flash_size != 'keep': # TODO: check this even with 'keep' + flash_end = flash_size_bytes(args.flash_size) + for address, argfile in args.addr_filename: + argfile.seek(0, 2) # seek to end + if address + argfile.tell() > flash_end: + raise FatalError(("File %s (length %d) at offset %d will not fit in %d bytes of flash. " + "Use --flash-size argument, or change flashing address.") + % (argfile.name, argfile.tell(), address, flash_end)) + argfile.seek(0) + + if args.erase_all: + erase_flash(esp, args) + + """ Create a list describing all the files we have to flash. Each entry holds an "encrypt" flag + marking whether the file needs encryption or not. This list needs to be sorted. + + First, append to each entry of our addr_filename list the flag args.encrypt + For example, if addr_filename is [(0x1000, "partition.bin"), (0x8000, "bootloader")], + all_files will be [(0x1000, "partition.bin", args.encrypt), (0x8000, "bootloader", args.encrypt)], + where, of course, args.encrypt is either True or False + """ + all_files = [(offs, filename, args.encrypt) for (offs, filename) in args.addr_filename] + + """Now do the same with encrypt_files list, if defined. + In this case, the flag is True + """ + if args.encrypt_files is not None: + encrypted_files_flag = [(offs, filename, True) for (offs, filename) in args.encrypt_files] + + # Concatenate both lists and sort them. + # As both list are already sorted, we could simply do a merge instead, + # but for the sake of simplicity and because the lists are very small, + # let's use sorted. + all_files = sorted(all_files + encrypted_files_flag, key=lambda x: x[0]) + + for address, argfile, encrypted in all_files: + compress = args.compress + + # Check whether we can compress the current file before flashing + if compress and encrypted: + print('\nWARNING: - compress and encrypt options are mutually exclusive ') + print('Will flash %s uncompressed' % argfile.name) + compress = False + + if args.no_stub: + print('Erasing flash...') + image = pad_to(argfile.read(), esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4) + if len(image) == 0: + print('WARNING: File %s is empty' % argfile.name) + continue + image = _update_image_flash_params(esp, address, args, image) + calcmd5 = hashlib.md5(image).hexdigest() + uncsize = len(image) + if compress: + uncimage = image + image = zlib.compress(uncimage, 9) + blocks = esp.flash_defl_begin(uncsize, len(image), address) + else: + blocks = esp.flash_begin(uncsize, address, begin_rom_encrypted=encrypted) + argfile.seek(0) # in case we need it again + seq = 0 + written = 0 t = time.time() - flasher.flash_write(address, image, not args.no_progress) + while len(image) > 0: + print_overwrite('Writing at 0x%08x... (%d %%)' % (address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks)) + sys.stdout.flush() + block = image[0:esp.FLASH_WRITE_SIZE] + if compress: + esp.flash_defl_block(block, seq, timeout=timeout_per_mb(COMP_BLOCK_WRITE_TIMEOUT_PER_MB, uncsize)) + else: + # Pad the last block + block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) + if encrypted: + esp.flash_encrypt_block(block, seq) + else: + esp.flash_block(block, seq) + image = image[esp.FLASH_WRITE_SIZE:] + seq += 1 + written += len(block) t = time.time() - t - print ('\rWrote %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' - % (len(image), address, t, len(image) / t * 8 / 1000)) - print 'Leaving...' + speed_msg = "" + if compress: + if t > 0.0: + speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000) + print_overwrite('Wrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, written, address, t, speed_msg), last_line=True) + else: + if t > 0.0: + speed_msg = " (%.1f kbit/s)" % (written / t * 8 / 1000) + print_overwrite('Wrote %d bytes at 0x%08x in %.1f seconds%s...' % (written, address, t, speed_msg), last_line=True) + + if not encrypted and not esp.secure_download_mode: + try: + res = esp.flash_md5sum(address, uncsize) + if res != calcmd5: + print('File md5: %s' % calcmd5) + print('Flash md5: %s' % res) + print('MD5 of 0xFF is %s' % (hashlib.md5(b'\xFF' * uncsize).hexdigest())) + raise FatalError("MD5 of file does not match data in flash!") + else: + print('Hash of data verified.') + except NotImplementedInROMError: + pass + + print('\nLeaving...') + + if esp.IS_STUB: + # skip sending flash_finish to ROM loader here, + # as it causes the loader to exit and run user code + esp.flash_begin(0, 0) + + # Get the "encrypted" flag for the last file flashed + # Note: all_files list contains triplets like: + # (address: Integer, filename: String, encrypted: Boolean) + last_file_encrypted = all_files[-1][2] + + # Check whether the last file flashed was compressed or not + if args.compress and not last_file_encrypted: + esp.flash_defl_finish(False) + else: + esp.flash_finish(False) + if args.verify: - print 'Verifying just-written flash...' - _verify_flash(flasher, args, flash_params) - flasher.boot_fw() + print('Verifying just-written flash...') + print('(This option is deprecated, flash contents are now always read back after flashing.)') + # If some encrypted files have been flashed print a warning saying that we won't check them + if args.encrypt or args.encrypt_files is not None: + print('WARNING: - cannot verify encrypted files, they will be ignored') + # Call verify_flash function only if there at least one non-encrypted file flashed + if not args.encrypt: + verify_flash(esp, args) def image_info(args): - image = LoadFirmwareImage(args.filename) + image = LoadFirmwareImage(args.chip, args.filename) print('Image version: %d' % image.version) - 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): - if image.version == 2 and idx == 0: - print 'Segment 1: %d bytes IROM0 (no load address)' % size - else: - 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!') + print('Entry point: %08x' % image.entrypoint if image.entrypoint != 0 else 'Entry point not set') + print('%d segments' % len(image.segments)) + print() + idx = 0 + for seg in image.segments: + idx += 1 + seg_name = ", ".join([seg_range[2] for seg_range in image.ROM_LOADER.MEMORY_MAP if seg_range[0] <= seg.addr < seg_range[1]]) + print('Segment %d: %r [%s]' % (idx, seg, seg_name)) + calc_checksum = image.calculate_checksum() + print('Checksum: %02x (%s)' % (image.checksum, + 'valid' if image.checksum == calc_checksum else 'invalid - calculated %02x' % calc_checksum)) + try: + digest_msg = 'Not appended' + if image.append_digest: + is_valid = image.stored_digest == image.calc_digest + digest_msg = "%s (%s)" % (hexify(image.calc_digest).lower(), + "valid" if is_valid else "invalid") + print('Validation Hash: %s' % digest_msg) + except AttributeError: + pass # ESP8266 image has no append_digest field def make_image(args): - image = ESPFirmwareImage() + image = ESP8266ROMFirmwareImage() if len(args.segfile) == 0: raise FatalError('No segments specified') if len(args.segfile) != len(args.segaddr): raise FatalError('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) + with open(seg, 'rb') as f: + data = f.read() + image.segments.append(ImageSegment(addr, data)) image.entrypoint = args.entrypoint image.save(args.output) def elf2image(args): e = ELFFile(args.input) - if args.version == '1': - image = ESPFirmwareImage() + if args.chip == 'auto': # Default to ESP8266 for backwards compatibility + print("Creating image for ESP8266...") + args.chip = 'esp8266' + + if args.chip == 'esp32': + image = ESP32FirmwareImage() + if args.secure_pad: + image.secure_pad = '1' + elif args.secure_pad_v2: + image.secure_pad = '2' + image.min_rev = int(args.min_rev) + elif args.chip == 'esp32s2': + image = ESP32S2FirmwareImage() + if args.secure_pad_v2: + image.secure_pad = '2' + image.min_rev = 0 + elif args.chip == 'esp32s3beta2': + image = ESP32S3BETA2FirmwareImage() + if args.secure_pad_v2: + image.secure_pad = '2' + image.min_rev = 0 + elif args.chip == 'esp32c3': + image = ESP32C3FirmwareImage() + if args.secure_pad_v2: + image.secure_pad = '2' + image.min_rev = 0 + elif args.version == '1': # ESP8266 + image = ESP8266ROMFirmwareImage() else: - image = OTAFirmwareImage() - irom_data = e.load_section('.irom0.text') - if len(irom_data) == 0: - raise FatalError(".irom0.text section not found in ELF file - can't create V2 image.") - image.add_segment(0, irom_data, 16) - image.entrypoint = e.get_entry_point() - 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 = ESP8266V2FirmwareImage() + image.entrypoint = e.entrypoint + image.segments = e.sections # ELFSection is a subclass of ImageSegment + image.flash_mode = {'qio': 0, 'qout': 1, 'dio': 2, 'dout': 3}[args.flash_mode] + image.flash_size_freq = image.ROM_LOADER.FLASH_SIZES[args.flash_size] + image.flash_size_freq += {'40m': 0, '26m': 1, '20m': 2, '80m': 0xf}[args.flash_freq] - 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, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70, '64m':0x80, '128m':0x90}[args.flash_size] - image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + if args.elf_sha256_offset: + image.elf_sha256 = e.sha256() + image.elf_sha256_offset = args.elf_sha256_offset - irom_offs = e.get_symbol_addr("_irom0_text_start") - 0x40200000 + image.verify() - if args.version == '1': - if args.output is None: - args.output = args.input + '-' - image.save(args.output + "0x00000.bin") - data = e.load_section(".irom0.text") - if irom_offs < 0: - raise FatalError('Address of symbol _irom0_text_start in ELF is located before flash mapping address. Bad linker script?') - if (irom_offs & 0xFFF) != 0: # irom0 isn't flash sector aligned - print "WARNING: irom0 section offset is 0x%08x. ELF is probably linked for 'elf2image --version=2'" % irom_offs - with open(args.output + "0x%05x.bin" % irom_offs, "wb") as f: - f.write(data) - f.close() - else: # V2 OTA image - if args.output is None: - args.output = "%s-0x%05x.bin" % (os.path.splitext(args.input)[0], irom_offs & ~(ESPROM.ESP_FLASH_SECTOR - 1)) - image.save(args.output) + if args.output is None: + args.output = image.default_output_name(args.input) + image.save(args.output) def read_mac(esp, args): mac = esp.read_mac() - print 'MAC: %s' % ':'.join(map(lambda x: '%02x' % x, mac)) + + def print_mac(label, mac): + print('%s: %s' % (label, ':'.join(map(lambda x: '%02x' % x, mac)))) + print_mac("MAC", mac) def chip_id(esp, args): - chipid = esp.chip_id() - print 'Chip ID: 0x%08x' % chipid + try: + chipid = esp.chip_id() + print('Chip ID: 0x%08x' % chipid) + except NotSupportedError: + print('Warning: %s has no Chip ID. Reading MAC instead.' % esp.CHIP_NAME) + read_mac(esp, args) def erase_flash(esp, args): - print 'Erasing flash (this may take a while)...' - esp.flash_erase() + print('Erasing flash (this may take a while)...') + t = time.time() + esp.erase_flash() + print('Chip erase completed successfully in %.1fs' % (time.time() - t)) + + +def erase_region(esp, args): + print('Erasing region (may be slow depending on size)...') + t = time.time() + esp.erase_region(args.address, args.size) + print('Erase completed successfully in %.1f seconds.' % (time.time() - t)) def run(esp, args): @@ -942,83 +3200,170 @@ def run(esp, args): def flash_id(esp, args): flash_id = esp.flash_id() - print 'Manufacturer: %02x' % (flash_id & 0xff) - print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff) + print('Manufacturer: %02x' % (flash_id & 0xff)) + flid_lowbyte = (flash_id >> 16) & 0xFF + print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, flid_lowbyte)) + print('Detected flash size: %s' % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))) def read_flash(esp, args): - flasher = CesantaFlasher(esp, args.baud) + if args.no_progress: + flash_progress = None + else: + def flash_progress(progress, length): + msg = '%d (%d %%)' % (progress, progress * 100.0 / length) + padding = '\b' * len(msg) + if progress == length: + padding = '\n' + sys.stdout.write(msg + padding) + sys.stdout.flush() t = time.time() - data = flasher.flash_read(args.address, args.size, not args.no_progress) + data = esp.read_flash(args.address, args.size, flash_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) + print_overwrite('Read %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' + % (len(data), args.address, t, len(data) / t * 8 / 1000), last_line=True) + with open(args.filename, 'wb') as f: + f.write(data) -def _verify_flash(flasher, args, flash_params=None): +def verify_flash(esp, args): differences = False + for address, argfile in args.addr_filename: - image = argfile.read() + image = pad_to(argfile.read(), 4) 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 = _update_image_flash_params(esp, address, args, image) + image_size = len(image) - print 'Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name) + 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() + digest = esp.flash_md5sum(address, image_size) + expected_digest = hashlib.md5(image).hexdigest() if digest == expected_digest: - print '-- verify OK (digest matched)' + print('-- verify OK (digest matched)') continue else: differences = True if getattr(args, 'diff', 'no') != 'yes': - print '-- verify FAILED (digest mismatch)' + print('-- verify FAILED (digest mismatch)') continue - flash = flasher.flash_read(address, image_size) + flash = esp.read_flash(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]) + diff = [i for i in range(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])) + flash_byte = flash[d] + image_byte = image[d] + if PYTHON2: + flash_byte = ord(flash_byte) + image_byte = ord(image_byte) + print(' %08x %02x %02x' % (address + d, flash_byte, image_byte)) if differences: raise FatalError("Verify failed.") -def verify_flash(esp, args, flash_params=None): - flasher = CesantaFlasher(esp) - _verify_flash(flasher, args, flash_params) +def read_flash_status(esp, args): + print('Status value: 0x%04x' % esp.read_status(args.bytes)) + + +def write_flash_status(esp, args): + fmt = "0x%%0%dx" % (args.bytes * 2) + args.value = args.value & ((1 << (args.bytes * 8)) - 1) + print(('Initial flash status: ' + fmt) % esp.read_status(args.bytes)) + print(('Setting flash status: ' + fmt) % args.value) + esp.write_status(args.value, args.bytes, args.non_volatile) + print(('After flash status: ' + fmt) % esp.read_status(args.bytes)) + + +def get_security_info(esp, args): + (flags, flash_crypt_cnt, key_purposes) = esp.get_security_info() + # TODO: better display + print('Flags: 0x%08x (%s)' % (flags, bin(flags))) + print('Flash_Crypt_Cnt: 0x%x' % flash_crypt_cnt) + print('Key_Purposes: %s' % (key_purposes,)) def version(args): - print __version__ + print(__version__) # # End of operations functions # -def main(): +def main(custom_commandline=None): + """ + Main function for esptool + + custom_commandline - Optional override for default arguments parsing (that uses sys.argv), can be a list of custom arguments + as strings. Arguments and their values need to be added as individual items to the list e.g. "-b 115200" thus + becomes ['-b', '115200']. + """ parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool') + parser.add_argument('--chip', '-c', + help='Target chip type', + type=lambda c: c.lower().replace('-', ''), # support ESP32-S2, etc. + choices=['auto', 'esp8266', 'esp32', 'esp32s2', 'esp32s3beta2', 'esp32c3'], + default=os.environ.get('ESPTOOL_CHIP', 'auto')) + parser.add_argument( '--port', '-p', help='Serial port device', - default=os.environ.get('ESPTOOL_PORT', '/dev/ttyUSB0')) + default=os.environ.get('ESPTOOL_PORT', None)) parser.add_argument( '--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)) + default=os.environ.get('ESPTOOL_BAUD', ESPLoader.ESP_ROM_BAUD)) + + parser.add_argument( + '--before', + help='What to do before connecting to the chip', + choices=['default_reset', 'no_reset', 'no_reset_no_sync'], + default=os.environ.get('ESPTOOL_BEFORE', 'default_reset')) + + parser.add_argument( + '--after', '-a', + help='What to do after esptool.py is finished', + choices=['hard_reset', 'soft_reset', 'no_reset'], + default=os.environ.get('ESPTOOL_AFTER', 'hard_reset')) + + parser.add_argument( + '--no-stub', + help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.", + action='store_true') + + parser.add_argument( + '--trace', '-t', + help="Enable trace-level output of esptool.py interactions.", + action='store_true') + + parser.add_argument( + '--override-vddsdio', + help="Override ESP32 VDDSDIO internal voltage regulator (use with care)", + choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES, + nargs='?') + + parser.add_argument( + '--connect-attempts', + help=('Number of attempts to connect, negative or 0 for infinite. ' + 'Default: %d.' % DEFAULT_CONNECT_ATTEMPTS), + type=int, + default=os.environ.get('ESPTOOL_CONNECT_ATTEMPTS', DEFAULT_CONNECT_ATTEMPTS)) subparsers = parser.add_subparsers( dest='operation', help='Run esptool {command} -h for additional help') + def add_spi_connection_arg(parent): + parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. Override default SPI Flash connection. ' + 'Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS).', + action=SpiConnectionAction) + parser_load_ram = subparsers.add_parser( 'load_ram', help='Download an image to RAM and execute') @@ -1043,26 +3388,56 @@ def main(): 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): + def add_spi_flash_subparsers(parent, is_elf2image): """ Add common parser arguments for SPI flash properties """ + extra_keep_args = [] if is_elf2image else ['keep'] + auto_detect = not is_elf2image + + if auto_detect: + extra_fs_message = ", detect, or keep" + else: + extra_fs_message = "" + parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency', - choices=['40m', '26m', '20m', '80m'], - default=os.environ.get('ESPTOOL_FF', '40m')) + choices=extra_keep_args + ['40m', '26m', '20m', '80m'], + default=os.environ.get('ESPTOOL_FF', '40m' if is_elf2image else 'keep')) 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')) + choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'], + default=os.environ.get('ESPTOOL_FM', 'qio' if is_elf2image else 'keep')) + parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16M)' + ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)' + extra_fs_message, + action=FlashSizeAction, auto_detect=auto_detect, + default=os.environ.get('ESPTOOL_FS', '1MB' if is_elf2image else 'keep')) + add_spi_connection_arg(parent) parser_write_flash = subparsers.add_parser( '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('--erase-all', '-e', + help='Erase all regions of flash (not just write areas) before programming', + action="store_true") + + add_spi_flash_subparsers(parser_write_flash, is_elf2image=False) 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_write_flash.add_argument('--verify', help='Verify just-written data on flash ' + '(mostly superfluous, data is read back during flashing)', action='store_true') + parser_write_flash.add_argument('--encrypt', help='Apply flash encryption when writing data (required correct efuse settings)', + action='store_true') + # In order to not break backward compatibility, our list of encrypted files to flash is a new parameter + parser_write_flash.add_argument('--encrypt-files', metavar='
', + help='Files to be encrypted on the flash. Address followed by binary filename, separated by space.', + action=AddrFilenamePairAction) + parser_write_flash.add_argument('--ignore-flash-encryption-efuse-setting', help='Ignore flash encryption efuse settings ', + action='store_true') + + compress_args = parser_write_flash.add_mutually_exclusive_group(required=False) + compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)', + action="store_true", default=None) + compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)', + action="store_true") subparsers.add_parser( 'run', @@ -1086,8 +3461,17 @@ def main(): 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_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1', '2'], default='1') + parser_elf2image.add_argument('--min-rev', '-r', help='Minimum chip revision', choices=['0', '1', '2', '3'], default='0') + parser_elf2image.add_argument('--secure-pad', action='store_true', + help='Pad image so once signed it will end on a 64KB boundary. For Secure Boot v1 images only.') + parser_elf2image.add_argument('--secure-pad-v2', action='store_true', + help='Pad image to 64KB, so once signed its signature sector will start at the next 64K block. ' + 'For Secure Boot v2 images only.') + parser_elf2image.add_argument('--elf-sha256-offset', help='If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary.', + type=arg_auto_int, default=None) + + add_spi_flash_subparsers(parser_elf2image, is_elf2image=True) subparsers.add_parser( 'read_mac', @@ -1097,13 +3481,31 @@ def main(): 'chip_id', help='Read Chip ID from OTP ROM') - subparsers.add_parser( + parser_flash_id = subparsers.add_parser( 'flash_id', help='Read SPI flash manufacturer and device ID') + add_spi_connection_arg(parser_flash_id) + + parser_read_status = subparsers.add_parser( + 'read_flash_status', + help='Read SPI flash status register') + + add_spi_connection_arg(parser_read_status) + parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1, 2, 3], default=2) + + parser_write_status = subparsers.add_parser( + 'write_flash_status', + help='Write SPI flash status register') + + add_spi_connection_arg(parser_write_status) + parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true') + parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1, 2, 3], default=2) + parser_write_status.add_argument('value', help='New value', type=arg_auto_int) parser_read_flash = subparsers.add_parser( 'read_flash', help='Read SPI flash content') + add_spi_connection_arg(parser_read_flash) 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') @@ -1116,36 +3518,252 @@ def main(): action=AddrFilenamePairAction) parser_verify_flash.add_argument('--diff', '-d', help='Show differences', choices=['no', 'yes'], default='no') + add_spi_flash_subparsers(parser_verify_flash, is_elf2image=False) - subparsers.add_parser( + parser_erase_flash = subparsers.add_parser( 'erase_flash', help='Perform Chip Erase on SPI flash') + add_spi_connection_arg(parser_erase_flash) + + parser_erase_region = subparsers.add_parser( + 'erase_region', + help='Erase a region of the flash') + add_spi_connection_arg(parser_erase_region) + parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int) + parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int) subparsers.add_parser( 'version', help='Print esptool version') + subparsers.add_parser('get_security_info', help='Get some security-related data') + # 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() + expand_file_arguments() - print 'esptool.py v%s' % __version__ + args = parser.parse_args(custom_commandline) + 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. + # or be a member function of the ESPLoader class. + + if args.operation is None: + parser.print_help() + sys.exit(1) + + # Forbid the usage of both --encrypt, which means encrypt all the given files, + # and --encrypt-files, which represents the list of files to encrypt. + # The reason is that allowing both at the same time increases the chances of + # having contradictory lists (e.g. one file not available in one of list). + if args.operation == "write_flash" and args.encrypt and args.encrypt_files is not None: + raise FatalError("Options --encrypt and --encrypt-files must not be specified at the same time.") 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) + + if PYTHON2: + # This function is depreciated in Python3 + operation_args = inspect.getargspec(operation_func).args + else: + operation_args = inspect.getfullargspec(operation_func).args + + if operation_args[0] == 'esp': # operation function takes an ESPLoader connection object + if args.before != "no_reset_no_sync": + initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate + else: + initial_baud = args.baud + + if args.port is None: + if list_ports is None: + raise FatalError("Listing all serial ports is currently not available on this operating system version. " + "Specify the port when running esptool.py") + ser_list = sorted(ports.device for ports in list_ports.comports()) + print("Found %d serial ports" % len(ser_list)) + else: + ser_list = [args.port] + esp = None + for each_port in reversed(ser_list): + print("Serial port %s" % each_port) + try: + if args.chip == 'auto': + esp = ESPLoader.detect_chip(each_port, initial_baud, args.before, args.trace, + args.connect_attempts) + else: + chip_class = { + 'esp8266': ESP8266ROM, + 'esp32': ESP32ROM, + 'esp32s2': ESP32S2ROM, + 'esp32s3beta2': ESP32S3BETA2ROM, + 'esp32c3': ESP32C3ROM, + }[args.chip] + esp = chip_class(each_port, initial_baud, args.trace) + esp.connect(args.before, args.connect_attempts) + break + except (FatalError, OSError) as err: + if args.port is not None: + raise + print("%s failed to connect: %s" % (each_port, err)) + esp = None + if esp is None: + raise FatalError("Could not connect to an Espressif device on any of the %d available serial ports." % len(ser_list)) + + if esp.secure_download_mode: + print("Chip is %s in Secure Download Mode" % esp.CHIP_NAME) + else: + print("Chip is %s" % (esp.get_chip_description())) + print("Features: %s" % ", ".join(esp.get_chip_features())) + print("Crystal is %dMHz" % esp.get_crystal_freq()) + read_mac(esp, args) + + if not args.no_stub: + if esp.secure_download_mode: + print("WARNING: Stub loader is not supported in Secure Download Mode, setting --no-stub") + args.no_stub = True + else: + esp = esp.run_stub() + + if args.override_vddsdio: + esp.override_vddsdio(args.override_vddsdio) + + if args.baud > initial_baud: + try: + esp.change_baud(args.baud) + except NotImplementedInROMError: + print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d" % initial_baud) + + # override common SPI flash parameter stuff if configured to do so + if hasattr(args, "spi_connection") and args.spi_connection is not None: + if esp.CHIP_NAME != "ESP32": + raise FatalError("Chip %s does not support --spi-connection option." % esp.CHIP_NAME) + print("Configuring SPI flash mode...") + esp.flash_spi_attach(args.spi_connection) + elif args.no_stub: + print("Enabling default SPI flash mode...") + # ROM loader doesn't enable flash unless we explicitly do it + esp.flash_spi_attach(0) + + if hasattr(args, "flash_size"): + print("Configuring flash size...") + detect_flash_size(esp, args) + if args.flash_size != 'keep': # TODO: should set this even with 'keep' + esp.flash_set_parameters(flash_size_bytes(args.flash_size)) + + try: + operation_func(esp, args) + finally: + try: # Clean up AddrFilenamePairAction files + for address, argfile in args.addr_filename: + argfile.close() + except AttributeError: + pass + + # Handle post-operation behaviour (reset or other) + if operation_func == load_ram: + # the ESP is now running the loaded image, so let it run + print('Exiting immediately.') + elif args.after == 'hard_reset': + print('Hard resetting via RTS pin...') + esp.hard_reset() + elif args.after == 'soft_reset': + print('Soft resetting...') + # flash_finish will trigger a soft reset + esp.soft_reset(False) + else: + print('Staying in bootloader.') + if esp.IS_STUB: + esp.soft_reset(True) # exit stub back to ROM loader + + esp._port.close() + else: operation_func(args) +def expand_file_arguments(): + """ Any argument starting with "@" gets replaced with all values read from a text file. + Text file arguments can be split by newline or by space. + Values are added "as-is", as if they were specified in this order on the command line. + """ + new_args = [] + expanded = False + for arg in sys.argv: + if arg.startswith("@"): + expanded = True + with open(arg[1:], "r") as f: + for line in f.readlines(): + new_args += shlex.split(line) + else: + new_args.append(arg) + if expanded: + print("esptool.py %s" % (" ".join(new_args[1:]))) + sys.argv = new_args + + +class FlashSizeAction(argparse.Action): + """ Custom flash size parser class to support backwards compatibility with megabit size arguments. + + (At next major relase, remove deprecated sizes and this can become a 'normal' choices= argument again.) + """ + def __init__(self, option_strings, dest, nargs=1, auto_detect=False, **kwargs): + super(FlashSizeAction, self).__init__(option_strings, dest, nargs, **kwargs) + self._auto_detect = auto_detect + + def __call__(self, parser, namespace, values, option_string=None): + try: + value = { + '2m': '256KB', + '4m': '512KB', + '8m': '1MB', + '16m': '2MB', + '32m': '4MB', + '16m-c1': '2MB-c1', + '32m-c1': '4MB-c1', + }[values[0]] + print("WARNING: Flash size arguments in megabits like '%s' are deprecated." % (values[0])) + print("Please use the equivalent size '%s'." % (value)) + print("Megabit arguments may be removed in a future release.") + except KeyError: + value = values[0] + + known_sizes = dict(ESP8266ROM.FLASH_SIZES) + known_sizes.update(ESP32ROM.FLASH_SIZES) + if self._auto_detect: + known_sizes['detect'] = 'detect' + known_sizes['keep'] = 'keep' + if value not in known_sizes: + raise argparse.ArgumentError(self, '%s is not a known flash size. Known sizes: %s' % (value, ", ".join(known_sizes.keys()))) + setattr(namespace, self.dest, value) + + +class SpiConnectionAction(argparse.Action): + """ Custom action to parse 'spi connection' override. Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas. + """ + def __call__(self, parser, namespace, value, option_string=None): + if value.upper() == "SPI": + value = 0 + elif value.upper() == "HSPI": + value = 1 + elif "," in value: + values = value.split(",") + if len(values) != 5: + raise argparse.ArgumentError(self, '%s is not a valid list of comma-separate pin numbers. Must be 5 numbers - CLK,Q,D,HD,CS.' % value) + try: + values = tuple(int(v, 0) for v in values) + except ValueError: + raise argparse.ArgumentError(self, '%s is not a valid argument. All pins must be numeric values' % values) + if any([v for v in values if v > 33 or v < 0]): + raise argparse.ArgumentError(self, 'Pin numbers must be in the range 0-33.') + # encode the pin numbers as a 32-bit integer with packed 6-bit values, the same way ESP32 ROM takes them + # TODO: make this less ESP32 ROM specific somehow... + clk, q, d, hd, cs = values + value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk + else: + raise argparse.ArgumentError(self, '%s is not a valid spi-connection value. ' + 'Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS).' % value) + setattr(namespace, self.dest, value) + + class AddrFilenamePairAction(argparse.Action): """ Custom parser class for the address/filename pairs passed as arguments """ def __init__(self, option_strings, dest, nargs='+', **kwargs): @@ -1154,83 +3772,247 @@ class AddrFilenamePairAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): # validate pair arguments pairs = [] - for i in range(0,len(values),2): + 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]) + address = int(values[i], 0) + except ValueError: + 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') + raise argparse.ArgumentError(self, 'Must be pairs of an address and the binary filename to write there') pairs.append((address, argfile)) + + # Sort the addresses and check for overlapping + end = 0 + for address, argfile in sorted(pairs, key=lambda x: x[0]): + argfile.seek(0, 2) # seek to end + size = argfile.tell() + argfile.seek(0) + sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1) + sector_end = ((address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) - 1 + if sector_start < end: + message = 'Detected overlap at address: 0x%x for file: %s' % (address, argfile.name) + raise argparse.ArgumentError(self, message) + end = sector_end setattr(namespace, self.dest, pairs) -# 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} -""" -if __name__ == '__main__': +# Binary stub code (see flasher_stub dir for source & details) +ESP8266ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" +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\ +"""))) +ESP32ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" +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\ +"""))) +ESP32S2ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" +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\ +"""))) +ESP32S3BETA2ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" +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\ +"""))) +ESP32C3ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b""" +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\ +"""))) + + +def _main(): try: main() except FatalError as e: - print '\nA fatal error occurred: %s' % e + print('\nA fatal error occurred: %s' % e) sys.exit(2) + + +if __name__ == '__main__': + _main() diff --git a/os/flash.sh b/os/flash.sh index 552208d..beb0894 100755 --- a/os/flash.sh +++ b/os/flash.sh @@ -4,7 +4,7 @@ if [ $# -ne 1 ]; then echo "One parameter required: the device of the serial interface" echo "$0 " echo "e.g.:" - echo "$0 ttyUSB0" + echo "$0 /dev/ttyUSB0" exit 1 fi @@ -14,8 +14,8 @@ DEVICE=$1 # check the serial connection -if [ ! -c /dev/$DEVICE ]; then - echo "/dev/$DEVICE does not exist" +if [ ! -c $DEVICE ]; then + echo "$DEVICE does not exist" exit 1 fi @@ -24,13 +24,12 @@ if [ ! -f esptool.py ]; then echo "esptool.py" exit 1 fi - -./esptool.py --port /dev/$DEVICE $BAUD read_mac +python3 esptool.py --port $DEVICE $BAUD read_mac if [ $? -ne 0 ]; then echo "Error reading the MAC -> set the device into the bootloader!" exit 1 fi echo "Flashing the new" -#./esptool.py --port /dev/$DEVICE $BAUD write_flash -fm dio 0x00000 nodemcu2.bin -./esptool.py --port /dev/$DEVICE $BAUD write_flash -fm dio 0x00000 0x00000.bin 0x10000 0x10000.bin 0x3fc000 esp_init_data_default.bin 0x07e000 blank.bin 0x3fe000 blank.bin +#python3 esptool.py --port $DEVICE $BAUD write_flash -fm dio 0x00000 nodemcu2.bin +python3 esptool.py --port $DEVICE write_flash -fm dio 0x00000 0x00000.bin 0x10000 0x10000.bin 0x3fc000 esp_init_data_default.bin From fe8817bfe65d3c7d1333a304a59cfa9ca7da45ac Mon Sep 17 00:00:00 2001 From: Ollo Date: Mon, 7 Dec 2020 20:54:46 +0100 Subject: [PATCH 41/59] Ignore locally stored configuration files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0ac4a73..40db714 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ wlancfg.lua +config.lua *.swp unit/testTimesMarchOctoberWithAllSeconds.lua From a2021fcb4808928f4a55a14fd8142a6f0dcab172 Mon Sep 17 00:00:00 2001 From: Ollo Date: Mon, 7 Dec 2020 21:12:48 +0100 Subject: [PATCH 42/59] Sync the time every 5 minutes from the internet --- main.lua | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/main.lua b/main.lua index 59aca40..51e3e0e 100644 --- a/main.lua +++ b/main.lua @@ -154,20 +154,26 @@ function normalOperation() tmr.alarm(2, 500, 0 ,function() syncTimeFromInternet() + displayTime() end) tmr.alarm(3, 2000, 0 ,function() - if (startTelnetServer ~= nil) then - startTelnetServer() - else - print("NO Telent found") - end + if (startTelnetServer ~= nil) then + startTelnetServer() + else + print("NO Telent found") + end end) - displayTime() -- Start the time Thread tmr.alarm(1, 10000, 1 ,function() displayTime() collectgarbage() end) + + -- sync the time every 5 minutes + tmr.alarm(4, 300000, 1 ,function() + syncTimeFromInternet() + displayTime() + end) end -- when no wifi available, open an accesspoint and ask the user From 8858fa19bd971738709ce411d641bdf49c511ab2 Mon Sep 17 00:00:00 2001 From: Ollo Date: Mon, 7 Dec 2020 21:26:02 +0100 Subject: [PATCH 43/59] Added MQTT module --- main.lua | 4 ++-- mqtt.lua | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 mqtt.lua diff --git a/main.lua b/main.lua index 51e3e0e..1260ff7 100644 --- a/main.lua +++ b/main.lua @@ -42,6 +42,7 @@ function syncTimeFromInternet() end ) end + briPercent = 50 function displayTime() local sec, usec = rtctime.get() @@ -146,7 +147,7 @@ function normalOperation() print('IP: ',wifi.sta.getip()) -- Here the WLAN is found, and something is done print("Solving dependencies") - local dependModules = { "timecore" , "wordclock", "telnet" } + local dependModules = { "timecore" , "wordclock", "telnet", "mqtt" } for _,mod in pairs(dependModules) do print("Loading " .. mod) mydofile(mod) @@ -154,7 +155,6 @@ function normalOperation() tmr.alarm(2, 500, 0 ,function() syncTimeFromInternet() - displayTime() end) tmr.alarm(3, 2000, 0 ,function() if (startTelnetServer ~= nil) then diff --git a/mqtt.lua b/mqtt.lua new file mode 100644 index 0000000..845a6eb --- /dev/null +++ b/mqtt.lua @@ -0,0 +1,44 @@ +-- MQTT extension +function startMqtt() + m = mqtt.Client("wordclock", 120) + -- on publish message receive event + m:on("message", function(client, topic, data) + print(topic .. ":" ) + if data ~= nil then + print(data) + if (data == "ON") then + mqttBrightness=100 + m:publish(mqttPrefix .. "/clock", "ON", 0, 0) + elseif (data == "OFF") then + mqttBrightness=0 + m:publish(mqttPrefix .. "/clock", "OFF", 0, 0) + else + if (tonumber(data) >= 0 and tonumber(data) <= 100) then + mqttBrightness=tonumber(data) + m:publish(mqttPrefix .. "/clock", tostring(data), 0, 0) + end + end + end + end) + + m:connect(mqttServer, 1883, 0, function(client) + print("[MQTT] connected") + mqttConnected = true + -- subscribe topic with qos = 0 + client:subscribe(mqttPrefix .. "/command", 0) + -- publish a message with data = hello, QoS = 0, retain = 0 + client:publish(mqttPrefix .. "/ip", tostring(wifi.sta.getip()), 0, 0) + end, + function(client, reason) + print("failed reason: " .. reason) + end) +end + +if (mqttServer ~= nil and mqttPrefix ~= nil) then + startMqtt() + print "Started MQTT client" + + tmr.alarm(5, 60000, 1 ,function() + m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) + end) +end \ No newline at end of file From df7f33bbf05af4587ebb832f906df071200994e8 Mon Sep 17 00:00:00 2001 From: Ollo Date: Mon, 7 Dec 2020 21:37:04 +0100 Subject: [PATCH 44/59] Brightness is controllable via MQTT --- mqtt.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mqtt.lua b/mqtt.lua index 845a6eb..67e0356 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -7,14 +7,14 @@ function startMqtt() if data ~= nil then print(data) if (data == "ON") then - mqttBrightness=100 + briPercent=100 m:publish(mqttPrefix .. "/clock", "ON", 0, 0) elseif (data == "OFF") then - mqttBrightness=0 + briPercent=0 m:publish(mqttPrefix .. "/clock", "OFF", 0, 0) else if (tonumber(data) >= 0 and tonumber(data) <= 100) then - mqttBrightness=tonumber(data) + briPercent=tonumber(data) m:publish(mqttPrefix .. "/clock", tostring(data), 0, 0) end end @@ -22,7 +22,7 @@ function startMqtt() end) m:connect(mqttServer, 1883, 0, function(client) - print("[MQTT] connected") + print("MQTT is connected") mqttConnected = true -- subscribe topic with qos = 0 client:subscribe(mqttPrefix .. "/command", 0) @@ -38,7 +38,7 @@ if (mqttServer ~= nil and mqttPrefix ~= nil) then startMqtt() print "Started MQTT client" - tmr.alarm(5, 60000, 1 ,function() + tmr.alarm(5, 30000, 1 ,function() m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) end) end \ No newline at end of file From de47de1e6fff0936d4a831a2675ed0668c631f6e Mon Sep 17 00:00:00 2001 From: Ollo Date: Tue, 8 Dec 2020 20:30:43 +0100 Subject: [PATCH 45/59] Always keep the LEDs at a minimum --- main.lua | 5 ++++- mqtt.lua | 12 +++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/main.lua b/main.lua index 1260ff7..6d10fd7 100644 --- a/main.lua +++ b/main.lua @@ -54,6 +54,9 @@ function displayTime() local words = display_timestat(time.hour, time.minute) if ((dim ~= nil) and (dim == "on")) then words.briPercent=briPercent + if (words.briPercent ~= nil and words.briPercent < 3) then + words.briPercent=3 + end else words.briPercent=nil end @@ -164,7 +167,7 @@ function normalOperation() end end) -- Start the time Thread - tmr.alarm(1, 10000, 1 ,function() + tmr.alarm(1, 5000, 1 ,function() displayTime() collectgarbage() end) diff --git a/mqtt.lua b/mqtt.lua index 67e0356..8f51f35 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -9,13 +9,16 @@ function startMqtt() if (data == "ON") then briPercent=100 m:publish(mqttPrefix .. "/clock", "ON", 0, 0) + displayTime() elseif (data == "OFF") then briPercent=0 m:publish(mqttPrefix .. "/clock", "OFF", 0, 0) + displayTime() else if (tonumber(data) >= 0 and tonumber(data) <= 100) then briPercent=tonumber(data) m:publish(mqttPrefix .. "/clock", tostring(data), 0, 0) + displayTime() end end end @@ -37,8 +40,11 @@ end if (mqttServer ~= nil and mqttPrefix ~= nil) then startMqtt() print "Started MQTT client" - - tmr.alarm(5, 30000, 1 ,function() + oldBrightness=0 + tmr.alarm(5, 10000, 1 ,function() + if (oldBrightness <> briPercent) then m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) + end + oldBrightness = briPercent end) -end \ No newline at end of file +end From b95f59ee7f08996575966afdd41f9e5345dbeb02 Mon Sep 17 00:00:00 2001 From: Ollo Date: Tue, 8 Dec 2020 20:45:18 +0100 Subject: [PATCH 46/59] Fix LUA syntax --- mqtt.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt.lua b/mqtt.lua index 8f51f35..7fca973 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -42,7 +42,7 @@ if (mqttServer ~= nil and mqttPrefix ~= nil) then print "Started MQTT client" oldBrightness=0 tmr.alarm(5, 10000, 1 ,function() - if (oldBrightness <> briPercent) then + if (oldBrightness ~= briPercent) then m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) end oldBrightness = briPercent From 1eaf05a1ad95219dc3e10e571c28f61ea6633909 Mon Sep 17 00:00:00 2001 From: Ollo Date: Tue, 8 Dec 2020 20:45:28 +0100 Subject: [PATCH 47/59] Add stop functionality --- main.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.lua b/main.lua index 6d10fd7..63718f3 100644 --- a/main.lua +++ b/main.lua @@ -188,6 +188,10 @@ function normalOperation() end +function stopWordclock() + for i=0,5,1 do tmr.stop(i) end +end + -------------------main program ----------------------------- ws2812.init() -- WS2812 LEDs initialized on GPIO2 From 10104b5d0e88b0a278b6dcaf20eff7654cd346c4 Mon Sep 17 00:00:00 2001 From: Ollo Date: Tue, 8 Dec 2020 21:11:53 +0100 Subject: [PATCH 48/59] Don call display function in MQTT module --- mqtt.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/mqtt.lua b/mqtt.lua index 7fca973..2c5d832 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -9,16 +9,13 @@ function startMqtt() if (data == "ON") then briPercent=100 m:publish(mqttPrefix .. "/clock", "ON", 0, 0) - displayTime() elseif (data == "OFF") then briPercent=0 m:publish(mqttPrefix .. "/clock", "OFF", 0, 0) - displayTime() else if (tonumber(data) >= 0 and tonumber(data) <= 100) then briPercent=tonumber(data) m:publish(mqttPrefix .. "/clock", tostring(data), 0, 0) - displayTime() end end end From 7e7a87f90aaa3688d9d9139df5815ce2679c6395 Mon Sep 17 00:00:00 2001 From: Ollo Date: Tue, 8 Dec 2020 21:51:14 +0100 Subject: [PATCH 49/59] Move startup procedure into one loop --- main.lua | 50 ++++++++++++++++++++++++++++++++------------------ mqtt.lua | 25 ++++++++++++++----------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/main.lua b/main.lua index 63718f3..a7c96ba 100644 --- a/main.lua +++ b/main.lua @@ -31,16 +31,20 @@ end function syncTimeFromInternet() ---ptbtime1.ptb.de + if (syncRunning == nil) then + syncRunning=true sntp.sync(sntpserverhostname, function(sec,usec,server) print('sync', sec, usec, server) displayTime() + syncRunning=nil end, function() print('failed!') + syncRunning=nil end ) + end end briPercent = 50 @@ -155,25 +159,34 @@ function normalOperation() print("Loading " .. mod) mydofile(mod) end - - tmr.alarm(2, 500, 0 ,function() - syncTimeFromInternet() - end) - tmr.alarm(3, 2000, 0 ,function() - if (startTelnetServer ~= nil) then - startTelnetServer() - else - print("NO Telent found") - end - end) - -- Start the time Thread + + setupCounter=5 tmr.alarm(1, 5000, 1 ,function() - displayTime() - collectgarbage() - end) + if (setupCounter > 4) then + syncTimeFromInternet() + setupCounter=setupCounter-1 + elseif (setupCounter > 3) then + if (startTelnetServer ~= nil) then + startTelnetServer() + else + print("NO Telent found") + end + setupCounter=setupCounter-1 + elseif (setupCounter > 2) then + if (startMqttClient ~= nil) then + startMqttClient() + else + print("NO Mqtt found") + end + setupCounter=setupCounter-1 + else + displayTime() + end + collectgarbage() + end) -- sync the time every 5 minutes - tmr.alarm(4, 300000, 1 ,function() + tmr.alarm(2, 300000, 1 ,function() syncTimeFromInternet() displayTime() end) @@ -198,7 +211,7 @@ ws2812.init() -- WS2812 LEDs initialized on GPIO2 if ( file.open("config.lua") ) then --- Normal operation wifi.setmode(wifi.STATION) - dofile("config.lua") + mydofile("config") normalOperation() else -- Logic for inital setup @@ -217,6 +230,7 @@ tmr.alarm(4, 500, 1 ,function() ws2812.write(ledBuf) if (btnCounter >= 110) then file.remove("config.lua") + file.remove("config.lc") node.restart() end end diff --git a/mqtt.lua b/mqtt.lua index 2c5d832..23c3ec1 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -34,14 +34,17 @@ function startMqtt() end) end -if (mqttServer ~= nil and mqttPrefix ~= nil) then - startMqtt() - print "Started MQTT client" - oldBrightness=0 - tmr.alarm(5, 10000, 1 ,function() - if (oldBrightness ~= briPercent) then - m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) - end - oldBrightness = briPercent - end) -end +function startMqttClient() + if (mqttServer ~= nil and mqttPrefix ~= nil) then + startMqtt() + print "Started MQTT client" + oldBrightness=0 + tmr.alarm(5, 10000, 1 ,function() + if (oldBrightness ~= briPercent) then + m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) + m:publish(mqttPrefix .. "/heap", tostring(node.heap()), 0, 0) + end + oldBrightness = briPercent + end) + end +end \ No newline at end of file From efe5b38e587b95a7f7fa168f1f4a9ac0a0f8fe90 Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 10 Dec 2020 21:11:35 +0100 Subject: [PATCH 50/59] Different loop cycles --- main.lua | 2 +- mqtt.lua | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/main.lua b/main.lua index a7c96ba..bc1de9a 100644 --- a/main.lua +++ b/main.lua @@ -186,7 +186,7 @@ function normalOperation() end) -- sync the time every 5 minutes - tmr.alarm(2, 300000, 1 ,function() + tmr.alarm(2, 300003, 1 ,function() syncTimeFromInternet() displayTime() end) diff --git a/mqtt.lua b/mqtt.lua index 23c3ec1..14e9bb9 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -39,12 +39,13 @@ function startMqttClient() startMqtt() print "Started MQTT client" oldBrightness=0 - tmr.alarm(5, 10000, 1 ,function() + tmr.alarm(5, 5001, 1 ,function() if (oldBrightness ~= briPercent) then m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) + else m:publish(mqttPrefix .. "/heap", tostring(node.heap()), 0, 0) end oldBrightness = briPercent end) end -end \ No newline at end of file +end From 6746d62567c748eff0314dc13d820a3dc8de0134 Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 10 Dec 2020 22:15:27 +0100 Subject: [PATCH 51/59] Formating added --- displayword.lua | 28 ++++++++++++++-------------- main.lua | 4 ++-- wordclock.lua | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/displayword.lua b/displayword.lua index 58752c9..04bfde2 100644 --- a/displayword.lua +++ b/displayword.lua @@ -3,20 +3,20 @@ local M do local updateColor = function (data) if (data.amountOfChars > 0) then - local div = tonumber(data.drawnCharacters/data.amountOfChars) - if (div < 1) then - return data.colorFg - elseif (div < 2) then - return data.colorMin1 - elseif (div < 3) then - return data.colorMin2 - elseif (div < 4) then - return data.colorMin3 - elseif (div < 5) then - return data.colorMin4 - else - return data.colorFg - end + local div = tonumber(data.drawnCharacters/data.amountOfChars) + if (div < 1) then + return data.colorFg + elseif (div < 2) then + return data.colorMin1 + elseif (div < 3) then + return data.colorMin2 + elseif (div < 4) then + return data.colorMin3 + elseif (div < 5) then + return data.colorMin4 + else + return data.colorFg + end else return data.colorFg end diff --git a/main.lua b/main.lua index bc1de9a..81ce2c4 100644 --- a/main.lua +++ b/main.lua @@ -72,9 +72,9 @@ function displayTime() invertRows=true end displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows) - if (displayword.data.drawnCharacters ~= nil) then + if (displayword.data.drawnCharacters ~= nil) then ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, displayword.data.drawnCharacters) - end + end end displayword = nil if (ledBuf ~= nil) then diff --git a/wordclock.lua b/wordclock.lua index ef3136e..0228c51 100755 --- a/wordclock.lua +++ b/wordclock.lua @@ -64,7 +64,7 @@ function display_timestat(hours, minutes, longmode) ret.before=1 elseif (minutes==9) then -- Hande if three quater or quater before is displayed - if (threequater ~= nil) then + if ((threequater ~= nil) and (threequater==true or threequater=="on")) then ret.threequater=1 else ret.quater = 1 From 7b02f1a447782e34b133db2798cea0134eb6bf27 Mon Sep 17 00:00:00 2001 From: Ollo Date: Sun, 13 Dec 2020 16:37:40 +0100 Subject: [PATCH 52/59] Seperate function to calculate the amount of characters to draw --- displayword.lua | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ main.lua | 10 +++----- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/displayword.lua b/displayword.lua index 04bfde2..a87c43d 100644 --- a/displayword.lua +++ b/displayword.lua @@ -305,12 +305,77 @@ if (words.fiveMin== 1) then collectgarbage() return buf end + +-- Count amount of characters to display +local countChars = function(words) + local characters = 0 + for key,value in pairs(words) do + if (value > 0) then + if (key == "it") then + characters = characters + 2 + elseif (key == "is") then + characters = characters + 3 + elseif (key == "fiveMin") then + characters = characters + 4 + elseif (key == "tenMin") then + characters = characters + 4 + elseif (key == "after") then + characters = characters + 4 + elseif (key == "before") then + characters = characters + 3 + elseif (key == "threeHour") then + characters = characters + 4 + elseif (key == "quater") then + characters = characters + 7 + elseif (key == "threequater") then + characters = characters + 11 + elseif (key == "half") then + characters = characters + 4 + elseif (key == "one") then + characters = characters + 3 + elseif (key == "oneLong") then + characters = characters + 4 + elseif (key == "two") then + characters = characters + 4 + elseif (key == "three") then + characters = characters + 4 + elseif (key == "four") then + characters = characters + 4 + elseif (key == "five") then + characters = characters + 4 + elseif (key == "six") then + characters = characters + 4 + elseif (key == "seven") then + characters = characters + 6 + elseif (key == "eight") then + characters = characters + 4 + elseif (key == "nine") then + characters = characters + 4 + elseif (key == "ten") then + characters = characters + 4 + elseif (key == "eleven") then + characters = characters + 3 + elseif (key == "twelve") then + characters = characters + 5 + elseif (key == "twenty") then + characters = characters + 7 + elseif (key == "clock") then + characters = characters + 3 + elseif (key == "sr_nc") then + characters = characters + 3 + end + end + end + return characters +end + M = { generateLEDs = generateLEDs, round = round, drawLEDs = drawLEDs, updateColor = updateColor, data = data, + countChars = countChars } end displayword = M diff --git a/main.lua b/main.lua index 81ce2c4..cd85dc3 100644 --- a/main.lua +++ b/main.lua @@ -70,16 +70,14 @@ function displayTime() local invertRows=false if ((inv46 ~= nil) and (inv46 == "on")) then invertRows=true - end - displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows) - if (displayword.data.drawnCharacters ~= nil) then - ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, displayword.data.drawnCharacters) - end + end + local characters = displayword.countChars(words) + ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, characters) end displayword = nil if (ledBuf ~= nil) then ws2812.write(ledBuf) - else + else if ((colorBg ~= nil) and (color ~= nil)) then ws2812.write(colorBg:rep(107) .. color:rep(3)) else From 9d764a3b8eb05aa47f093e05e0b2396719681faf Mon Sep 17 00:00:00 2001 From: Ollo Date: Mon, 14 Dec 2020 19:45:09 +0100 Subject: [PATCH 53/59] Added temperature measurement; but ESP is resetting due to RAM usage --- ds18b20.lua | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mqtt.lua | 46 ++++++++++++++++-- 2 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 ds18b20.lua diff --git a/ds18b20.lua b/ds18b20.lua new file mode 100644 index 0000000..fac19b4 --- /dev/null +++ b/ds18b20.lua @@ -0,0 +1,138 @@ +-------------------------------------------------------------------------------- +-- DS18B20 one wire module for NODEMCU +-- NODEMCU TEAM +-- LICENCE: http://opensource.org/licenses/MIT +-- Vowstar +-- 2015/02/14 sza2 Fix for negative values +-------------------------------------------------------------------------------- + +-- Set module name as parameter of require +local modname = ... +local M = {} +_G[modname] = M +-------------------------------------------------------------------------------- +-- Local used variables +-------------------------------------------------------------------------------- +-- DS18B20 dq pin +local pin = nil +-- DS18B20 default pin +local defaultPin = 9 +-------------------------------------------------------------------------------- +-- Local used modules +-------------------------------------------------------------------------------- +-- Table module +local table = table +-- String module +local string = string +-- One wire module +local ow = ow +-- Timer module +local tmr = tmr +-- Limited to local environment +setfenv(1,M) +-------------------------------------------------------------------------------- +-- Implementation +-------------------------------------------------------------------------------- +C = 0 +F = 1 +K = 2 +function setup(dq) + pin = dq + if(pin == nil) then + pin = defaultPin + end + ow.setup(pin) +end + +function addrs() + setup(pin) + tbl = {} + ow.reset_search(pin) + repeat + addr = ow.search(pin) + if(addr ~= nil) then + table.insert(tbl, addr) + end + tmr.wdclr() + until (addr == nil) + ow.reset_search(pin) + return tbl +end + +function readNumber(addr, unit) + result = nil + setup(pin) + flag = false + if(addr == nil) then + ow.reset_search(pin) + count = 0 + repeat + count = count + 1 + addr = ow.search(pin) + tmr.wdclr() + until((addr ~= nil) or (count > 100)) + ow.reset_search(pin) + end + if(addr == nil) then + return result + end + crc = ow.crc8(string.sub(addr,1,7)) + if (crc == addr:byte(8)) then + if ((addr:byte(1) == 0x10) or (addr:byte(1) == 0x28)) then + -- print("Device is a DS18S20 family device.") + ow.reset(pin) + ow.select(pin, addr) + ow.write(pin, 0x44, 1) + -- tmr.delay(1000000) + present = ow.reset(pin) + ow.select(pin, addr) + ow.write(pin,0xBE,1) + -- print("P="..present) + data = nil + data = string.char(ow.read(pin)) + for i = 1, 8 do + data = data .. string.char(ow.read(pin)) + end + -- print(data:byte(1,9)) + crc = ow.crc8(string.sub(data,1,8)) + -- print("CRC="..crc) + if (crc == data:byte(9)) then + t = (data:byte(1) + data:byte(2) * 256) + if (t > 32767) then + t = t - 65536 + end + if(unit == nil or unit == C) then + t = t * 625 + elseif(unit == F) then + t = t * 1125 + 320000 + elseif(unit == K) then + t = t * 625 + 2731500 + else + return nil + end + t = t / 100 + -- print("Temperature="..t1.."."..t2.." Centigrade") + -- result = t1.."."..t2 + return t + end + tmr.wdclr() + else + -- print("Device family is not recognized.") + end + else + -- print("CRC is not valid!") + end + return result +end + +function read(addr, unit) + t = readNumber(addr, unit) + if (t == nil) then + return nil + else + return t + end +end + +-- Return module table +return M diff --git a/mqtt.lua b/mqtt.lua index 14e9bb9..4d054c2 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -1,3 +1,7 @@ +-- Global variable +t=nil +mqttConnected = false + -- MQTT extension function startMqtt() m = mqtt.Client("wordclock", 120) @@ -34,18 +38,50 @@ function startMqtt() end) end +function readTemp() + if (t ~= nil) then + addrs=t.addrs() + -- Total DS18B20 numbers + sensors=table.getn(addrs) + local temp1=0 + if (sensors >= 1) then + temp1=t.read(addrs[1]) + end + return temp1 + else + return nil + end +end + function startMqttClient() if (mqttServer ~= nil and mqttPrefix ~= nil) then startMqtt() print "Started MQTT client" + if (file.open("ds18b20.lc")) then + t=require("ds18b20") + t.setup(2) -- GPIO4 + readTemp() -- read once, to setup chip + print "Setup temperature" + end + oldBrightness=0 + oldTemp=0 tmr.alarm(5, 5001, 1 ,function() - if (oldBrightness ~= briPercent) then - m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) - else - m:publish(mqttPrefix .. "/heap", tostring(node.heap()), 0, 0) + if (mqttConnected) then + local temp = nil + if (t ~= nil) then + temp=readTemp() + end + if (oldBrightness ~= briPercent) then + m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) + elseif (temp ~= nil and temp ~= oldTemp) then + oldTemp = temp + m:publish(mqttPrefix .. "/temp", tostring(temp), 0, 0) + else + m:publish(mqttPrefix .. "/heap", tostring(node.heap()), 0, 0) + end + oldBrightness = briPercent end - oldBrightness = briPercent end) end end From d1b5e67ce6d70f3958b20b87975dc006ae0be826 Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 14 Jan 2021 21:16:12 +0100 Subject: [PATCH 54/59] Removed temperature reading --- ds18b20.lua | 138 ---------------------------------------------------- mqtt.lua | 25 ---------- 2 files changed, 163 deletions(-) delete mode 100644 ds18b20.lua diff --git a/ds18b20.lua b/ds18b20.lua deleted file mode 100644 index fac19b4..0000000 --- a/ds18b20.lua +++ /dev/null @@ -1,138 +0,0 @@ --------------------------------------------------------------------------------- --- DS18B20 one wire module for NODEMCU --- NODEMCU TEAM --- LICENCE: http://opensource.org/licenses/MIT --- Vowstar --- 2015/02/14 sza2 Fix for negative values --------------------------------------------------------------------------------- - --- Set module name as parameter of require -local modname = ... -local M = {} -_G[modname] = M --------------------------------------------------------------------------------- --- Local used variables --------------------------------------------------------------------------------- --- DS18B20 dq pin -local pin = nil --- DS18B20 default pin -local defaultPin = 9 --------------------------------------------------------------------------------- --- Local used modules --------------------------------------------------------------------------------- --- Table module -local table = table --- String module -local string = string --- One wire module -local ow = ow --- Timer module -local tmr = tmr --- Limited to local environment -setfenv(1,M) --------------------------------------------------------------------------------- --- Implementation --------------------------------------------------------------------------------- -C = 0 -F = 1 -K = 2 -function setup(dq) - pin = dq - if(pin == nil) then - pin = defaultPin - end - ow.setup(pin) -end - -function addrs() - setup(pin) - tbl = {} - ow.reset_search(pin) - repeat - addr = ow.search(pin) - if(addr ~= nil) then - table.insert(tbl, addr) - end - tmr.wdclr() - until (addr == nil) - ow.reset_search(pin) - return tbl -end - -function readNumber(addr, unit) - result = nil - setup(pin) - flag = false - if(addr == nil) then - ow.reset_search(pin) - count = 0 - repeat - count = count + 1 - addr = ow.search(pin) - tmr.wdclr() - until((addr ~= nil) or (count > 100)) - ow.reset_search(pin) - end - if(addr == nil) then - return result - end - crc = ow.crc8(string.sub(addr,1,7)) - if (crc == addr:byte(8)) then - if ((addr:byte(1) == 0x10) or (addr:byte(1) == 0x28)) then - -- print("Device is a DS18S20 family device.") - ow.reset(pin) - ow.select(pin, addr) - ow.write(pin, 0x44, 1) - -- tmr.delay(1000000) - present = ow.reset(pin) - ow.select(pin, addr) - ow.write(pin,0xBE,1) - -- print("P="..present) - data = nil - data = string.char(ow.read(pin)) - for i = 1, 8 do - data = data .. string.char(ow.read(pin)) - end - -- print(data:byte(1,9)) - crc = ow.crc8(string.sub(data,1,8)) - -- print("CRC="..crc) - if (crc == data:byte(9)) then - t = (data:byte(1) + data:byte(2) * 256) - if (t > 32767) then - t = t - 65536 - end - if(unit == nil or unit == C) then - t = t * 625 - elseif(unit == F) then - t = t * 1125 + 320000 - elseif(unit == K) then - t = t * 625 + 2731500 - else - return nil - end - t = t / 100 - -- print("Temperature="..t1.."."..t2.." Centigrade") - -- result = t1.."."..t2 - return t - end - tmr.wdclr() - else - -- print("Device family is not recognized.") - end - else - -- print("CRC is not valid!") - end - return result -end - -function read(addr, unit) - t = readNumber(addr, unit) - if (t == nil) then - return nil - else - return t - end -end - --- Return module table -return M diff --git a/mqtt.lua b/mqtt.lua index 4d054c2..ae95c87 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -38,32 +38,10 @@ function startMqtt() end) end -function readTemp() - if (t ~= nil) then - addrs=t.addrs() - -- Total DS18B20 numbers - sensors=table.getn(addrs) - local temp1=0 - if (sensors >= 1) then - temp1=t.read(addrs[1]) - end - return temp1 - else - return nil - end -end - function startMqttClient() if (mqttServer ~= nil and mqttPrefix ~= nil) then startMqtt() print "Started MQTT client" - if (file.open("ds18b20.lc")) then - t=require("ds18b20") - t.setup(2) -- GPIO4 - readTemp() -- read once, to setup chip - print "Setup temperature" - end - oldBrightness=0 oldTemp=0 tmr.alarm(5, 5001, 1 ,function() @@ -74,9 +52,6 @@ function startMqttClient() end if (oldBrightness ~= briPercent) then m:publish(mqttPrefix .. "/brightness", tostring(briPercent), 0, 0) - elseif (temp ~= nil and temp ~= oldTemp) then - oldTemp = temp - m:publish(mqttPrefix .. "/temp", tostring(temp), 0, 0) else m:publish(mqttPrefix .. "/heap", tostring(node.heap()), 0, 0) end From fb2556c62c6c6cd18e846640cdbaee3863bdace1 Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 14 Jan 2021 21:27:13 +0100 Subject: [PATCH 55/59] Refactor MQTT function names --- mqtt.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/mqtt.lua b/mqtt.lua index ae95c87..1df27b8 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -1,9 +1,9 @@ -- Global variable -t=nil +m=nil mqttConnected = false -- MQTT extension -function startMqtt() +function registerMqtt() m = mqtt.Client("wordclock", 120) -- on publish message receive event m:on("message", function(client, topic, data) @@ -30,17 +30,20 @@ function startMqtt() mqttConnected = true -- subscribe topic with qos = 0 client:subscribe(mqttPrefix .. "/command", 0) - -- publish a message with data = hello, QoS = 0, retain = 0 - client:publish(mqttPrefix .. "/ip", tostring(wifi.sta.getip()), 0, 0) + tmr.alarm(3, 500, 0, function() + -- publish a message with data = hello, QoS = 0, retain = 0 + client:publish(mqttPrefix .. "/ip", tostring(wifi.sta.getip()), 0, 0) + end) end, function(client, reason) print("failed reason: " .. reason) + m=nil end) end -function startMqttClient() +function registerMqttClient() if (mqttServer ~= nil and mqttPrefix ~= nil) then - startMqtt() + registerMqtt() print "Started MQTT client" oldBrightness=0 oldTemp=0 From 8d6fc180a33ba1c8252e7681412d7779d59a327c Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 14 Jan 2021 21:32:12 +0100 Subject: [PATCH 56/59] Background color is adjustable --- mqtt.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mqtt.lua b/mqtt.lua index 1df27b8..eacea11 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -16,6 +16,11 @@ function registerMqtt() elseif (data == "OFF") then briPercent=0 m:publish(mqttPrefix .. "/clock", "OFF", 0, 0) + elseif (data:sub(1,1) == "#" and data:len() == 7) then + red = tonumber(data:sub(2,3), 16) + green = tonumber(data:sub(4,5), 16) + blue = tonumber(data:sub(6,7), 16) + colorBg=string.char(red, green, blue) else if (tonumber(data) >= 0 and tonumber(data) <= 100) then briPercent=tonumber(data) @@ -37,7 +42,7 @@ function registerMqtt() end, function(client, reason) print("failed reason: " .. reason) - m=nil + mqttConnected = false end) end From 7195c0899221cd895176ee39cc4efd53eac95bc1 Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 14 Jan 2021 21:51:29 +0100 Subject: [PATCH 57/59] Fixed Mqtt function names --- mqtt.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt.lua b/mqtt.lua index eacea11..2959cdd 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -46,7 +46,7 @@ function registerMqtt() end) end -function registerMqttClient() +function startMqttClient() if (mqttServer ~= nil and mqttPrefix ~= nil) then registerMqtt() print "Started MQTT client" From 30343fe638bd99f533bd46e050027db69e337ba9 Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 14 Jan 2021 21:55:51 +0100 Subject: [PATCH 58/59] Made Telnetserver optional --- init.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 1b60740..3023999 100644 --- a/init.lua +++ b/init.lua @@ -36,8 +36,10 @@ end function mydofile(mod) if (file.open(mod .. ".lua")) then dofile( mod .. ".lua") - else + elseif (file.open(mod .. ".lc")) then dofile(mod .. ".lc") + else + print("Error: " .. mod) end end From b44eb6f13eb81db5053036c9d3055c71cad957a4 Mon Sep 17 00:00:00 2001 From: Ollo Date: Thu, 14 Jan 2021 22:14:59 +0100 Subject: [PATCH 59/59] Background can be changed via MQTT --- displayword.lua | 7 ++++++- main.lua | 2 +- mqtt.lua | 16 +++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/displayword.lua b/displayword.lua index a87c43d..d725b84 100644 --- a/displayword.lua +++ b/displayword.lua @@ -54,7 +54,7 @@ end local data={} -- Module displaying of the words -local generateLEDs = function(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4, invertRows, amountOfChars) +local generateLEDs = function(words, colorBg, colorFg, colorMin1, colorMin2, colorMin3, colorMin4, invertRows, amountOfChars) -- Set the local variables needed for the colored progress bar if (words == nil) then return nil @@ -103,9 +103,14 @@ local generateLEDs = function(words, colorFg, colorMin1, colorMin2, colorMin3, c end data.drawnCharacters=0 local charsPerLine=11 + -- Space / background has no color by default local space=string.char(0,0,0) + if (colorBg ~= nil) then + space = colorBg + end + -- Set the foreground color as the default color local buf=data.colorFg local line=space diff --git a/main.lua b/main.lua index cd85dc3..2497c2b 100644 --- a/main.lua +++ b/main.lua @@ -72,7 +72,7 @@ function displayTime() invertRows=true end local characters = displayword.countChars(words) - ledBuf = displayword.generateLEDs(words, color, color1, color2, color3, color4, invertRows, characters) + ledBuf = displayword.generateLEDs(words, colorBg, color, color1, color2, color3, color4, invertRows, characters) end displayword = nil if (ledBuf ~= nil) then diff --git a/mqtt.lua b/mqtt.lua index 2959cdd..a1141ee 100644 --- a/mqtt.lua +++ b/mqtt.lua @@ -16,15 +16,21 @@ function registerMqtt() elseif (data == "OFF") then briPercent=0 m:publish(mqttPrefix .. "/clock", "OFF", 0, 0) - elseif (data:sub(1,1) == "#" and data:len() == 7) then - red = tonumber(data:sub(2,3), 16) - green = tonumber(data:sub(4,5), 16) - blue = tonumber(data:sub(6,7), 16) - colorBg=string.char(red, green, blue) + elseif (data:sub(1,1) == "#" and data:len() == 7) then + red = tonumber(data:sub(2,3), 16) + green = tonumber(data:sub(4,5), 16) + blue = tonumber(data:sub(6,7), 16) + colorBg=string.char(red, green, blue) + print("Updated BG: " .. tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue) ) + if (displayTime~= nil) then + displayTime() + end else if (tonumber(data) >= 0 and tonumber(data) <= 100) then briPercent=tonumber(data) m:publish(mqttPrefix .. "/clock", tostring(data), 0, 0) + else + print "Unknown MQTT command" end end end