Merge pull request #1 from C3MA/master

Merge C3MA code back into the source
This commit is contained in:
0110 2021-01-31 12:01:09 +01:00 committed by GitHub
commit 2fc241a6af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 6650 additions and 762 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
wlancfg.lua
config.lua
*.swp
unit/testTimesMarchOctoberWithAllSeconds.lua

View File

@ -1,6 +1,7 @@
# ESP Wordclock
## Setup
### Initial Setup
Install the firmware on the ESP:
The ESP must be set into the bootloader mode, like [this](https://www.ccc-mannheim.de/wiki/ESP8266#Boot_Modi)
@ -10,11 +11,10 @@ cd os/
./flash.sh ttyUSB0
</pre>
Reboot the ESP, with a serial terminal,
format the filesystem with the following command and reboot it:
Connect to the ESP via a terminal emulator like screen using a baud rate of 115200. Then format the filesystem and reboot the ESP with the following commands:
<pre>
file.format()
node.reboot()
node.restart()
</pre>
Then disconnect the serial terminal and copy the required files to the microcontroller:
@ -22,5 +22,60 @@ Then disconnect the serial terminal and copy the required files to the microcont
./tools/initialFlash.sh /dev/ttyUSB0
</pre>
## Internal Setup
### Upgrade
Determine the IP address of your clock and execute the following script:
<pre>
./tools/remoteFlash.sh IP-Address
</pre>
## Hardware Setup
Mandatory:
* GPIO2 LEDs
* GPIO0 Bootloader (at start)
* GPIO0 factory reset (long during operation)
Optinal:
* ADC VT93N2, 48k light resistor
* GPIO4 DS18B20 Temperatur sensor
## MQTT Interface
### Status
* **basetopic**/brightness **Current brightness in percent**
* **basetopic**/background **Current background color**
* **basetopic**/row1 **Current background color**
* **basetopic**/temp **Temperatur**
### Commands
* **basetopic**/cmd/single
* ON **Set brightness to 100%**
* OFF **Set brightness to 0%**
* 0-100 **Set brightness to given value**
* #rrggbb **Background color is set to hex representation of red, green and blue**
* 0-255,0-255,0-255 **Background color is set to decimal representation of red, green an blue**
* **basetopic**/cmd/telnet
* ignored **Stop MQTT server and start telnetserver at port 23**
* **basetopic**/cmd/row1
* 0-255,0-255,0-255 **Background color is set to decimal representation of red, green an blue**
* **basetopic**/cmd/row1
* 0-255,0-255,0-255 **Background color is set to decimal representation of red, green an blue**
* **basetopic**/cmd/row2
* 0-255,0-255,0-255 **Background color is set to decimal representation of red, green an blue**
* **basetopic**/cmd/row3
* 0-255,0-255,0-255 **Background color is set to decimal representation of red, green an blue**
* For all rows...
* **basetopic**/cmd/row10
* 0-255,0-255,0-255 **Background color is set to decimal representation of red, green an blue**
## OpenHAB2
Tested MQTT with binding-mqtt 2.5.x
### Configuration
```
Thing mqtt:topic:wordclock "Wordclock" (mqtt:broker) @ "MQTT" {
Channels:
Type dimmer : dim "Dimming" [ stateTopic="basetopic/brightness", commandTopic="basetopic/cmd/single" ]
Type string : cmd "Command" [ commandTopic="basetopic/cmd/single" ]
Type switch : active "Active" [ commandTopic="basetopic/cmd/single" ]
Type colorRGB : background "Background" [ stateTopic="basetopic/background", commandTopic="basetopic/cmd/single", on="28,0,0", off="0,0,0" ]
}
```

186
commands.lua Normal file
View File

@ -0,0 +1,186 @@
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
(_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, ")
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
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 (_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
if ((_inv46 == true) or (_inv == "on")) then
inv46 = "on"
elseif ((_inv46 == false) or (_inv == "off")) then
inv46 = "off"
else
inv46 = "off"
end
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("NTP = " .. tostring(sntpserver))
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))
local configFile="config.lua"
-- 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")
else
print("Cannot rename " .. configFile .. ".new")
end
end

View File

@ -1,26 +1,18 @@
-- Module filling a buffer, sent to the LEDs
function updateColor(data)
if (data.usedCharacters <= data.charsPerMinute) then
if (data.words.min1 == 1 or data.words.min2 == 1 or data.words.min3 == 1 or data.words.min4 == 1) then
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
else
return data.colorFg
end
elseif (data.usedCharacters <= data.charsPerMinute*2) then
if (data.words.min2 == 1 or data.words.min3 == 1 or data.words.min4 == 1) then
elseif (div < 3) then
return data.colorMin2
else
return data.colorFg
end
elseif (data.usedCharacters <= data.charsPerMinute*3) then
if (data.words.min3 == 1 or data.words.min4 == 1) then
elseif (div < 4) then
return data.colorMin3
else
return data.colorFg
end
elseif (data.usedCharacters > data.charsPerMinute*3) then
if (data.words.min4 == 1) then
elseif (div < 5) then
return data.colorMin4
else
return data.colorFg
@ -30,7 +22,10 @@ function updateColor(data)
end
end
function drawLEDs(data, numberNewChars)
local drawLEDs = function(data, numberNewChars)
if (numberNewChars == nil) then
numberNewChars=0
end
local tmpBuf=nil
for i=1,numberNewChars do
if (tmpBuf == nil) then
@ -38,165 +33,311 @@ function drawLEDs(data, numberNewChars)
else
tmpBuf=tmpBuf .. updateColor(data)
end
data.usedCharacters=data.usedCharacters+1
data.drawnCharacters=data.drawnCharacters+1
end
return tmpBuf
end
-- Utility function for round
local round = function(num)
under = math.floor(num)
upper = math.floor(num) + 1
underV = -(under - num)
upperV = upper - num
if (upperV > underV) then
return under
else
return upper
end
end
local data={}
-- Module displaying of the words
function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4, characters)
local generateLEDs = function(words, colorBg, colorFg, colorMin1, colorMin2, colorMin3, colorMin4, invertRows, amountOfChars)
-- Set the local variables needed for the colored progress bar
data={}
data.charsPerMinute=math.floor(characters/3) -- devide by three (Minute 1 to Minute 3, Minute 4 takes the last chars)
data.words=words
if (words == nil) then
return nil
end
if (invertRows == nil) then
invertRows=false
end
local minutes=1
if (words.min1 == 1) then
minutes = minutes + 1
elseif (words.min2 == 1) then
minutes = minutes + 2
elseif (words.min3 == 1) then
minutes = minutes + 3
elseif (words.min4 == 1) then
minutes = minutes + 4
end
-- always set a foreground value
if (colorFg == nil) then
colorFg = string.char(255,255,255)
end
if (amountOfChars ~= nil) then
data.amountOfChars = amountOfChars/minutes
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)
print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(words.briPercent) .. "% current: " .. tostring(per) .. "%")
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=colorFg
data.colorMin1=colorMin1
data.colorMin2=colorMin2
data.colorMin3=colorMin3
data.colorMin4=colorMin4
data.usedCharacters=0
end
data.drawnCharacters=0
local charsPerLine=11
-- Space / background has no color by default
local space=string.char(0,0,0)
-- update the background color, if set
-- Background color must always be set
if (colorBg ~= nil) then
space = colorBg
else
colorBg = space
end
-- Set the foreground color as the default color
local buf=colorFg
local buf=data.colorFg
local line=space
-- line 1----------------------------------------------
if (words.itis == 1) then
if (rowbgColor[1] ~= nil) then
space = rowbgColor[1]
end
if (words.it==1) then
buf=drawLEDs(data,2) -- ES
print(tostring(buf))
else
buf=space:rep(2)
end
-- K fill character
buf=buf .. space:rep(1)
if (words.is == 1) then
buf=buf .. drawLEDs(data,3) -- IST
else
buf=buf .. space:rep(3)
end
-- L fill character
buf=buf .. space:rep(1)
else
buf=space:rep(7)
end
if (words.fiveMin== 1) then
buf= buf .. drawLEDs(data,4) -- FUENF
else
buf= buf .. space:rep(4)
end
-- line 2-- even row (so inverted) --------------------
if (words.twenty == 1) then
buf= buf .. drawLEDs(data,7) -- ZWANZIG
if (rowbgColor[2] ~= nil) then
space = rowbgColor[2]
else
buf= buf .. space:rep(7)
space = colorBg
end
if (words.tenMin == 1) then
buf= buf .. drawLEDs(data,4) -- ZEHN
line= drawLEDs(data,4) -- ZEHN
else
buf= buf .. space:rep(4)
line= space:rep(4)
end
-- line3----------------------------------------------
if (words.threequater == 1) then
buf= buf .. drawLEDs(data,11) -- Dreiviertel
elseif (words.quater == 1) then
buf= buf .. space:rep(4)
buf= buf .. drawLEDs(data,7) -- VIERTEL
if (words.twenty == 1) then
line= line .. drawLEDs(data,7) -- ZWANZIG
else
buf= buf .. space:rep(11)
line= line .. space:rep(7)
end
--line 4-------- even row (so inverted) -------------
if (words.before == 1) then
buf=buf .. space:rep(2)
buf= buf .. drawLEDs(data,3) -- VOR
else
buf= buf .. space:rep(5)
end
if (words.after == 1) then
buf= buf .. drawLEDs(data,4) -- NACH
buf= buf .. space:rep(2) -- TG
else
buf= buf .. space:rep(6)
end
------------------------------------------------
if (words.half == 1) then
buf= buf .. drawLEDs(data,4) -- HALB
buf= buf .. space:rep(1) -- X
else
buf= buf .. space:rep(5)
end
if (words.twelve == 1) then
buf= buf .. drawLEDs(data,5) -- ZWOELF
buf= buf .. space:rep(1) -- P
else
buf= buf .. space:rep(6)
end
------------even row (so inverted) ---------------------
if (words.seven == 1) then
buf= buf .. drawLEDs(data,6) -- SIEBEN
buf= buf .. space:rep(5)
elseif (words.oneLong == 1) then
buf= buf .. space:rep(5)
buf= buf .. drawLEDs(data,4) -- EINS
buf= buf .. space:rep(2)
elseif (words.one == 1) then
buf= buf .. space:rep(6)
buf= buf .. drawLEDs(data,3) -- EIN
buf= buf .. space:rep(2)
elseif (words.two == 1) then
buf= buf .. space:rep(7)
buf= buf .. drawLEDs(data,4) -- ZWEI
else
buf= buf .. space:rep(11)
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
else
buf= buf .. space:rep(11)
end
------------even row (so inverted) ---------------------
if (words.four == 1) then
buf= buf .. drawLEDs(data,4) -- VIER
buf= buf .. space:rep(7)
elseif (words.nine == 1) then
buf= buf .. space:rep(4)
buf= buf .. drawLEDs(data,4) -- NEUN
buf= buf .. space:rep(3)
elseif (words.eleven == 1) then
buf= buf .. space:rep(8)
buf= buf .. drawLEDs(data,3) -- ELEVEN
else
buf= buf .. space:rep(11)
end
------------------------------------------------
if (words.eight == 1) then
buf= buf .. space:rep(1)
buf= buf .. drawLEDs(data,4) -- ACHT
buf= buf .. space:rep(6)
elseif (words.ten == 1) then
buf= buf .. space:rep(5)
buf= buf .. drawLEDs(data,4) -- ZEHN
buf= buf .. space:rep(2)
else
buf= buf .. space:rep(11)
end
------------even row (so inverted) ---------------------
if (words.clock == 1) then
buf= buf .. drawLEDs(data,3) -- UHR
else
buf= buf .. space:rep(3)
end
if (words.six == 1) then
buf= buf .. space:rep(2)
buf= buf .. drawLEDs(data,5) -- SECHS
buf= buf .. space:rep(1)
else
buf= buf .. space:rep(8)
-- fill, the buffer
for i = 0,10 do
buf = buf .. line:sub((11-i)*3-2,(11-i)*3)
end
-- line3----------------------------------------------
if (rowbgColor[3] ~= nil) then
space = rowbgColor[3]
else
space = colorBg
end
if (words.threequater == 1) then
line= drawLEDs(data,11) -- Dreiviertel
elseif (words.quater == 1) then
line= space:rep(4)
line= line .. drawLEDs(data,7) -- VIERTEL
else
line= space:rep(11)
end
-- fill, the buffer
buf = buf .. line
--line 4-------- even row (so inverted) -------------
if (rowbgColor[4] ~= nil) then
space = rowbgColor[4]
else
space = colorBg
end
if (words.after == 1) then
line= space:rep(2) -- TG
line= line .. drawLEDs(data,4) -- NACH
else
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
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 (rowbgColor[5] ~= nil) then
space = rowbgColor[5]
else
space = colorBg
end
if (words.half == 1) then
line= drawLEDs(data,4) -- HALB
line= line .. space:rep(1) -- X
else
line= space:rep(5)
end
if (words.twelve == 1) then
line= line .. drawLEDs(data,5) -- ZWOELF
line= line .. space:rep(1) -- P
else
line= line .. space:rep(6)
end
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 (rowbgColor[6] ~= nil) then
space = rowbgColor[6]
else
space = colorBg
end
if (words.seven == 1) then
line= space:rep(5)
line= line .. drawLEDs(data,6) -- SIEBEN
elseif (words.oneLong == 1) then
line= space:rep(2)
line= line .. drawLEDs(data,4) -- EINS
line= line .. space:rep(5)
elseif (words.one == 1) then
line= space:rep(2)
line= line .. drawLEDs(data,3) -- EIN
line= line .. space:rep(6)
elseif (words.two == 1) then
line= drawLEDs(data,4) -- ZWEI
line= line .. space:rep(7)
else
line= space:rep(11)
end
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 (rowbgColor[7] ~= nil) then
space = rowbgColor[7]
else
space = colorBg
end
if (words.three == 1) then
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
line= space:rep(11)
end
buf = buf .. line
------------even row (so inverted) ---------------------
if (rowbgColor[8] ~= nil) then
space = rowbgColor[8]
else
space = colorBg
end
if (words.four == 1) then
line= space:rep(7)
line= line .. drawLEDs(data,4) -- VIER
elseif (words.nine == 1) then
line= space:rep(3)
line= line .. drawLEDs(data,4) -- NEUN
line= line .. space:rep(4)
elseif (words.eleven == 1) then
line= drawLEDs(data,3) -- ELEVEN
line= line .. space:rep(8)
else
line= space:rep(11)
end
for i = 0,10 do
buf = buf .. line:sub((11-i)*3-2,(11-i)*3)
end
------------------------------------------------
if (rowbgColor[9] ~= nil) then
space = rowbgColor[9]
else
space = colorBg
end
if (words.eight == 1) then
line= space:rep(1)
line= line .. drawLEDs(data,4) -- ACHT
line= line .. space:rep(6)
elseif (words.ten == 1) then
line= space:rep(5)
line= line .. drawLEDs(data,4) -- ZEHN
line= line .. space:rep(2)
else
line= space:rep(11)
end
buf = buf .. line
------------even row (so inverted) ---------------------
if (rowbgColor[10] ~= nil) then
space = rowbgColor[10]
else
space = colorBg
end
if (words.six == 1) then
line= space:rep(1)
line= line .. drawLEDs(data,5) -- SECHS
line= line .. space:rep(2)
else
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
@ -220,3 +361,77 @@ function generateLEDs(words, colorFg, colorMin1, colorMin2, colorMin3, colorMin4
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

138
ds18b20.lua Normal file
View File

@ -0,0 +1,138 @@
--------------------------------------------------------------------------------
-- DS18B20 one wire module for NODEMCU
-- NODEMCU TEAM
-- LICENCE: http://opensource.org/licenses/MIT
-- Vowstar <vowstar@nodemcu.com>
-- 2015/02/14 sza2 <sza2trash@gmail.com> 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

View File

@ -1,22 +0,0 @@
<html>
<head><title>WordClock Setup Page</title>
</head><body>
<h1>Welcome to the WordClock</h1>
<form action="" method="POST">
<table>
<tr><th>WIFI-SSID</b></th><td><input name="ssid" value="${SSID}"></td><td /></tr>
<tr><th>WIFI-Password</th><td><input name="password"></td><td /></tr>
<tr><th>SNTP Server</th><td><input name="sntpserver" value="${sntpserverhostname}" ></td><td>ntp server to sync the time</tr>
<tr><th>Offset to UTC time</th><td><input type="number" name="timezoneoffset" value="${TIMEZONEOFFSET}"></td><td>Define the offset to UTC time in hours. E.g +1</tr>
<tr><th>Color</th><td><input type="color" name="fcolor" value="${HEXCOLOR}"></td><td /></tr>
<tr><th>1. Minute Color</th><td><input type=\"color\" name=\"colorMin1\" value=\"" .. hexColor1 .. "\"></td><td /></tr>"
<tr><th>2. Minute Color</th><td><input type=\"color\" name=\"colorMin2\" value=\"" .. hexColor2 .. "\"></td><td /></tr>"
<tr><th>3. Minute Color</th><td><input type=\"color\" name=\"colorMin3\" value=\"" .. hexColor3 .. "\"></td><td /></tr>"
<tr><th>4. Minute Color</th><td><input type=\"color\" name=\"colorMin4\" value=\"" .. hexColor4 .. "\"></td><td /></tr>"
<tr><th>Three quater</th><td><input type="checkbox" name="threequater" ${THREEQUARTERCHECKED}></td><td>Dreiviertel Joa/nei</td></tr>
<tr><th>ColorMode</th><td><input type="checkbox" name="colorMode" ${COLORMODECHECKED}></td><td>If checked, words are dark, rest is colored</td></tr>
<tr><td colspan="3"><div align="center"><input type="submit" value="Save Configuration" onclick="this.value='Submitting ..';this.disabled='disabled'; this.form.submit();"></div></td></tr>
<tr><td colspan="3"><div align="center"><input type="submit" name="action" value="Reboot"></div></td></tr>
</table></form>
$ADDITIONAL_LINE
</body></html>

View File

@ -7,11 +7,13 @@ counter1=0
ws2812.write(string.char(0,0,0):rep(114))
tmr.alarm(2, 85, 1, function()
counter1=counter1+1
ws2812.write(string.char(128,0,0):rep(counter1) .. string.char(0,0,0):rep(MAXLEDS - (counter1*2)) .. string.char(0,0,64):rep(counter1))
spaceLeds = math.max(MAXLEDS - (counter1*2), 0)
ws2812.write(string.char(128,0,0):rep(counter1) .. string.char(0,0,0):rep(spaceLeds) .. string.char(0,0,64):rep(counter1))
end)
local blacklistfile="init.lua config.lua config.lua.new"
local blacklistfile="init.lua config.lua config.lua.new webpage.html"
function recompileAll()
for i=0,5 do tmr.stop(i) end
-- compile all files
l = file.list();
for k,_ in pairs(l) do
@ -34,15 +36,28 @@ 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
tmr.alarm(1, 5000, 0, function()
tmr.stop(2)
if (file.open("main.lua")) then
if (
(file.open("main.lua")) or
(file.open("timecore.lua")) or
(file.open("wordclock.lua")) or
(file.open("displayword.lua")) or
(file.open("mqtt.lua")) or
(file.open("ds18b20.lua")) or
(file.open("telnet.lua"))
) then
c = string.char(0,128,0)
w = string.char(0,0,0)
ws2812.write(w:rep(4) .. c .. w:rep(15) .. c .. w:rep(9) .. c .. w:rep(30) .. c .. w:rep(41) .. c )
recompileAll()
print("Rebooting ...")
-- reboot repairs everything
@ -54,3 +69,4 @@ tmr.alarm(1, 5000, 0, function()
print("No Main file found")
end
end)
print("Init file end reached")

148
main.lua
View File

@ -1,5 +1,7 @@
-- Main Module
displayword = {}
function startSetupMode()
tmr.stop(0)
tmr.stop(1)
@ -29,36 +31,62 @@ 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
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 charactersOfTime = display_countwords_de(words)
ledBuf = generateLEDs(words, color, color1, color2, color3, color4,
charactersOfTime)
print("Local time : " .. time.year .. "-" .. time.month .. "-" .. time.day .. " " .. time.hour .. ":" .. time.minute .. ":" .. time.second .. " in " .. charactersOfTime .. " chars")
-- Write the buffer to the LEDs
local time = getTime(sec, timezoneoffset)
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
dofile("displayword.lc")
if (displayword ~= nil) then
--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
local characters = displayword.countChars(words)
ledBuf = displayword.generateLEDs(words, colorBg, color, color1, color2, color3, color4, invertRows, characters)
end
displayword = nil
if (ledBuf ~= nil) then
ws2812.write(ledBuf)
else
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
-- Used for debugging
if (clockdebug ~= nil) then
for key,value in pairs(words) do
@ -68,7 +96,7 @@ function displayTime()
end
end
-- cleanup
ledBuf=nil
briPercent=words.briPercent
words=nil
time=nil
collectgarbage()
@ -80,18 +108,41 @@ 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.
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))
@ -101,24 +152,41 @@ 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", "mqtt" }
for _,mod in pairs(dependModules) do
print("Loading " .. mod)
mydofile(mod)
end
tmr.alarm(2, 500, 0 ,function()
setupCounter=5
tmr.alarm(1, 5000, 1 ,function()
if (setupCounter > 4) then
syncTimeFromInternet()
end)
tmr.alarm(3, 2000, 0 ,function()
print("Start webserver...")
mydofile("webserver")
startWebServer()
setupCounter=setupCounter-1
elseif (setupCounter > 3) then
if (startMqttClient ~= nil) then
startMqttClient()
else
print("NO Mqtt found")
mydofile("telnet")
end
setupCounter=setupCounter-1
elseif (setupCounter > 2) then
if (startTelnetServer ~= nil) then
startTelnetServer()
else
displayTime()
end
setupCounter=setupCounter-1
else
displayTime()
end
collectgarbage()
end)
displayTime()
-- Start the time Thread
tmr.alarm(1, 20000, 1 ,function()
-- sync the time every 5 minutes
tmr.alarm(2, 300003, 1 ,function()
syncTimeFromInternet()
displayTime()
end)
@ -132,15 +200,37 @@ 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
if ( file.open("config.lua") ) then
--- Normal operation
wifi.setmode(wifi.STATION)
dofile("config.lua")
mydofile("config")
normalOperation()
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")
file.remove("config.lc")
node.restart()
end
end
end)

169
mqtt.lua Normal file
View File

@ -0,0 +1,169 @@
-- Global variable
m=nil
mqttConnected = false
-- Temp:
t=nil
rowbgColor= {}
function handleSingleCommand(client, topic, data)
if (data == "ON") then
briPercent=100
m:publish(mqttPrefix .. "/clock", "ON", 0, 0)
elseif (data == "OFF") then
briPercent=0
m:publish(mqttPrefix .. "/clock", "OFF", 0, 0)
elseif ((data:sub(1,1) == "#" and data:len() == 7) or (string.match(data, "%d+,%d+,%d+"))) then
local red=0
local green=0
local blue=0
if (data:sub(1,1) == "#") then
red = tonumber(data:sub(2,3), 16)
green = tonumber(data:sub(4,5), 16)
blue = tonumber(data:sub(6,7), 16)
else
red, green, blue = string.match(data, "(%d+),(%d+),(%d+)")
end
colorBg=string.char(green * briPercent / 100, red * briPercent / 100, blue * briPercent / 100)
print("Updated BG: " .. tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue) )
m:publish(mqttPrefix .. "/background", tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue), 0, 0)
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
-- Parse MQTT data and extract color
-- @param data MQTT information
-- @param row string of the row e.g. "row1" used to publish current state
function parseBgColor(data, row)
local red=nil
local green=nil
local blue=nil
if (data:sub(1,1) == "#") then
red = tonumber(data:sub(2,3), 16)
green = tonumber(data:sub(4,5), 16)
blue = tonumber(data:sub(6,7), 16)
else
red, green, blue = string.match(data, "(%d+),(%d+),(%d+)")
end
if ((red ~= nil) and (green ~= nil) and (blue ~= nil) ) then
m:publish(mqttPrefix .. "/"..row, tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue), 0, 0)
return string.char(green * briPercent / 100, red * briPercent / 100, blue * briPercent / 100)
else
return nil
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[0])
end
return temp1
else
return nil
end
end
-- MQTT extension
function registerMqtt()
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 (topic == (mqttPrefix .. "/cmd/single")) then
handleSingleCommand(client, topic, data)
else
-- Handle here the /cmd/# sublevel
if (string.match(topic, "telnet$")) then
client:publish(mqttPrefix .. "/telnet", tostring(wifi.sta.getip()), 0, 0)
ws2812.write(string.char(0,0,0):rep(114))
print("Stop Mqtt and Temp")
m=nil
t=nil
mqttConnected = false
for i=0,6,1 do tmr.stop(i) end
collectgarbage()
mydofile("telnet")
if (startTelnetServer ~= nil) then
startTelnetServer()
end
else
for i=1,10,1 do
if (string.match(topic, "row".. tostring(i) .."$")) then
rowbgColor[i] = parseBgColor(data, "row" .. tostring(i))
print("Updated row" .. tostring(i) )
return
end
end
end
end
end
end)
m:connect(mqttServer, 1883, 0, function(client)
print("MQTT is connected")
mqttConnected = true
-- subscribe topic with qos = 0
client:subscribe(mqttPrefix .. "/cmd/#", 0)
tmr.alarm(3, 1000, 0, function()
-- publish a message with data = hello, QoS = 0, retain = 0
client:publish(mqttPrefix .. "/ip", tostring(wifi.sta.getip()), 0, 0)
local red = string.byte(colorBg,2)
local green = string.byte(colorBg,1)
local blue = string.byte(colorBg,3)
client:publish(mqttPrefix .. "/background", tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue), 0, 0)
end)
end,
function(client, reason)
print("failed reason: " .. reason)
mqttConnected = false
end)
end
function startMqttClient()
if (mqttServer ~= nil and mqttPrefix ~= nil) then
registerMqtt()
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 (mqttConnected) then
local temp = nil
if (t ~= nil) then
temp=readTemp()
print(tostring(temp) .. "°C")
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/100).."."..tostring(temp%100), 0, 0)
else
m:publish(mqttPrefix .. "/heap", tostring(node.heap()), 0, 0)
end
oldBrightness = briPercent
end
end)
end
end

Binary file not shown.

Binary file not shown.

View File

@ -1,16 +1,20 @@
# Firmware was compiled with the following modules:
* LUA_USE_BUILTIN_STRING // for string.xxx()
* LUA_USE_BUILTIN_TABLE // for table.xxx()
* LUA_USE_BUILTIN_COROUTINE // for coroutine.xxx()
* LUA_USE_BUILTIN_MATH // for math.xxx(), partially work
* LUA_USE_BUILTIN_DEBUG_MINIMAL // for debug.getregistry() and debug.traceback()
* LUA_USE_MODULES_ADC
* LUA_USE_MODULES_BIT
* LUA_USE_MODULES_DHT
* LUA_USE_MODULES_FILE
* LUA_USE_MODULES_GPIO
* LUA_USE_MODULES_I2C
* LUA_USE_MODULES_MQTT
* LUA_USE_MODULES_NET
* LUA_USE_MODULES_NODE
* LUA_USE_MODULES_OW
* LUA_USE_MODULES_RTCFIFO
* LUA_USE_MODULES_RTCMEM
* LUA_USE_MODULES_RTCTIME
* LUA_USE_MODULES_SNTP
* LUA_USE_MODULES_SPI
* LUA_USE_MODULES_TMR
* LUA_USE_MODULES_UART
* LUA_USE_MODULES_WIFI

1
os/blank.bin Normal file
View File

@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ if [ $# -ne 1 ]; then
echo "One parameter required: the device of the serial interface"
echo "$0 <device>"
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
#./esptool.py --port /dev/$DEVICE $BAUD write_flash 0x00000 nodemcu-master-enduser_setup,file,gpio,net,node,rtcfifo,rtcmem,rtctime,sntp,spi,tmr,uart,wifi,ws2812-integer.bin
./esptool.py --port /dev/ttyUSB0 $BAUD write_flash 0x00000 0x00000.bin 0x10000 0x10000.bin
echo "Flashing the new"
#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

BIN
os/nodemcu2.bin Normal file

Binary file not shown.

8
simulation/.classpath Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="libs/luaj-jme-3.0.1.jar" sourcepath="libs/luaj-3.0.1/src"/>
<classpathentry kind="lib" path="libs/luaj-jse-3.0.1.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

1
simulation/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/bin/

17
simulation/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>WS2812Emulation</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

12
simulation/Readme.md Normal file
View File

@ -0,0 +1,12 @@
# Simulation
The simualation should be started with the following arguments at this position:
`../init.lua ws28128ClockLayout.txt config.lua`
# Use it without Eclipse
Compiling:
`javac -d bin/ -cp libs/luaj-jme-3.0.1.jar:libs/luaj-jse-3.0.1.jar $(find src -name '*.java')`
Running:
`java -cp libs/luaj-jme-3.0.1.jar:libs/luaj-jse-3.0.1.jar:bin de.c3ma.ollo.WS2812Simulation ../init.lua ws28128ClockLayout.txt config.lua`

13
simulation/config.lua Normal file
View File

@ -0,0 +1,13 @@
green2=200
red=128
blue=200
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"
timezoneoffset=1

3
simulation/libs/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
luaj-3.0.1.zip
luaj-3.0.1/
luaj-sources-3.0.1.jar

View File

@ -0,0 +1,7 @@
# Dependencies
The following file is expected here:
`luaj-3.0.1.zip`
It can be downloaded here:
https://sourceforge.net/projects/luaj/files/latest/download

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,17 @@
package de.c3ma.ollo;
/**
* created at 29.12.2017 - 18:29:07<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public interface LuaSimulation {
public void rebootTriggered();
public void setSimulationTime(long timeInMillis);
public void setADC(int value);
}

View File

@ -0,0 +1,54 @@
package de.c3ma.ollo;
import org.luaj.vm2.LuaValue;
/**
* created at 29.12.2017 - 18:48:27<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class LuaThreadTmr extends Thread {
private boolean running = true;
private boolean stopped = false;
private LuaValue code;
private final int delay;
private final int timerNumber;
public LuaThreadTmr(int timerNumber, LuaValue code, boolean endlessloop, int delay) {
this.code = code;
this.running = endlessloop;
this.delay = delay;
this.timerNumber = timerNumber;
}
@Override
public void run() {
try {
do {
Thread.sleep(delay);
if (code != null) {
code.call();
}
} while(running);
} catch(InterruptedException ie) {
System.err.println("[TMR] Timer" + timerNumber + " interrupted");
}
stopped = true;
}
public boolean isStopped() { return stopped; }
public void stopThread() {
running = false;
code = null;
}
}

View File

@ -0,0 +1,187 @@
package de.c3ma.ollo;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import javax.swing.SwingUtilities;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.JsePlatform;
import de.c3ma.ollo.mockup.DoFileFunction;
import de.c3ma.ollo.mockup.ESP8266Adc;
import de.c3ma.ollo.mockup.ESP8266File;
import de.c3ma.ollo.mockup.ESP8266Net;
import de.c3ma.ollo.mockup.ESP8266Node;
import de.c3ma.ollo.mockup.ESP8266Time;
import de.c3ma.ollo.mockup.ESP8266Tmr;
import de.c3ma.ollo.mockup.ESP8266Uart;
import de.c3ma.ollo.mockup.ESP8266Wifi;
import de.c3ma.ollo.mockup.ESP8266Ws2812;
/**
* created at 28.12.2017 - 13:19:32<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
*
* @author ollo<br />
*/
public class WS2812Simulation implements LuaSimulation {
private Globals globals = JsePlatform.standardGlobals();
private ESP8266Tmr espTmr = new ESP8266Tmr();
private ESP8266File espFile = new ESP8266File();
private ESP8266Node espNode = new ESP8266Node(this);
private DoFileFunction doFile = new DoFileFunction(globals);
private ESP8266Ws2812 ws2812 = new ESP8266Ws2812();
private ESP8266Adc adc = new ESP8266Adc();
private String scriptName;
public WS2812Simulation(File sourceFolder) {
globals.load(new ESP8266Uart());
globals.load(ws2812);
globals.load(espTmr);
globals.load(espFile);
globals.load(espNode);
globals.load(adc);
globals.load(new ESP8266Wifi());
globals.load(new ESP8266Net());
globals.load(new ESP8266Time());
globals.set("dofile", doFile);
try {
File tempFile = File.createTempFile("NodemcuSimuFile", "");
File tempDir = new File(tempFile.getParent() + File.separator + "Nodemcu" + System.currentTimeMillis());
tempDir.mkdir();
System.out.println("[Nodemcu] Directory is " + tempDir.getAbsolutePath());
// Copy all files into the temporary folder
for (File f : sourceFolder.listFiles()) {
Files.copy(f.toPath(), new File(tempDir.getAbsolutePath() + File.separator + f.getName()).toPath());
}
espFile.setWorkingDirectory(tempDir);
espNode.setWorkingDirectory(tempDir);
doFile.setWorkingDirectory(tempDir);
} catch (IOException e) {
System.err.println("[Nodemcu] " + e.getMessage());
espFile = null;
espNode = null;
}
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (args.length == 0) {
printUsage();
return;
}
if (args.length >= 1) {
File f = new File(args[0]);
if (f.exists()) {
WS2812Simulation simu = new WS2812Simulation(f.getParentFile());
System.out.println("File : " + f.getAbsolutePath());
if (args.length >= 2) {
simu.setWS2812Layout(new File(args[1]));
}
try {
if (args.length >= 3) {
File additionalFile = new File(args[2]);
if (additionalFile.exists() && (simu.doFile != null)) {
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");
printUsage();
System.exit(1);
}
}
if (args.length >= 4) {
try {
ESP8266Tmr.gTimingFactor = Integer.parseInt(args[3]);
} catch (NumberFormatException nfe) {
System.err.println("Timing factor not parsable: " + nfe.getMessage());
printUsage();
}
}
simu.callScript(f.getName());
} catch (IOException e) {
System.err.println("[Nodemcu] " + e.getMessage());
}
}
} else {
printUsage();
}
}
});
}
private void setWS2812Layout(File file) {
if (file.exists()) {
ws2812.setLayout(file, this);
} else {
throw new RuntimeException("WS2812 Layout: " + file.getAbsolutePath() + " does not exists");
}
}
private static void printUsage() {
System.out.println("Usage:");
System.out.println("one argument required: file to execute.");
System.out.println(".e.g: init.lua (ws2812 layout configuration) (additional LUA script) (timing speedup factor)");
}
@Override
public void rebootTriggered() {
System.out.println("=================== Reboot in Simulation -> call it again =================");
this.espTmr.stopAllTimer();
try {
Thread.sleep(200);
if (this.scriptName != null) {
System.out.println("Reexecuting...");
callScript(this.scriptName);
}
} catch (InterruptedException e) {
}
}
private void callScript(String filename) {
this.scriptName = filename;
if ((espFile != null) && (espFile.getFileInWorkingDir(filename) != null)) {
LuaValue chunk = globals.loadfile(espFile.getFileInWorkingDir(filename).getAbsolutePath());
chunk.call();
} else {
throw new RuntimeException("Copy into temporary folder failed; script not available");
}
}
@Override
public void setSimulationTime(long timeInMillis) {
ESP8266Time.setOverwrittenTime(timeInMillis);
}
@Override
public void setADC(int value) {
adc.setADC(value);
}
}

View File

@ -0,0 +1,52 @@
package de.c3ma.ollo.mockup;
import java.io.File;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
/**
* created at 29.12.2017 - 20:23:48<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class DoFileFunction extends OneArgFunction {
private File workingDir = null;
private Globals globals;
public DoFileFunction(Globals globals) {
this.globals = globals;
}
@Override
public LuaValue call(LuaValue luaFilename) {
String filename = luaFilename.checkjstring();
File f = new File(workingDir.getAbsolutePath() + File.separator + filename);
if (f.exists()) {
LuaValue chunk = this.globals.loadfile(f.getAbsolutePath());
chunk.call();
return LuaValue.valueOf(true);
} else {
return LuaValue.valueOf(false);
}
}
public void setWorkingDirectory(File workingDir) {
this.workingDir = workingDir;
}
public String getWorkingDirectory() {
if (workingDir != null) {
return workingDir.getAbsolutePath();
} else {
return null;
}
}
}

View File

@ -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<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
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);
}
}

View File

@ -0,0 +1,101 @@
package de.c3ma.ollo.mockup;
import java.io.File;
import java.util.ArrayList;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
/**
* created at 29.12.2017 - 01:08:53<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class ESP8266File extends TwoArgFunction {
private File workingDir = null;
private File openedFile = null;
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable file = new LuaTable();
file.set("open", new OpenFunction());
file.set("list", new ListFunction());
file.set("remove", new RemoveFunction());
env.set("file", file);
env.get("package").get("loaded").set("file", file);
return file;
}
private class ListFunction extends TwoArgFunction {
@Override
public LuaValue call(LuaValue arg1, LuaValue arg2) {
final LuaTable fileList = new LuaTable();
if ((workingDir != null) && (workingDir.exists())) {
File[] files = workingDir.listFiles();
for (File file : files) {
fileList.set(file.getName(), file.getAbsolutePath());
}
}
return fileList;
}
}
private class OpenFunction extends OneArgFunction {
@Override
public LuaValue call(LuaValue fileName) {
final String codeFileName = fileName.checkjstring();
final File f = new File( workingDir.getAbsolutePath() + File.separator + codeFileName);
//System.out.println("[FILE] Loading " + codeFileName);
if (f.exists()) {
ESP8266File.this.openedFile = f;
}
return LuaValue.valueOf((f.exists()));
}
}
private class RemoveFunction extends OneArgFunction {
@Override
public LuaValue call(LuaValue fileName) {
final String luaFileName = fileName.checkjstring();
System.out.println("[FILE] Removing " + luaFileName);
File f = new File(workingDir.getAbsolutePath() + File.separator + fileName);
if (f.exists()) {
return LuaValue.valueOf(f.delete());
} else {
return LuaValue.valueOf(false);
}
}
}
public void setWorkingDirectory(File workingDir) {
this.workingDir = workingDir;
}
public File getFileInWorkingDir(String filename) {
File f = new File (workingDir.getAbsolutePath() + File.separator + filename);
if (f.exists()) {
return f;
} else {
return null;
}
}
}

View File

@ -0,0 +1,83 @@
package de.c3ma.ollo.mockup;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;
import de.c3ma.ollo.LuaSimulation;
/**
* created at 29.12.2017 - 01:29:40<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class ESP8266Net extends TwoArgFunction {
public static final int PORTNUMBER_OFFSET = 4000;
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable net = new LuaTable();
net.set("createServer", new CreateServerFunction());
//FIXME net.set("send", new SendFunction());
net.set("TCP", "TCP");
env.set("net", net);
env.get("package").get("loaded").set("net", net);
return net;
}
private class CreateServerFunction extends OneArgFunction {
@Override
public LuaValue call(LuaValue arg) {
final LuaTable srv = new LuaTable();
srv.set("listen", new ListenFunction());
return srv;
}
}
private class ListenFunction extends TwoArgFunction {
@Override
public LuaValue call(LuaValue port, LuaValue function) {
int portnumber = port.checkint();
LuaFunction onListenFunction = function.checkfunction();
System.out.println("[Net] listening " + portnumber + "(simulating at " + (PORTNUMBER_OFFSET+ portnumber) + ")");
try
{
ServerSocket serverSocket = new ServerSocket(PORTNUMBER_OFFSET+portnumber);
serverSocket.setSoTimeout( 60000 ); // Timeout after one minute
Socket client = serverSocket.accept();
}
catch ( InterruptedIOException iioe )
{
System.err.println( "Timeout nach einer Minute!" );
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("[Net] server running");
return LuaValue.valueOf(true);
}
}
}

View File

@ -0,0 +1,79 @@
package de.c3ma.ollo.mockup;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;
import de.c3ma.ollo.LuaSimulation;
/**
* created at 29.12.2017 - 01:29:40<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class ESP8266Node extends TwoArgFunction {
private File workingDir = null;
private LuaSimulation nodemcuSimu;
public ESP8266Node(LuaSimulation nodemcuSimu) {
this.nodemcuSimu = nodemcuSimu;
}
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable node = new LuaTable();
node.set("compile", new CompileFunction());
node.set("restart", new RestartFunction());
env.set("node", node);
env.get("package").get("loaded").set("node", node);
return node;
}
private class CompileFunction extends OneArgFunction {
@Override
public LuaValue call(LuaValue fileName) {
final String codeFileName = fileName.checkjstring();
final String compiledFileName = fileName.checkjstring().replace(".lua", ".lc");
final File f = new File( workingDir.getAbsolutePath() + File.separator + codeFileName);
System.out.println("[Node] Compiling " + compiledFileName);
final File outf = new File( workingDir.getAbsolutePath() + File.separator + compiledFileName);
if (f.exists()) {
//Simply copy the file as .lc file
try {
Files.copy(f.toPath(), outf.toPath());
} catch (IOException e) {
return LuaValue.valueOf(false);
}
}
return LuaValue.valueOf(f.exists());
}
}
private class RestartFunction extends ZeroArgFunction {
@Override
public LuaValue call() {
System.out.println("[Node] Restart");
nodemcuSimu.rebootTriggered();
return LuaValue.valueOf(false);
}
}
public void setWorkingDirectory(File workingDir) {
this.workingDir = workingDir;
}
}

View File

@ -0,0 +1,102 @@
package de.c3ma.ollo.mockup;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.ThreeArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;
/**
* created at 29.12.2017 - 00:07:22<br />
* creator: ollo<br />
* project: Time Emulation<br />
*
* Simulating the following modules:
* Sntp
* rtctime
*
* $Id: $<br />
* @author ollo<br />
*/
public class ESP8266Time extends TwoArgFunction {
private static long gSimulationStartTime = 0;
private static long gOverwrittenTime = 0;
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable sntp = new LuaTable();
sntp.set("sync", new SyncFunction());
env.set("sntp", sntp);
final LuaTable rtctime = new LuaTable();
rtctime.set("get", new GetFunction());
env.set("rtctime", rtctime);
env.get("package").get("loaded").set("sntp", sntp);
env.get("package").get("loaded").set("rtctime", rtctime);
return sntp;
}
/**
* Generate a time. If there is no speedup, it is simply the current system time.
* Otherwise the time is speedup by the given factor
* @return
*/
private long generateCurrenttime() {
if (gSimulationStartTime == 0) {
gSimulationStartTime = System.currentTimeMillis();
}
if (gOverwrittenTime == 0) {
/* Time simulation is disabled -> calculate something according to the speedup factor */
long time = System.currentTimeMillis();
if (ESP8266Tmr.gTimingFactor > 1) {
time = gSimulationStartTime + ((time - gSimulationStartTime) * ESP8266Tmr.gTimingFactor);
}
return time;
} else {
return gOverwrittenTime;
}
}
private class SyncFunction extends ThreeArgFunction {
@Override
public LuaValue call(LuaValue server, LuaValue callbackSuccess, LuaValue callbackFailure) {
String serverName = server.checkjstring();
LuaFunction cb = callbackSuccess.checkfunction();
System.out.println("[SNTP] sync " + serverName);
/*FIXME also make it possible to simulate the time */
long time = generateCurrenttime();
int seconds = (int) (time / 1000);
int useconds = (int) (time % 1000);
cb.call(LuaValue.valueOf(seconds), LuaValue.valueOf(useconds), LuaValue.valueOf(serverName));
return LuaValue.valueOf(true);
}
}
private class GetFunction extends ZeroArgFunction {
@Override
public LuaValue call() {
LuaValue[] v = new LuaValue[2];
/*FIXME also make it possible to simulate the time */
long time = generateCurrenttime();
int seconds = (int) (time / 1000);
int useconds = (int) (time % 1000);
v[0] = LuaValue.valueOf(seconds);
v[1] = LuaValue.valueOf(useconds);
return LuaValue.varargsOf(v).arg1();
}
}
public static void setOverwrittenTime(long timeInMillis) {
gOverwrittenTime = timeInMillis;
}
}

View File

@ -0,0 +1,95 @@
package de.c3ma.ollo.mockup;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.VarArgFunction;
import de.c3ma.ollo.LuaThreadTmr;
/**
* created at 29.12.2017 - 00:07:22<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class ESP8266Tmr extends TwoArgFunction {
private static final int MAXTHREADS = 7;
private static LuaThreadTmr[] allThreads = new LuaThreadTmr[MAXTHREADS];
public static int gTimingFactor = 1;
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable tmr = new LuaTable();
tmr.set("stop", new stop());
tmr.set("alarm", new alarm());
env.set("tmr", tmr);
env.get("package").get("loaded").set("tmr", tmr);
/* initialize the Threads */
for (Thread t : allThreads) {
t = null;
}
return tmr;
}
private boolean stopTmr(int i) {
if (allThreads[i] != null) {
allThreads[i].stopThread();
allThreads[i] = null;
return true;
} else {
return false;
}
}
private class stop extends OneArgFunction {
@Override
public LuaValue call(LuaValue arg) {
final int timerNumer = arg.toint();
System.out.println("[TMR] Timer" + timerNumer + " stopped");
return LuaValue.valueOf(stopTmr(timerNumer));
}
}
private class alarm extends VarArgFunction {
public Varargs invoke(Varargs varargs) {
if (varargs.narg()== 4) {
final int timerNumer = varargs.arg(1).toint();
final byte endlessloop = varargs.arg(3).tobyte();
final int delay = varargs.arg(2).toint();
final LuaValue code = varargs.arg(4);
System.out.println("[TMR] Timer" + timerNumer );
if ((timerNumer < 0) || (timerNumer > timerNumer)) {
return LuaValue.error("[TMR] Timer" + timerNumer + " is not available, choose 0 to 6");
}
if (stopTmr(timerNumer)) {
System.err.println("[TMR] Timer" + timerNumer + " stopped");
}
/* The cycletime is at least 1 ms */
allThreads[timerNumer] = new LuaThreadTmr(timerNumer, code, (endlessloop == 1), Math.max(delay / gTimingFactor, 1));
allThreads[timerNumer].start();
}
return LuaValue.valueOf(true);
}
}
public void stopAllTimer() {
for (int i = 0; i < allThreads.length; i++) {
stopTmr(i);
}
}
}

View File

@ -0,0 +1,38 @@
package de.c3ma.ollo.mockup;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.VarArgFunction;
/**
* created at 28.12.2017 - 23:05:05<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class ESP8266Uart extends TwoArgFunction {
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable uart = new LuaTable();
uart.set("setup", new setup());
env.set("uart", uart);
env.get("package").get("loaded").set("uart", uart);
return uart;
}
private class setup extends VarArgFunction {
public Varargs invoke(Varargs varargs) {
if (varargs.narg()== 6) {
System.out.println("[UART] " + varargs.arg(2) + " " + varargs.arg(3)
+ ((varargs.arg(4).checkint() > 0) ? "Y" : "N")
+ varargs.arg(5));
}
return LuaValue.valueOf(true);
}
}
}

View File

@ -0,0 +1,81 @@
package de.c3ma.ollo.mockup;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;
import de.c3ma.ollo.LuaSimulation;
/**
* created at 29.12.2017 - 01:29:40<br />
* creator: ollo<br />
* project: WifiEmulation<br />
* $Id: $<br />
* @author ollo<br />
*/
public class ESP8266Wifi extends TwoArgFunction {
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable wifi = new LuaTable();
wifi.set("setmode", new SetModeFunction());
final LuaTable ap = new LuaTable();
ap.set("config", new ConfigFunction());
wifi.set("ap", ap);
final LuaTable sta = new LuaTable();
sta.set("status", new StatusFunction());
sta.set("getip", new GetIpFunction());
wifi.set("sta", sta);
wifi.set("SOFTAP", "SOFTAP");
wifi.set("STATION", "STATION");
env.set("wifi", wifi);
env.get("package").get("loaded").set("wifi", wifi);
return wifi;
}
private class SetModeFunction extends OneArgFunction {
@Override
public LuaValue call(LuaValue apmode) {
final String APmodeString = apmode.checkjstring();
System.out.println("[Wifi] set mode " + APmodeString);
return LuaValue.valueOf(true);
}
}
private class ConfigFunction extends OneArgFunction {
@Override
public LuaValue call(LuaValue arg) {
System.out.println("[Wifi] config");
return LuaValue.valueOf(true);
}
}
private class StatusFunction extends ZeroArgFunction {
@Override
public LuaValue call() {
return LuaValue.valueOf(5);
}
}
private class GetIpFunction extends ZeroArgFunction {
@Override
public LuaValue call() {
return LuaValue.valueOf("127.0.0.1");
}
}
}

View File

@ -0,0 +1,88 @@
package de.c3ma.ollo.mockup;
import java.io.File;
import javax.swing.SwingUtilities;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;
import de.c3ma.ollo.LuaSimulation;
import de.c3ma.ollo.mockup.ui.WS2812Layout;
/**
* created at 28.12.2017 - 23:34:04<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
*
* @author ollo<br />
*/
public class ESP8266Ws2812 extends TwoArgFunction {
private static WS2812Layout layout = null;
@Override
public LuaValue call(LuaValue modname, LuaValue env) {
env.checkglobals();
final LuaTable ws2812 = new LuaTable();
ws2812.set("init", new init());
ws2812.set("write", new write());
env.set("ws2812", ws2812);
env.get("package").get("loaded").set("ws2812", ws2812);
return ws2812;
}
private class init extends ZeroArgFunction {
@Override
public LuaValue call() {
System.out.println("[WS2812] init");
return LuaValue.valueOf(true);
}
}
private class write extends OneArgFunction {
@Override
public LuaValue call(LuaValue arg) {
if (arg.isstring()) {
LuaString jstring = arg.checkstring();
final int length = jstring.rawlen();
if ((length % 3) == 0) {
final byte[] array = jstring.m_bytes;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
for (int i = 0; i < length; i += 3) {
if (ESP8266Ws2812.layout != null) {
int r = array[i + 0]+(Byte.MIN_VALUE*-1);
int b = array[i + 1]+(Byte.MIN_VALUE*-1);
int g = array[i + 2]+(Byte.MIN_VALUE*-1);
ESP8266Ws2812.layout.updateLED(i / 3, r, g, b);
}
}
}
});
}
if (ESP8266Ws2812.layout == null) {
System.out.println("[WS2812] write length:" + length);
} else {
}
}
return LuaValue.valueOf(true);
}
}
public void setLayout(File file, LuaSimulation nodemcuSimu) {
if (ESP8266Ws2812.layout == null) {
ESP8266Ws2812.layout = WS2812Layout.parse(file, nodemcuSimu);
}
}
}

View File

@ -0,0 +1,323 @@
package de.c3ma.ollo.mockup.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.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;
import de.c3ma.ollo.LuaSimulation;
/**
* created at 02.01.2018 - 12:57:02<br />
* creator: ollo<br />
* project: WS2812Emulation<br />
* $Id: $<br />
*
* @author ollo<br />
*/
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<String> mLines = new ArrayList<String>();
private int mColumn = 0;
private int mRow = 0;
private Element[][] mElements;
private LuaSimulation nodemcuSimu;
public WS2812Layout(LuaSimulation nodemcuSimu) {
this.nodemcuSimu = nodemcuSimu;
}
public static WS2812Layout parse(File file, LuaSimulation nodemcuSimu) {
WS2812Layout layout = null;
try {
BufferedReader br = new BufferedReader(new FileReader(file));
try {
String line = br.readLine();
if (line != null) {
layout = new WS2812Layout(nodemcuSimu);
}
while (line != null) {
if (!line.startsWith("#")) {
layout.mLines.add(line);
layout.mRow++;
layout.mColumn = Math.max(layout.mColumn, line.length());
}
/* get the next line */
line = br.readLine();
}
/* parse each line */
layout.parse();
layout.createAndDisplayGUI();
} finally {
if (br != null) {
br.close();
}
}
} catch (IOException ioe) {
}
return layout;
}
private void createAndDisplayGUI() {
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
contentPane.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 2));
JPanel ledPanel = new JPanel();
ledPanel.setBackground(Color.BLACK);
ledPanel.setLayout(new GridLayout(this.mRow, this.mColumn, 10, 10));
for (int i = 0; i < this.mRow; i++) {
for (int j = 0; j < this.mColumn; j++) {
if (this.mElements[i][j] != null) {
ledPanel.add(this.mElements[i][j]);
}
}
}
contentPane.add(ledPanel, BorderLayout.CENTER);
JSlider adc = new JSlider(JSlider.VERTICAL,
ADC_MIN, ADC_MAX, ADC_INIT);
adc.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
nodemcuSimu.setADC(adc.getValue());
}
});
contentPane.add(adc, BorderLayout.EAST);
JPanel bottomPanel = new JPanel();
final JTextField dateTime = new JTextField("yyyy-mm-dd HH:MM:SS");
dateTime.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
warn();
}
public void removeUpdate(DocumentEvent e) {
warn();
}
public void insertUpdate(DocumentEvent e) {
warn();
}
public void warn() {
final String pattern = "(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})";
final String current = dateTime.getText();
if (current.length() <=0) {
/* color "nothing" green */
dateTime.setForeground(Color.GREEN);
/* disable the time simulation */
nodemcuSimu.setSimulationTime(0);
return;
}
if (!current.matches(pattern)) {
dateTime.setForeground(Color.RED);
} else {
dateTime.setForeground(Color.BLACK);
Pattern dateTimePattern = Pattern.compile(pattern);
Matcher matcher = dateTimePattern.matcher(current);
int year=0;
int month=0;
int day=0;
int hour=0;
int minute=0;
int second=0;
matcher.find();
for (int g = 1; g <= matcher.groupCount(); g++) {
switch(g) {
case 1: /* Year */
year = Integer.parseInt(matcher.group(g));
break;
case 2: /* Month */
month = Integer.parseInt(matcher.group(g));
break;
case 3: /* Day */
day = Integer.parseInt(matcher.group(g));
break;
case 4: /* Hour */
hour = Integer.parseInt(matcher.group(g));
break;
case 5: /* Minute */
minute = Integer.parseInt(matcher.group(g));
break;
case 6: /* Second */
second = Integer.parseInt(matcher.group(g));
break;
}
}
System.out.println("[GUI] Set time to: " + year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second);
GregorianCalendar gc = new GregorianCalendar(year, month, day, hour, minute, second);
nodemcuSimu.setSimulationTime(gc.getTimeInMillis());
}
}
});
bottomPanel.add(dateTime);
final JButton btnSetCurrentTime = new JButton("Set time");
btnSetCurrentTime.setActionCommand("Set time");
btnSetCurrentTime.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JButton but = (JButton) ae.getSource();
if (but.equals(btnSetCurrentTime)) {
GregorianCalendar gc = new GregorianCalendar();
dateTime.setText(String.format("%d-%02d-%02d %02d:%02d:%02d",
gc.get(Calendar.YEAR), gc.get(Calendar.MONTH), gc.get(Calendar.DAY_OF_MONTH),
gc.get(Calendar.HOUR_OF_DAY), gc.get(Calendar.MINUTE), gc.get(Calendar.SECOND)));
}
}
});
bottomPanel.add(btnSetCurrentTime);
final JButton btnReboot = new JButton("Reboot");
btnReboot.setActionCommand("Reboot simulation");
btnReboot.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JButton but = (JButton) ae.getSource();
if (but.equals(btnReboot)) {
System.out.println("[Node] Restart");
nodemcuSimu.rebootTriggered();
}
}
});
bottomPanel.add(btnReboot);
contentPane.add(bottomPanel, BorderLayout.SOUTH);
setContentPane(contentPane);
pack();
setLocationByPlatform(true);
setVisible(true);
}
private void parse() {
this.mElements = new Element[this.mRow][this.mColumn];
int row = 0;
for (String line : this.mLines) {
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if ((('A' <= c) && (c <= 'Z')) || (('0' <= c) && (c <= '9')) || (c == 'Ä') || (c == 'Ö')
|| (c == 'Ü')) {
this.mElements[row][i] = new Element(c);
} else {
this.mElements[row][i] = new Element();
}
this.mElements[row][i].setColor(0, 0, 0);
}
row++;
}
}
public class Element extends JLabel {
/**
*
*/
private static final long serialVersionUID = -3933903441113933637L;
private boolean noText = false;
/**
* Draw a simple rect
*/
public Element() {
super();
this.noText = true;
this.setBackground(Color.BLACK);
}
/**
* Draw a character
*
* @param character
* to show
*/
public Element(char character) {
super("" + character);
setFont(new Font("Dialog", Font.BOLD, 24));
setHorizontalAlignment(CENTER);
// FIXME: Background color is not updated:
this.setBackground(Color.BLACK);
}
public void setColor(int red, int green, int blue) {
this.setForeground(new Color(red, green, blue));
this.repaint();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (noText) {
sb.append(" ");
} else {
sb.append("" + this.getText());
}
sb.append("|" + Integer.toHexString(this.getForeground().getRed()) + " "
+ Integer.toHexString(this.getForeground().getGreen()) + " "
+ Integer.toHexString(this.getForeground().getBlue()));
return sb.toString();
}
}
public void updateLED(int index, int r, int g, int b) {
if (mElements != null) {
int i = (index / mColumn);
int j = (index % mColumn);
// Swap each second row
if (i % 2 == 1) {
j = (mColumn-1) - j;
}
if ((i < mElements.length) && (j < mElements[i].length) && (mElements[i][j] != null)) {
Element curlbl = mElements[i][j];
curlbl.setColor(r, g, b);
}
}
}
}

View File

@ -0,0 +1,17 @@
# This file describes the layout of the WS2812 LEDs.
# _ will only draw a rect
# A-Z or 1-9 will draw the text or number
# each element will be updated with its color with the known commands of the nodemcu firmware
#
# Here the configuration for the wordclock:
ESKISTLFÜNF
ZEHNZWANZIG
DREIVIERTEL
TGNACHVORJM
HALBXZWÖLFP
ZWEINSIEBEN
KDREIRHFÜNF
ELFNEUNVIER
WACHTZEHNRS
BSECHSFMUHR
____

36
telnet.lua Normal file
View File

@ -0,0 +1,36 @@
-- 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("- mydofile(\"commands\")")
print("- storeConfig()")
print("Visite https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_en for further commands")
end)
print("Telnetserver is up")
end

View File

@ -1,2 +1,4 @@
# Source:
# luatool.py
**Not** supported with python 3.x
## Source:
https://github.com/4refr0nt/luatool/tree/master/luatool

View File

@ -3,6 +3,14 @@
LUATOOL=./tools/luatool.py
DEVICE=$1
BAUD=115200
# check environment
if [ ! -f $LUATOOL ]; then
echo "$LUATOOL not found"
echo "is the command prompt at the same level as the tools folder ?"
exit 1
fi
# check the serial connection
@ -11,23 +19,38 @@ if [ ! -c $DEVICE ]; then
exit 1
fi
if [ $# -ne 1 ]; then
if [ $# -eq 0 ]; then
echo ""
echo "e.g. usage $0 <device>"
echo "e.g. usage $0 <device> [<files to upoad>]"
exit 1
fi
FILES="displayword.lua main.lua timecore.lua webpage.lua webserver.lua wordclock.lua init.lua"
if [ $# -eq 1 ]; then
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
$LUATOOL -p $DEVICE -w -b $BAUD
if [ $? -ne 0 ]; then
echo "STOOOOP"
exit 1
fi
echo
#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 "Start Flasing ..."
for f in $FILES; do
if [ ! -f $f ]; then
@ -36,7 +59,7 @@ for f in $FILES; do
exit 1
fi
echo "------------- $f ------------"
$LUATOOL -p $DEVICE -f $f -t $f
$LUATOOL -p $DEVICE -f $f -b $BAUD -t $f
if [ $? -ne 0 ]; then
echo "STOOOOP"
exit 1
@ -44,6 +67,6 @@ for f in $FILES; do
done
echo "Reboot the ESP"
$LUATOOL -p $DEVICE -r
echo "node.restart()" >> $DEVICE
exit 0

56
tools/remoteFlash.sh Executable file
View File

@ -0,0 +1,56 @@
#!/bin/bash
IP=$1
FLASHTOOL=./tools/tcpFlash.py
if [ ! -f $FLASHTOOL ]; then
echo "Execute the script in root folder of the project"
exit 2
fi
if [ "$IP" == "" ]; then
echo "IP address of ESP required"
echo "usage:"
echo "$0 <IP>"
echo "$0 192.168.0.2"
exit 1
fi
# check the connection
echo "Searching $IP ..."
ping $IP -c 2 >> /dev/null
if [ $? -ne 0 ]; then
echo "Entered IP address: $IP is NOT online"
exit 2
fi
echo "Upgrading $IP"
echo "stopWordclock()" > /tmp/wordClockCMD.txt
echo "uart.write(0, tostring(node.heap())" >> /tmp/wordClockCMD.txt
echo "c = string.char(0,0,128)" >> /tmp/wordClockCMD.txt
echo "w = string.char(0,0,0)" >> /tmp/wordClockCMD.txt
echo "ws2812.write(w:rep(4) .. c .. w:rep(15) .. c .. w:rep(9) .. c .. w:rep(30) .. c .. w:rep(41) .. c )" >> /tmp/wordClockCMD.txt
$FLASHTOOL -f /tmp/wordClockCMD.txt -t $IP -v
FILES="displayword.lua main.lua timecore.lua webpage.html webserver.lua wordclock.lua init.lua"
echo "Start Flasing ..."
for f in $FILES; do
if [ ! -f $f ]; then
echo "Cannot find $f"
echo "place the terminal into the folder where the lua files are present"
exit 1
fi
echo "------------- $f ------------"
$FLASHTOOL -t $IP -f $f
if [ $? -ne 0 ]; then
echo "STOOOOP"
exit 1
fi
done
echo "TODO: Reboot the ESP"
#echo "node.restart()" | nc $IP 80
exit 0

145
tools/tcpFlash.py Executable file
View File

@ -0,0 +1,145 @@
#!/usr/bin/python
import argparse
import socket
import os.path
import sys #for exit and flushing of stdout
import time
def sendRecv(s, message, answer):
msg = message + "\n"
s.sendall(msg)
reply = s.recv(4096)
i=1
while ((not (answer in reply)) and (i < 10)):
reply += s.recv(4096)
i = i + 1
if answer not in reply:
return False
else:
return True
def sendCmd(s, message, cleaningEnter=False):
msg = message + "\n"
s.sendall(msg)
time.sleep(0.050)
reply = s.recv(4096)
i=1
while ((not (">" in reply)) and (i < 10)):
time.sleep((0.050) * i)
reply += s.recv(4096)
i = i + 1
# print "Send\t" + message
# print "Got\t" + reply
if (cleaningEnter):
s.sendall("\n")
if "stdin:1:" in reply:
print "ERROR, received : " + reply
return False
elif ">" in reply:
return True
else:
print "ERROR, received : " + reply
return False
def main(nodeip, luafile, volatile=None):
if ( not os.path.isfile(luafile) ):
print "The file " + luafile + " is not available"
else:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((nodeip, 23))
time.sleep(0.050)
s.sendall("\n")
# Receive the hello Message of answer of the ESP
if (not sendRecv(s, "\n", "Welcome to ") ):
print "Cannot connect to the ESP"
s.close()
sys.exit(2)
# Read all lines from the welcome message
i=0
reply = s.recv(4096)
while ((reply is not None) and (not (">" in reply)) and (i < 100)):
reply = s.recv(4096)
i = i + 1
# Communication tests
if ( not sendRecv(s, "print(12345)", "12345") ):
print "NOT communicating with an ESP8266 running LUA (nodemcu) firmware"
s.close()
sys.exit(3)
sendCmd(s, "for i=0,5 do tmr.stop(i) end")
sendCmd(s, "collectgarbage()")
if (volatile is None):
print "Flashing " + luafile
sendCmd(s, "file.remove(\"" + luafile+"\");", True)
sendCmd(s, "w= file.writeline", True)
sendCmd(s, "file.open(\"" + luafile + "\",\"w+\");", True)
else:
print "Executing " + luafile + " on nodemcu"
with open(luafile) as f:
contents = f.readlines()
i=1
for line in contents:
print "\rSending " + str(i) + "/" + str(len(contents)) + " ...",
sys.stdout.flush()
l = line.rstrip()
if ( l.endswith("]") ):
l = l + " "
print "add a space at the end"
if (volatile is None):
if (not sendCmd(s, "w([==[" + l + "]==]);")):
print "Cannot write line " + str(i)
s.close()
sys.exit(4)
else:
if (not sendCmd(s, l)):
print "Cannot write line " + str(i)
s.close()
sys.exit(4)
i=i+1
if (volatile is None):
# Finished with updating the file in LUA
if (not sendCmd(s, "w([[" + "--EOF" + "]]);")):
print "Cannot write line " + "-- EOF"
if (not sendCmd(s, "file.close();")):
print "Cannot close the file"
sys.exit(4)
# Check if the file exists:
if (not sendRecv(s, "=file.exists(\"" + luafile + "\")", "true")):
print("Cannot send " + luafile + " to the ESP")
sys.exit(4)
else:
print("Updated " + luafile + " successfully")
else:
print("Send " + luafile + " successfully")
# Cleaning the socket by closing it
s.close()
sys.exit(0) # Report that the flashing was succesfull
except socket.error, msg:
print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
sys.exit(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='IP address or dns of the ESP to flash')
parser.add_argument('-f', '--file', help='LUA file, that should be updated')
parser.add_argument('-v', '--volatile', help='File is executed at the commandline', action='store_const', const=1)
args = parser.parse_args()
if (args.target and args.file and args.volatile):
main(args.target, args.file, args.volatile)
elif (args.target and args.file):
main(args.target, args.file)
else:
parser.print_help()

View File

@ -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)

View File

@ -45,16 +45,9 @@ Please note that all settings are mandatory<br /><br />
<table id="table-6">
<tr><th><b>WIFI-SSID</b></th><td><input id="ssid" name="ssid" value="$SSID"></td><td>SSID of the wireless network</td></tr>
<tr><th>WIFI-Password</th><td><input id="password" name="password"></td><td>Password of the wireless network</td></tr>
<tr><th>SNTP Server</th><td><input id="sntpserver" name="sntpserver" value="$SNTPSERVER"></td><td>Server to sync the time with. Only one ntp server is allowed.</tr>
<tr><th>Offset to UTC time</th><td><input id="timezoneoffset" name="timezoneoffset" value="$TIMEOFFSET"></td><td>Define the offset to UTC time in hours. For example +1 hour for Germany</tr>
<tr><th>SNTP Server</th><td><input id="sntpserver" name="sntpserver" value="$SNTPSERVER"></td><td>Server to sync the time with. Only one ntp server is allowed.</td></tr>
<tr><th>Offset to UTC time</th><td><input id="timezoneoffset" name="timezoneoffset" value="$TIMEOFFSET"></td><td>Define the offset to UTC time in hours. For example +1 hour for Germany</td></tr>
<tr><th>Foreground Color</th><td><input type="color" name="fcolor" value="$HEXCOLORFG"></td><td>LED Color for all minutes, divisible by five</td></tr>
<tr><th>Background Color</th><td><input type="color" name="bcolor" value="$HEXCOLORBG"></td><td>Background LED Color</td></tr>
<tr><th>1. Minute Color</th><td><input type="color" name="colorMin1" value="$HEXCOLOR1"></td><td>First minute after</td></tr>
<tr><th>2. Minute Color</th><td><input type="color" name="colorMin2" value="$HEXCOLOR2"></td><td>Second minute after</td></tr>
<tr><th>3. Minute Color</th><td><input type="color" name="colorMin3" value="$HEXCOLOR3"></td><td>Third minute after</td></tr>
<tr><th>4. Minute Color</th><td><input type="color" name="colorMin4" value="$HEXCOLOR4"></td><td>Fourth minute after</td></tr>
<tr><th>Three quater</th><td><input type="checkbox" name="threequater" $THREEQUATER></td><td>Dreiviertel Joa/nei</td></tr>
<tr><td colspan="3"><div align="center"><input type="submit" value="Save Configuration" onclick="this.value='Submitting ..';this.disabled='disabled'; this.form.submit();"></div></td></tr>
<tr><td colspan="3"><div align="center"><input type="submit" name="action" value="Reboot"></div></td></tr>
</table>

View File

@ -1,7 +1,7 @@
--TODO:
configFile="config.lua"
httpSending=false
sentBytes=0
function sendPage(conn, nameOfFile, replaceMap)
collectgarbage()
@ -11,8 +11,9 @@ function sendPage(conn, nameOfFile, replaceMap)
conn:close()
print("Page sent")
collectgarbage()
httpSending=false
else
print("Next")
collectgarbage()
sendPage(conn, nameOfFile, replaceMap)
end
end)
@ -32,9 +33,6 @@ function sendPage(conn, nameOfFile, replaceMap)
local line = file.readline()
while (line ~= nil) do
-- increase the amount of sent bytes
sentBytes=sentBytes+string.len(line)
-- all placeholder begin with a $, so search for it in the current line
if (line:find("$") ~= nil) then
-- Replace the placeholder with the dynamic content
@ -45,10 +43,15 @@ function sendPage(conn, nameOfFile, replaceMap)
end
end
end
-- increase the amount of sent bytes
sentBytes=sentBytes+string.len(line)
buf = buf .. line
-- Sent after 1k data
if (string.len(buf) >= 700) then
-- Sent after 500 bytes data
if ( (string.len(buf) >= 500) or (node.heap() < 2000) ) then
line=nil
conn:send(buf)
print("Sent part of " .. sentBytes .. "B")
@ -62,10 +65,11 @@ function sendPage(conn, nameOfFile, replaceMap)
--reset amount of sent bytes, as we reached the end
sentBytes=0
-- send the rest
if (string.len(buf) > 0) then
conn:send(buf)
print("Sent rest")
end
end
end
function fillDynamicMap()
@ -118,24 +122,61 @@ 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
function stopWordclock()
print("Stop all Wordclock")
-- Stop all
for i=0,5 do tmr.stop(i) end
-- unload all other functions
-- grep function *.lua | grep -v webserver | grep -v init.lua | grep -v main.lua | cut -f 2 -d ':' | grep "^function" | sed "s/function //g" | grep -o "^[a-zA-Z0-9\_]*"
updateColor = nil
drawLEDs = nil
round = nil
generateLEDs = nil
isSummerTime = nil
getUTCtime = nil
getTime = nil
display_timestat = nil
collectgarbage()
end
function startWebServer()
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(conn,payload)
if (httpSending) then
print("HTTP sending... be patient!")
return
end
if (payload:find("GET /") ~= nil) then
--here is code for handling http request from a web-browser
httpSending=true
stopWordclock()
if (color == nil) then
color=string.char(0,128,0)
end
ws2812.write(string.char(0,0,0):rep(56) .. color:rep(2) .. string.char(0,0,0):rep(4) .. color:rep(2) .. string.char(0,0,0):rep(48))
-- Start Time after 3 minute
tmr.alarm(5, 180000, 0 ,function()
dependModules = { "timecore" , "wordclock", "displayword" }
for _,mod in pairs(dependModules) do
print("Loading " .. mod)
mydofile(mod)
end
-- Start the time Thread again
tmr.alarm(1, 20000, 1 ,function()
displayTime()
end)
end)
if (sendPage ~= nil) then
print("Sending webpage.html ...")
print("Sending webpage.html (" .. tostring(node.heap()) .. "B free) ...")
-- Load the sendPagewebcontent
replaceMap=fillDynamicMap()
sendPage(conn, "webpage.html", replaceMap)
end
else if (payload:find("POST /") ~=nil) then
--code for handling the POST-request (updating settings)
_, postdatastart = payload:find("\r\n\r\n")
@ -165,7 +206,9 @@ function startWebServer()
file.remove(configFile .. ".new")
sec, _ = rtctime.get()
file.open(configFile.. ".new", "w+")
file.write("-- Config\n" .. "wifi.sta.config(\"" .. _POST.ssid .. "\",[[" .. _POST.password .. "]])\n" .. "sntpserverhostname=\"" .. _POST.sntpserver .. "\"\n" .. "timezoneoffset=\"" .. _POST.timezoneoffset .. "\"\n")
file.write("-- Config\n" .. "station_cfg={}\nstation_cfg.ssid=\"" .. _POST.ssid .. "\"\nstation_cfg.pwd=\"" .. _POST.password .. "\"\nstation_cfg.save=false\nwifi.sta.config(station_cfg)\n")
file.write("sntpserverhostname=\"" .. _POST.sntpserver .. "\"\n" .. "timezoneoffset=\"" .. _POST.timezoneoffset .. "\"\n".. "inv46=\"" .. tostring(_POST.inv46) .. "\"\n" .. "dim=\"" .. tostring(_POST.dim) .. "\"\n")
if ( _POST.fcolor ~= nil) then
-- color=string.char(_POST.green, _POST.red, _POST.blue)
print ("Got fcolor: " .. _POST.fcolor)
@ -278,18 +321,17 @@ function startWebServer()
global_c=nil
end)
print("Welcome to Word Clock")
end
end
end)
conn:on("disconnection", function(c)
print("Goodbye")
node.output(nil) -- un-register the redirect output function, output goes to serial
collectgarbage()
--reset amount of sent bytes, as we reached the end
sentBytes=0
end)
end)
end
--FileView done.

View File

@ -12,7 +12,7 @@ function display_timestat(hours, minutes, longmode)
end
-- generate an empty return type
local ret = { itis=0, fiveMin=0, tenMin=0, after=0, before=0, threeHour=0, quater=0, threequater=0, half=0, s=0,
local ret = { it=0, is=0, fiveMin=0, tenMin=0, after=0, before=0, threeHour=0, quater=0, threequater=0, half=0, s=0,
one=0, oneLong=0, two=0, three=0, four=0, five=0, six=0, seven=0, eight=0, nine=0, ten=0, eleven=0, twelve=0,
twenty=0,
clock=0, sr_nc=0, min1=0, min2=0, min3=0, min4=0 }
@ -31,7 +31,8 @@ function display_timestat(hours, minutes, longmode)
if ((longmode==1)
or (minutes==0)
or (minutes==6)) then
ret.itis=1
ret.it=1
ret.is=1
end
-- Handle minutes
@ -63,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
@ -104,10 +105,10 @@ function display_timestat(hours, minutes, longmode)
end
if (hours == 1) then
if (ret.before == 1) then
ret.oneLong = 1
else
if ((ret.it == 1) and (ret.half == 0) ) then
ret.one=1
else
ret.oneLong=1
end
elseif (hours == 2) then
ret.two=1
@ -135,82 +136,3 @@ function display_timestat(hours, minutes, longmode)
collectgarbage()
return ret
end
-- @fn display_countwords
-- Count the amount of characters, used to describe the current time in words
-- @param words the same structure, as generated with the function @see display_timestat
-- @return the amount of words, used to describe the time or <code>0</code> on errors
function display_countwords_de(words)
local amount=0
if (words.itis == 1) then
amount = amount + 5
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