Compare commits
421 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb703a1165 | ||
|
8001a8e1d4 | ||
|
bf61184217 | ||
|
a13d74e005 | ||
|
d6cc8f73cc | ||
|
a2df4e3137 | ||
|
6160e00e17 | ||
|
f440ccf684 | ||
|
1755c9ef5a | ||
|
a755a1e5b3 | ||
|
017aef74f6 | ||
|
689ef10922 | ||
|
ad9beccadc | ||
|
db3a1e5e20 | ||
|
35a802009b | ||
|
bba21e562b | ||
|
a4893cc411 | ||
|
7e7736e12f | ||
|
e676f6f8cf | ||
|
0b72ed49bf | ||
|
158f989c53 | ||
|
f30143709a | ||
|
31cf255426 | ||
|
4ec5298ca5 | ||
|
cc768cdba9 | ||
|
875f188a47 | ||
|
0e5913095d | ||
|
800003f245 | ||
|
b9300048da | ||
|
aba8e21406 | ||
|
2322276745 | ||
|
042bef0bf7 | ||
|
a98a30c06b | ||
|
7f6c9689ea | ||
|
f35da0a3c8 | ||
|
40fce7763e | ||
|
5703c27576 | ||
|
3b2554ee53 | ||
|
fd85314d37 | ||
|
b8f037d61c | ||
|
8bc3b0cb66 | ||
|
ffa3bb69de | ||
|
96b690d90b | ||
|
0bc446fc2a | ||
|
e978e44063 | ||
|
ba1e93a67c | ||
|
412db554a0 | ||
|
d6d305ec70 | ||
|
7cf761c7a5 | ||
|
0f2855aafa | ||
|
b0f0481b6f | ||
|
5d96adb424 | ||
|
57de18b37c | ||
|
a5ddb2994d | ||
|
eff23d14cf | ||
|
2790a62175 | ||
|
d5986470fe | ||
|
19ff6d11c2 | ||
|
f021012a67 | ||
|
9b113099aa | ||
|
e10ad2fafc | ||
|
1863ab15b9 | ||
|
379233327c | ||
|
8b58d4a39f | ||
|
23bcd36a34 | ||
|
6fbaec749a | ||
|
e497bbe48e | ||
|
97bf9357f7 | ||
|
682201626c | ||
|
9508542158 | ||
|
41179c4afd | ||
|
94e554843f | ||
|
92a1c2c8d2 | ||
|
667350781c | ||
|
7ef7efbdad | ||
|
7d60405877 | ||
|
9cd54da774 | ||
|
91ab8609b9 | ||
|
7a2814d327 | ||
|
116c30f5e5 | ||
|
c513f7c899 | ||
|
3832e89afa | ||
|
7939bf578d | ||
|
2e77516d43 | ||
|
fd5ec411f1 | ||
|
2d7d1f65d5 | ||
|
b06ee392ab | ||
|
1ef3cf4f53 | ||
|
e2592cc774 | ||
|
4d4be95a3c | ||
|
447e17bb56 | ||
|
273e8c5b64 | ||
|
d4617d9861 | ||
|
93504e3d96 | ||
|
e924b638f1 | ||
|
6a70803b6c | ||
|
070d5de729 | ||
|
e7032a62b0 | ||
|
48b7751092 | ||
|
54a3761cee | ||
|
db22d50c26 | ||
|
639ce7ea72 | ||
|
5714c9f004 | ||
|
37b551c3fc | ||
|
6f7b4b111e | ||
|
a7bec14521 | ||
|
feaddece6e | ||
|
a66f2839c3 | ||
|
238618b245 | ||
|
d1a67f1cb4 | ||
|
890c84dfbd | ||
|
efee1dab1f | ||
|
efadc4bc06 | ||
|
5f8aa3c221 | ||
|
07e4ac2c43 | ||
|
780ea8f738 | ||
|
87c157fd10 | ||
|
cf74a0ae90 | ||
|
6b67a058cb | ||
|
d4f9e53ad4 | ||
|
34e7f173fc | ||
|
fc5bd38ac1 | ||
|
eb1760cbaa | ||
|
7b31b01259 | ||
|
5fafff7abc | ||
|
dcc660d8d6 | ||
|
9da84127f4 | ||
|
b02392dc64 | ||
|
bf360700d3 | ||
|
c5a99e58f0 | ||
|
6232bf5180 | ||
|
621cef2ab5 | ||
|
d309315e28 | ||
|
d1908c99c4 | ||
|
a355cff6e2 | ||
|
72734f1337 | ||
|
744c36b8b5 | ||
|
bb1aaa32a6 | ||
|
407ea2d2fa | ||
|
b5f36e5445 | ||
|
3059744dd7 | ||
|
712ddaafeb | ||
|
e4b22a9921 | ||
|
40447dd521 | ||
|
7941501cc5 | ||
|
84f5611a67 | ||
|
0857eb0e3c | ||
|
92276aff2d | ||
|
0b9e96518f | ||
|
1f92adc195 | ||
|
e9325a6c19 | ||
|
55e4c78572 | ||
|
404bb708db | ||
|
08f310c149 | ||
|
fbbecd0b6d | ||
|
716b31303f | ||
|
6699b70f14 | ||
|
89e215a609 | ||
|
a3aa7018d1 | ||
|
da139e3412 | ||
|
54095e7c20 | ||
|
6193618d1b | ||
|
f93d0df461 | ||
|
f72295e709 | ||
|
fc97df71ea | ||
|
768ff42c1a | ||
|
0e8fc88646 | ||
|
c2b40f692d | ||
|
2fc241a6af | ||
|
93b889f706 | ||
|
999c8ae3ac | ||
|
c73478d86e | ||
|
c3638989cd | ||
|
f79195e9a5 | ||
|
5b96bcc7bb | ||
|
f1baf8b916 | ||
|
1cea6b377e | ||
|
b5178d5f5b | ||
|
95aad4f254 | ||
|
84edd69c96 | ||
|
9763f4b573 | ||
|
4e9d78c86a | ||
|
9eabc733f4 | ||
|
a68313e55f | ||
|
b5658b048a | ||
|
62f40d202c | ||
|
8789f75e27 | ||
|
9534368b01 | ||
|
34e47c9a39 | ||
|
86a9503de9 | ||
|
8c454f0a9a | ||
|
8c18722ded | ||
|
3875afc047 | ||
|
c5aae1c7e3 | ||
|
b44eb6f13e | ||
|
30343fe638 | ||
|
7195c08992 | ||
|
8d6fc180a3 | ||
|
fb2556c62c | ||
|
d1b5e67ce6 | ||
|
9d764a3b8e | ||
|
7b02f1a447 | ||
|
6746d62567 | ||
|
efe5b38e58 | ||
|
7e7a87f90a | ||
|
10104b5d0e | ||
|
1eaf05a1ad | ||
|
b95f59ee7f | ||
|
de47de1e6f | ||
|
df7f33bbf0 | ||
|
8858fa19bd | ||
|
a2021fcb48 | ||
|
fe8817bfe6 | ||
|
ebacc86f01 | ||
|
b5f46a14d2 | ||
|
344fb0ab9a | ||
|
2f9f9fdc68 | ||
|
17a9416607 | ||
|
65d2de6801 | ||
|
edfb9f2803 | ||
|
77ebc55dbb | ||
|
3bd4fc8cb6 | ||
|
ba6193ff5b | ||
|
c85e3b34e0 | ||
|
c9b248531a | ||
|
f79524f6b2 | ||
|
aed713a1ff | ||
|
91c0a15212 | ||
|
7bc6ec1fbe | ||
|
e7d0ce1c43 | ||
|
401c4fd293 | ||
|
70602f1095 | ||
|
a7f566f706 | ||
|
26b16a1e3f | ||
|
e68ab9f715 | ||
|
10fc3a210a | ||
|
a81cf8f31d | ||
|
32106035c2 | ||
|
dacf22af1e | ||
|
22afc1e4c4 | ||
|
a36dc8d033 | ||
|
b9e3812a72 | ||
|
e14a7a576e | ||
|
69a2158ca6 | ||
|
13343fa645 | ||
|
467f525764 | ||
|
ba6734d5ad | ||
|
c3e48fb17a | ||
|
97de5dbb78 | ||
|
ab37d28904 | ||
|
aeae13642f | ||
|
a11f756c42 | ||
|
aeb5dab9fd | ||
|
417c93583d | ||
|
8c73a1a8a0 | ||
|
0aacfc2222 | ||
|
445c4b4a0e | ||
|
902ebdb85f | ||
|
e33ad2639e | ||
|
e5d7ec016b | ||
|
b5ec6cf619 | ||
|
da743d8b0a | ||
|
4577a0768e | ||
|
1b5ce385df | ||
|
b138b3c733 | ||
|
e4c8118731 | ||
|
f67f53d223 | ||
|
18da5e7e3b | ||
|
a8ca5ee47b | ||
|
0e49a6842b | ||
|
27d8f68e84 | ||
|
1543af8777 | ||
|
b38269166b | ||
|
f7cfa9ecb2 | ||
|
6def7cbc10 | ||
|
287d1eef42 | ||
|
b4e8eec9bf | ||
|
883adb671c | ||
|
e03ecf97b6 | ||
|
c3b94e0ade | ||
|
784a75241c | ||
|
7052c4c25a | ||
|
0adb96174e | ||
|
1d98087c98 | ||
|
4155d0018e | ||
|
5f4e2afc35 | ||
|
df1952ca04 | ||
|
c94e046f6c | ||
|
a37474724a | ||
|
aea5608b71 | ||
|
8c455fda26 | ||
|
3b1e94fe3b | ||
|
62a419fa59 | ||
|
6e80d1141d | ||
|
15a21b78b5 | ||
|
e4e58bfe24 | ||
|
caea476b40 | ||
|
9aa94b3372 | ||
|
613cf39086 | ||
|
70575e78df | ||
|
65e6900c19 | ||
|
d6b09b9113 | ||
|
6082899391 | ||
|
e9c28e8547 | ||
|
8b3e61be61 | ||
|
08b0a9372b | ||
|
3f8abd30bf | ||
|
c9924f4a22 | ||
|
83934efeaa | ||
|
e01d699e8a | ||
|
674835ae73 | ||
|
7965229dac | ||
|
a4b085054e | ||
|
ca47018a32 | ||
|
28b7bbe04a | ||
|
d3d15776dc | ||
|
8dc7cf2af7 | ||
|
e25a36ba33 | ||
|
d851964bc9 | ||
|
0038a7997e | ||
|
cb5205d4af | ||
|
be8b8a99ca | ||
|
238487929a | ||
|
fc81ac63f7 | ||
|
4522e40d52 | ||
|
f51613255b | ||
|
78a9027ad7 | ||
|
a83600281d | ||
|
225f4af842 | ||
|
36fea30afc | ||
|
68c38fed52 | ||
|
989d8b263a | ||
|
34bec501ca | ||
|
f2b625f0cb | ||
|
b597441192 | ||
|
b3283d8dcc | ||
|
061b4cff46 | ||
|
18e2f348a9 | ||
|
6d3f161b09 | ||
|
f5c3064773 | ||
|
93c6365ef8 | ||
|
3163148238 | ||
|
6813822f6d | ||
|
3a55addf66 | ||
|
8858bf2289 | ||
|
6621dee748 | ||
|
068f4b62e2 | ||
|
1051abbc45 | ||
|
24c5d23bef | ||
|
e485f2a0bd | ||
|
0085f9ae22 | ||
|
467530bec1 | ||
|
4be4e3fcee | ||
|
0ec3e0c159 | ||
|
e42edd7ccc | ||
|
542bd6613d | ||
|
2acd9af762 | ||
|
9adf5b9ed7 | ||
|
353e6f0117 | ||
|
fba6a90085 | ||
|
11bf35f22e | ||
|
b0d64e4140 | ||
|
cee1eeb6f3 | ||
|
84a36f2c6b | ||
|
4ee159cba3 | ||
|
a68f7571e7 | ||
|
1bbfb7feee | ||
|
38e591d061 | ||
|
4810985a54 | ||
|
b80a604b46 | ||
|
687a2bbbb9 | ||
|
d2ad06abf1 | ||
|
8aa34ce408 | ||
|
b99728ada6 | ||
|
60066bfca2 | ||
|
d1ad170955 | ||
|
3cb00ff3df | ||
|
7f0d578ca5 | ||
|
4b1cea0224 | ||
|
cdcd37a997 | ||
|
a20064bf61 | ||
|
093ab81c34 | ||
|
67c506571f | ||
|
8de86b065d | ||
|
a4eda884e3 | ||
|
dc8c5b5361 | ||
|
6e8a35307e | ||
|
eebe794df2 | ||
|
141b1b25d9 | ||
|
065caa4ddd | ||
|
28fca5989b | ||
|
28a5bdae85 | ||
|
10fa0b67a6 | ||
|
d166fb68df | ||
|
4ddbebd040 | ||
|
8321e5f112 | ||
|
b1f702df75 | ||
|
499cbcbbc5 | ||
|
1dc2530d38 | ||
|
d9f8d04f55 | ||
|
d25d3d55a7 | ||
|
1c0b5883b9 | ||
|
27ca03878c | ||
|
7140717696 | ||
|
6874d1545b | ||
|
58eed3bef5 | ||
|
4a97dbe0ca | ||
|
fc8ddab368 | ||
|
3e5327447b | ||
|
bab4c89d7b | ||
|
07852a6f25 | ||
|
c19d33d24f | ||
|
16b9675a4b | ||
|
0c8ab7155f | ||
|
b252453f1e | ||
|
309641c023 | ||
|
61c97d7a20 | ||
|
67434714ee | ||
|
53477902c2 | ||
|
c431d93895 | ||
|
1c7f3156ad |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
wlancfg.lua
|
wlancfg.lua
|
||||||
|
config.lua
|
||||||
|
simulation.config.lua
|
||||||
*.swp
|
*.swp
|
||||||
unit/testTimesMarchOctoberWithAllSeconds.lua
|
unit/testTimesMarchOctoberWithAllSeconds.lua
|
||||||
|
diet/*
|
||||||
|
104
Readme.md
104
Readme.md
@ -1,12 +1,106 @@
|
|||||||
# ESP Wordclock
|
# ESP Wordclock
|
||||||
## Setup
|
## Setup
|
||||||
Generate a wlancfg.lua for your wifi based on the given example wlancfg.lua.example
|
|
||||||
|
|
||||||
Copy the required files to the microcontroller:
|
### 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)
|
||||||
|
|
||||||
|
The firmware can be downloaded with the following script:
|
||||||
<pre>
|
<pre>
|
||||||
sudo ./programESP.sh serial wlancfg.lua.lua wlancfg.lua
|
cd os/
|
||||||
sudo ./programESP.sh serial init.lua init.lua
|
./flash.sh ttyUSB0
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Internal Setup
|
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.restart()
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Then disconnect the serial terminal and copy the required files to the microcontroller:
|
||||||
|
<pre>
|
||||||
|
./tools/initialDietFlash.sh /dev/ttyUSB0
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Install the optional packages:
|
||||||
|
<pre>
|
||||||
|
./tools/initialDietFlash.sh /dev/ttyUSB0 mqtt.lua
|
||||||
|
./tools/initialDietFlash.sh /dev/ttyUSB0 mqtt2.lua
|
||||||
|
./tools/initialDietFlash.sh /dev/ttyUSB0 ds18b20.lua
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 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
|
* GPIO2 LEDs
|
||||||
|
* GPIO0 Bootloader (at start)
|
||||||
|
* GPIO0 factory reset (long during operation)
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
* ADC VT93N2, 48k light resistor
|
||||||
|
* GPIO4 DS18B20 Temperatur sensor
|
||||||
|
|
||||||
|
## MQTT Interface
|
||||||
|
### Status
|
||||||
|
* **basetopic**/brightness **Current brightness in percent**
|
||||||
|
* **basetopic**/background **Current background color**
|
||||||
|
* **basetopic**/color **Current foreground color**
|
||||||
|
* **basetopic**/color1 **Current foreground color for first minute**
|
||||||
|
* **basetopic**/color2 **Current foreground color for second minute**
|
||||||
|
* **basetopic**/color3 **Current foreground color for third minute**
|
||||||
|
* **basetopic**/color4 **Current foreground color for fourth minute**
|
||||||
|
* **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/color
|
||||||
|
* 0-255,0-255,0-255 **Foreground color is set to decimal representation of red, green an blue**
|
||||||
|
* **basetopic**/cmd/color1
|
||||||
|
* 0-255,0-255,0-255 **Foreground color for first minute is set to decimal representation of red, green an blue**
|
||||||
|
* **basetopic**/cmd/color2
|
||||||
|
* 0-255,0-255,0-255 **Foreground color for second minute is set to decimal representation of red, green an blue**
|
||||||
|
* **basetopic**/cmd/color3
|
||||||
|
* 0-255,0-255,0-255 **Foreground color for third minute is set to decimal representation of red, green an blue**
|
||||||
|
* **basetopic**/cmd/color4
|
||||||
|
* 0-255,0-255,0-255 **Foreground color for fourth minute is set to decimal representation of red, green an blue**
|
||||||
|
* **basetopic**/cmd/telnet
|
||||||
|
* ignored **Stop MQTT server, clock 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
186
commands.lua
Normal 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
|
372
displayword.lua
Normal file
372
displayword.lua
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
-- Module filling a buffer, sent to the LEDs
|
||||||
|
local M
|
||||||
|
do
|
||||||
|
|
||||||
|
local data={}
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
-- @fn updateColor
|
||||||
|
-- Module displaying of the words
|
||||||
|
-- @param data struct with the following paramter:
|
||||||
|
-- aoC amount of characters for the complete message
|
||||||
|
-- mC amout of minutes to show
|
||||||
|
-- dC drawn characters
|
||||||
|
local updateColor = function (data)
|
||||||
|
if (data.aoC > 0) and (data.mC ~= nil) then
|
||||||
|
local specialChar = data.dC
|
||||||
|
if (data.mC < 1) then
|
||||||
|
specialChar = 0
|
||||||
|
elseif (data.dC > data.mC) then
|
||||||
|
specialChar = 0
|
||||||
|
end
|
||||||
|
if (specialChar < 1) then
|
||||||
|
return data.colorFg
|
||||||
|
elseif (specialChar < 2) then
|
||||||
|
return data.colorM1
|
||||||
|
elseif (specialChar < 3) then
|
||||||
|
return data.colorM2
|
||||||
|
elseif (specialChar < 4) then
|
||||||
|
return data.colorM3
|
||||||
|
elseif (specialChar < 5) then
|
||||||
|
return data.colorM4
|
||||||
|
else
|
||||||
|
return data.colorFg
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return data.colorFg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local drawLEDs = function(data, offset, numberNewChars)
|
||||||
|
if (numberNewChars == nil) then
|
||||||
|
numberNewChars=0
|
||||||
|
end
|
||||||
|
if (data.rgbBuffer == nil) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for i=1,numberNewChars do
|
||||||
|
data.dC=data.dC+1
|
||||||
|
data.rgbBuffer:set(tonumber(offset + i - 1), updateColor(data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- @fn swapLine
|
||||||
|
-- @param lineOffset offset (starting at 1) where the line is located to be swapped
|
||||||
|
-- works on the rgbBuffer, defined in data struct
|
||||||
|
-- @return <code>false</code> on errors, else <code>true</code>
|
||||||
|
local swapLine = function(data, lineOffset)
|
||||||
|
if (data.rgbBuffer == nil) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for i = 0,4 do
|
||||||
|
local num=tonumber(lineOffset)+i
|
||||||
|
local num2=tonumber(tonumber(lineOffset)+10-i)
|
||||||
|
local tmpC1, tmpC2, tmpC3=data.rgbBuffer:get(num)
|
||||||
|
local c1, c2, c3 =data.rgbBuffer:get(num2)
|
||||||
|
data.rgbBuffer:set(num, c1, c2, c3)
|
||||||
|
data.rgbBuffer:set(num2, tmpC1, tmpC2, tmpC3)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- @fn generateLEDs
|
||||||
|
-- Module displaying of the words
|
||||||
|
-- @param rgbBuffer OutputBuffer with 114 LEDs
|
||||||
|
-- @param words
|
||||||
|
-- @param colorBg background color
|
||||||
|
-- @param colorFg foreground color
|
||||||
|
-- @param colorM1 foreground color if one minute after a displayable time is present
|
||||||
|
-- @param colorM2 foreground color if two minutes after a displayable time is present
|
||||||
|
-- @param colorM3 foreground color if three minutes after a displayable time is present
|
||||||
|
-- @param colorM4 foreground color if four minutes after a displayable time is present
|
||||||
|
-- @param invertRows wheather line 4,5 and 6 shall be inverted or not
|
||||||
|
-- @param aoC Amount of characters to be displayed
|
||||||
|
local generateLEDs = function(rgbBuffer, words, colorBg, colorFg, colorM1, colorM2, colorM3, colorM4, invertRows, aoC)
|
||||||
|
-- Set the local variables needed for the colored progress bar
|
||||||
|
if (words == nil) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if (invertRows == nil) then
|
||||||
|
invertRows=false
|
||||||
|
end
|
||||||
|
|
||||||
|
local minutes=0
|
||||||
|
if (words.m1 == 1) then
|
||||||
|
minutes = minutes + 1
|
||||||
|
elseif (words.m2 == 1) then
|
||||||
|
minutes = minutes + 2
|
||||||
|
elseif (words.m3 == 1) then
|
||||||
|
minutes = minutes + 3
|
||||||
|
elseif (words.m4 == 1) then
|
||||||
|
minutes = minutes + 4
|
||||||
|
end
|
||||||
|
-- always set a foreground value
|
||||||
|
if (colorFg == nil) then
|
||||||
|
colorFg = string.char(255,255,255)
|
||||||
|
end
|
||||||
|
|
||||||
|
if (aoC ~= nil) then
|
||||||
|
data.aoC = aoC
|
||||||
|
data.mC = minutes
|
||||||
|
else
|
||||||
|
data.aoC = 0
|
||||||
|
end
|
||||||
|
data.rgbBuffer = rgbBuffer
|
||||||
|
|
||||||
|
if ( (adc ~= nil) and (words.briPer ~= nil) ) then
|
||||||
|
local per = math.floor(100*adc.read(0)/1000)
|
||||||
|
words.briPer = tonumber( ((words.briPer * 4) + per) / 5)
|
||||||
|
print("Minutes : " .. tostring(minutes) .. " bright: " .. tostring(words.briPer) .. "% current: " .. tostring(per) .. "%")
|
||||||
|
data.colorFg = string.char(string.byte(colorFg,1) * briPer / 100, string.byte(colorFg,2) * briPer / 100, string.byte(colorFg,3) * briPer / 100)
|
||||||
|
data.colorM1 = string.char(string.byte(colorM1,1) * briPer / 100, string.byte(colorM1,2) * briPer / 100, string.byte(colorM1,3) * briPer / 100)
|
||||||
|
data.colorM2 = string.char(string.byte(colorM2,1) * briPer / 100, string.byte(colorM2,2) * briPer / 100, string.byte(colorM2,3) * briPer / 100)
|
||||||
|
data.colorM3 = string.char(string.byte(colorM3,1) * briPer / 100, string.byte(colorM3,2) * briPer / 100, string.byte(colorM3,3) * briPer / 100)
|
||||||
|
data.colorM4 = string.char(string.byte(colorM4,1) * briPer / 100, string.byte(colorM4,2) * briPer / 100, string.byte(colorM4,3) * briPer / 100)
|
||||||
|
else
|
||||||
|
-- devide by five (Minute 0, Minute 1 to Minute 4 takes the last chars)
|
||||||
|
data.colorFg=colorFg
|
||||||
|
data.colorM1=colorM1
|
||||||
|
data.colorM2=colorM2
|
||||||
|
data.colorM3=colorM3
|
||||||
|
data.colorM4=colorM4
|
||||||
|
end
|
||||||
|
data.dC=0 -- drawn characters
|
||||||
|
local charsPerLine=11
|
||||||
|
|
||||||
|
-- Background color must always be set
|
||||||
|
if (colorBg ~= nil) then
|
||||||
|
rgbBuffer:fill(string.byte(colorBg,1), string.byte(colorBg,2), string.byte(colorBg,3)) -- draw the background
|
||||||
|
end
|
||||||
|
|
||||||
|
local lineIdx=1
|
||||||
|
-- line 1----------------------------------------------
|
||||||
|
if (rowbgColor[1] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[1]) end
|
||||||
|
end
|
||||||
|
if (words.it==1) then
|
||||||
|
drawLEDs(data, lineIdx, 2) -- ES
|
||||||
|
end
|
||||||
|
-- K fill character
|
||||||
|
if (words.is == 1) then
|
||||||
|
drawLEDs(data, lineIdx+3, 3) -- IST
|
||||||
|
end
|
||||||
|
-- L fill character
|
||||||
|
if (words.m5== 1) then
|
||||||
|
drawLEDs(data, lineIdx+7, 4) -- FUENF
|
||||||
|
end
|
||||||
|
-- line 2-- even row (so inverted) --------------------
|
||||||
|
lineIdx=12
|
||||||
|
if (rowbgColor[2] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[2]) end
|
||||||
|
end
|
||||||
|
if (words.m10 == 1) then
|
||||||
|
drawLEDs(data, lineIdx, 4) -- ZEHN
|
||||||
|
end
|
||||||
|
if (words.m20 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 4, 7) -- ZWANZIG
|
||||||
|
end
|
||||||
|
-- swap line
|
||||||
|
swapLine(data,lineIdx)
|
||||||
|
-- line3----------------------------------------------
|
||||||
|
lineIdx=23
|
||||||
|
if (rowbgColor[3] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[3]) end
|
||||||
|
end
|
||||||
|
if (words.h3q == 1) then
|
||||||
|
drawLEDs(data,lineIdx, 11) -- DREIVIERTEL
|
||||||
|
elseif (words.hq == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 4, 7) -- VIERTEL
|
||||||
|
end
|
||||||
|
--line 4-------- even row (so inverted) -------------
|
||||||
|
lineIdx=34
|
||||||
|
if (rowbgColor[4] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[4]) end
|
||||||
|
end
|
||||||
|
if (words.ha == 1) then
|
||||||
|
-- TG
|
||||||
|
drawLEDs(data, lineIdx + 2, 4) -- NACH
|
||||||
|
end
|
||||||
|
if (words.hb == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 6, 3) -- VOR
|
||||||
|
end
|
||||||
|
if (invertRows ~= true) then
|
||||||
|
swapLine(data,lineIdx)
|
||||||
|
end
|
||||||
|
-- line 5 ----------------------------------------------
|
||||||
|
lineIdx=45
|
||||||
|
if (rowbgColor[5] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[5]) end
|
||||||
|
end
|
||||||
|
if (words.half == 1) then
|
||||||
|
drawLEDs(data, lineIdx, 4) -- HALB
|
||||||
|
-- X
|
||||||
|
end
|
||||||
|
if (words.h12 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 5,5) -- ZWOELF
|
||||||
|
-- P
|
||||||
|
end
|
||||||
|
if (invertRows == true) then
|
||||||
|
swapLine(data,lineIdx)
|
||||||
|
end
|
||||||
|
------------even row (so inverted) ---------------------
|
||||||
|
lineIdx=56
|
||||||
|
if (rowbgColor[6] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[6]) end
|
||||||
|
end
|
||||||
|
if (words.h7 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 5, 6) -- SIEBEN
|
||||||
|
elseif (words.h1l == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 2,4) -- EINS
|
||||||
|
elseif (words.h1 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 2, 3) -- EIN
|
||||||
|
elseif (words.h2 == 1) then
|
||||||
|
drawLEDs(data, lineIdx, 4) -- ZWEI
|
||||||
|
end
|
||||||
|
if (invertRows ~= true) then
|
||||||
|
swapLine(data,lineIdx)
|
||||||
|
end
|
||||||
|
------------------------------------------------
|
||||||
|
lineIdx=67
|
||||||
|
if (rowbgColor[7] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[7]) end
|
||||||
|
end
|
||||||
|
if (words.h3 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 1,4) -- DREI
|
||||||
|
elseif (words.h5 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 7, 4) -- FUENF
|
||||||
|
end
|
||||||
|
------------even row (so inverted) ---------------------
|
||||||
|
lineIdx=78
|
||||||
|
if (rowbgColor[8] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[8]) end
|
||||||
|
end
|
||||||
|
if (words.h4 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 7, 4) -- VIER
|
||||||
|
elseif (words.h9 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 3, 4) -- NEUN
|
||||||
|
elseif (words.h11 == 1) then
|
||||||
|
drawLEDs(data, lineIdx, 3) -- ELF
|
||||||
|
end
|
||||||
|
swapLine(data,lineIdx)
|
||||||
|
------------------------------------------------
|
||||||
|
lineIdx=89
|
||||||
|
if (rowbgColor[9] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[9]) end
|
||||||
|
end
|
||||||
|
if (words.h8 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 1, 4) -- ACHT
|
||||||
|
elseif (words.h10 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 5, 4) -- ZEHN
|
||||||
|
end
|
||||||
|
|
||||||
|
------------even row (so inverted) ---------------------
|
||||||
|
lineIdx=100
|
||||||
|
if (rowbgColor[10] ~= nil) then
|
||||||
|
for i=lineIdx,lineIdx+10, 1 do data.rgbBuffer:set(i, rowbgColor[10]) end
|
||||||
|
end
|
||||||
|
if (words.h6 == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 1, 5) -- SECHS
|
||||||
|
end
|
||||||
|
if (words.cl == 1) then
|
||||||
|
drawLEDs(data, lineIdx + 8, 3) -- UHR
|
||||||
|
end
|
||||||
|
swapLine(data,lineIdx)
|
||||||
|
------ Minutes -----------
|
||||||
|
if (words.m1 == 1) then
|
||||||
|
data.rgbBuffer:set(111, colorFg)
|
||||||
|
end
|
||||||
|
if (words.m2 == 1) then
|
||||||
|
data.rgbBuffer:set(112, colorFg)
|
||||||
|
end
|
||||||
|
if (words.m3 == 1) then
|
||||||
|
data.rgbBuffer:set(113, colorFg)
|
||||||
|
end
|
||||||
|
if (words.m4 == 1) then
|
||||||
|
data.rgbBuffer:set(114, colorFg)
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
|
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 == "m5") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "m10") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "ha") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "hb") then
|
||||||
|
characters = characters + 3
|
||||||
|
elseif (key == "h3") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "hq") then
|
||||||
|
characters = characters + 7
|
||||||
|
elseif (key == "h3q") then
|
||||||
|
characters = characters + 11
|
||||||
|
elseif (key == "half") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h1") then
|
||||||
|
characters = characters + 3
|
||||||
|
elseif (key == "h1l") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h2") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h3") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h4") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h5") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h6") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h7") then
|
||||||
|
characters = characters + 6
|
||||||
|
elseif (key == "h8") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h9") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h10") then
|
||||||
|
characters = characters + 4
|
||||||
|
elseif (key == "h11") then
|
||||||
|
characters = characters + 3
|
||||||
|
elseif (key == "h12") then
|
||||||
|
characters = characters + 5
|
||||||
|
elseif (key == "m20") then
|
||||||
|
characters = characters + 7
|
||||||
|
elseif (key == "cl") then
|
||||||
|
characters = characters + 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return characters
|
||||||
|
end
|
||||||
|
|
||||||
|
M = {
|
||||||
|
generateLEDs = generateLEDs,
|
||||||
|
round = round,
|
||||||
|
drawLEDs = drawLEDs,
|
||||||
|
updateColor = updateColor,
|
||||||
|
data = data,
|
||||||
|
countChars = countChars
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return M
|
138
ds18b20.lua
Normal file
138
ds18b20.lua
Normal 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
|
85
init.lua
Normal file
85
init.lua
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
uart.setup(0, 115200, 8, 0, 1, 1 )
|
||||||
|
print("Autostart in 5 seconds...")
|
||||||
|
|
||||||
|
ws2812.init() -- WS2812 LEDs initialized on GPIO2
|
||||||
|
local MAXLEDS=110
|
||||||
|
local counter1=0
|
||||||
|
ws2812.write(string.char(0,0,0):rep(114))
|
||||||
|
local bootledtimer = tmr.create()
|
||||||
|
bootledtimer:register(75, tmr.ALARM_AUTO, function (timer)
|
||||||
|
counter1=counter1+1
|
||||||
|
spaceLeds = math.max(MAXLEDS - (counter1*2), 0)
|
||||||
|
ws2812.write(string.char(16,0,0):rep(counter1) .. string.char(0,0,0):rep(spaceLeds) .. string.char(0,0,8):rep(counter1))
|
||||||
|
if ((counter1*2) > 114) then
|
||||||
|
timer:unregister()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
bootledtimer:start()
|
||||||
|
|
||||||
|
function mydofile(mod)
|
||||||
|
print("load:" .. mod)
|
||||||
|
if (file.open(mod .. ".lua")) then
|
||||||
|
dofile( mod .. ".lua")
|
||||||
|
elseif (file.open(mod .. "_diet.lua")) then
|
||||||
|
dofile(mod .. "_diet.lua")
|
||||||
|
elseif (file.open(mod .. "_diet.lc")) then
|
||||||
|
dofile(mod .. "_diet.lc")
|
||||||
|
elseif (file.open(mod)) then
|
||||||
|
dofile(mod)
|
||||||
|
else
|
||||||
|
print("NA: " .. mod)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
initTimer = tmr.create()
|
||||||
|
initTimer:register(5000, tmr.ALARM_SINGLE, function (t)
|
||||||
|
bootledtimer:unregister()
|
||||||
|
initTimer:unregister()
|
||||||
|
initTimer=nil
|
||||||
|
bootledtimer=nil
|
||||||
|
local modlist = { "timecore" , "displayword", "ds18b20", "mqtt", "main", "webserver" }
|
||||||
|
for i,mod in pairs(modlist) do
|
||||||
|
if (file.open(mod .. "_diet.lua")) then
|
||||||
|
file.remove(mod .. "_diet.lc")
|
||||||
|
print(tostring(i) .. ". Compile " .. mod)
|
||||||
|
ws2812.write(string.char(0,0,0):rep(11*i)..string.char(128,0,0):rep(11))
|
||||||
|
node.compile(mod .. "_diet.lua")
|
||||||
|
print("cleanup..")
|
||||||
|
file.remove(mod .. "_diet.lua")
|
||||||
|
node.restart()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ( file.open("config.lua") ) then
|
||||||
|
--- Normal operation
|
||||||
|
print("Starting main")
|
||||||
|
mydofile("main")
|
||||||
|
wifi.setmode(wifi.STATION)
|
||||||
|
dofile("config.lua")
|
||||||
|
normalOperation()
|
||||||
|
else
|
||||||
|
-- Logic for inital setup
|
||||||
|
collectgarbage()
|
||||||
|
wifi.setmode(wifi.SOFTAP)
|
||||||
|
cfg={}
|
||||||
|
cfg.ssid="wordclock"
|
||||||
|
cfg.pwd="wordclock"
|
||||||
|
wifi.ap.config(cfg)
|
||||||
|
|
||||||
|
-- Write the buffer to the LEDs
|
||||||
|
local color=string.char(0,128,0)
|
||||||
|
local white=string.char(0,0,0)
|
||||||
|
local ledBuf= white:rep(6) .. color .. white:rep(7) .. color:rep(3) .. white:rep(44) .. color:rep(3) .. white:rep(50)
|
||||||
|
ws2812.write(ledBuf)
|
||||||
|
color=nil
|
||||||
|
white=nil
|
||||||
|
ledBuf=nil
|
||||||
|
|
||||||
|
print("Waiting in access point >wordclock< for Clients")
|
||||||
|
print("Please visit 192.168.4.1")
|
||||||
|
-- start the webserver module
|
||||||
|
mydofile("webserver")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
initTimer:start()
|
268
main.lua
Normal file
268
main.lua
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
-- Main Module
|
||||||
|
mlt = tmr.create() -- Main loop timer
|
||||||
|
rowbgColor= {}
|
||||||
|
-- Buffer of the clock
|
||||||
|
rgbBuffer = ws2812.newBuffer(114, 3)
|
||||||
|
-- 110 Character plus one LED for each minute,
|
||||||
|
-- that cannot be displayed, as the clock as only a resolution of 5 minutes
|
||||||
|
|
||||||
|
function syncTimeFromInternet()
|
||||||
|
if (syncRunning == nil) then
|
||||||
|
print("NTP: " .. tostring(sntpserverhostname))
|
||||||
|
syncRunning=true
|
||||||
|
sntp.sync(sntpserverhostname,
|
||||||
|
function(sec,usec,server)
|
||||||
|
syncRunning=nil
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
print('NTP failed!')
|
||||||
|
syncRunning=nil
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function displayTime()
|
||||||
|
collectgarbage()
|
||||||
|
local sec, usec = rtctime.get()
|
||||||
|
-- Handle lazy programmer:
|
||||||
|
if (timezoneoffset == nil) then
|
||||||
|
timezoneoffset=0
|
||||||
|
end
|
||||||
|
local tc = require("timecore_diet")
|
||||||
|
if (tc == nil) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local time = tc.getTime(sec, timezoneoffset)
|
||||||
|
tc = nil
|
||||||
|
timecore_diet=nil
|
||||||
|
package.loaded["timecore_diet"]=nil
|
||||||
|
|
||||||
|
collectgarbage()
|
||||||
|
local wc = require("wordclock_diet")
|
||||||
|
if (wc ~= nil) then
|
||||||
|
words = wc.timestat(time.hour, time.minute)
|
||||||
|
if ((dim ~= nil) and (dim == "on")) then
|
||||||
|
words.briPer=briPer
|
||||||
|
if (words.briPer ~= nil and words.briPer < 3) then
|
||||||
|
words.briPer=3
|
||||||
|
end
|
||||||
|
else
|
||||||
|
words.briPer=nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
wc = nil
|
||||||
|
wordclock_diet=nil
|
||||||
|
package.loaded["wordclock_diet"]=nil
|
||||||
|
|
||||||
|
collectgarbage()
|
||||||
|
local dw = require("displayword_diet")
|
||||||
|
if (dw ~= 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 c = dw.countChars(words)
|
||||||
|
dw.generateLEDs(rgbBuffer, words, colorBg, color, color1, color2, color3, color4, invertRows, c)
|
||||||
|
end
|
||||||
|
dw = nil
|
||||||
|
displayword_diet=nil
|
||||||
|
package.loaded["displayword_diet"]=nil
|
||||||
|
|
||||||
|
collectgarbage()
|
||||||
|
|
||||||
|
-- cleanup
|
||||||
|
i=nil
|
||||||
|
briPer=words.briPer
|
||||||
|
words=nil
|
||||||
|
time=nil
|
||||||
|
collectgarbage()
|
||||||
|
end
|
||||||
|
|
||||||
|
function normalOperation()
|
||||||
|
-- use default color, if nothing is defined
|
||||||
|
if (color == nil) then
|
||||||
|
-- Color is defined as GREEN, RED, BLUE
|
||||||
|
color=string.char(0,0,250)
|
||||||
|
end
|
||||||
|
print("start: " , node.heap())
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Define the main loop
|
||||||
|
local setupCounter=5
|
||||||
|
local alive=0
|
||||||
|
mlt:register(1000, tmr.ALARM_AUTO, function (lt)
|
||||||
|
if (setupCounter > 4) then
|
||||||
|
if (colorBg ~= nil) then
|
||||||
|
rgbBuffer:fill(string.byte(colorBg,1), string.byte(colorBg,2), string.byte(colorBg,3)) -- disable all LEDs
|
||||||
|
else
|
||||||
|
rgbBuffer:fill(0,0,0) -- disable all LEDs
|
||||||
|
end
|
||||||
|
syncTimeFromInternet()
|
||||||
|
setupCounter=setupCounter-1
|
||||||
|
alive = 1
|
||||||
|
rgbBuffer:set(19, color) -- N
|
||||||
|
rgbBuffer:set(31, color) -- T
|
||||||
|
if ((inv46 ~= nil) and (inv46 == "on")) then
|
||||||
|
rgbBuffer:set(45, color) -- P
|
||||||
|
else
|
||||||
|
rgbBuffer:set(55, color) -- P
|
||||||
|
end
|
||||||
|
elseif (setupCounter > 3) then
|
||||||
|
if (web == nil) then
|
||||||
|
-- Here the WLAN is found, and something is done
|
||||||
|
mydofile("mqtt")
|
||||||
|
rgbBuffer:fill(0,0,0) -- disable all LEDs
|
||||||
|
if (startMqttClient ~= nil) then
|
||||||
|
if ((inv46 ~= nil) and (inv46 == "on")) then
|
||||||
|
rgbBuffer:set(34, color) -- M
|
||||||
|
else
|
||||||
|
rgbBuffer:set(44, color) -- M
|
||||||
|
end
|
||||||
|
rgbBuffer:set(82, color) -- T
|
||||||
|
startMqttClient()
|
||||||
|
else
|
||||||
|
print("NO Mqtt found")
|
||||||
|
mydofile("telnet")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print("webserver prepared")
|
||||||
|
end
|
||||||
|
setupCounter=setupCounter-1
|
||||||
|
elseif (setupCounter > 2) then
|
||||||
|
if (web == nil) then
|
||||||
|
if (startTelnetServer ~= nil) then
|
||||||
|
startTelnetServer()
|
||||||
|
else
|
||||||
|
displayTime()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print("webserver supplant telnet")
|
||||||
|
end
|
||||||
|
setupCounter=setupCounter-1
|
||||||
|
elseif ( (alive % 120) == 0) then
|
||||||
|
-- sync the time every 5 minutes
|
||||||
|
local heapusage = node.heap()
|
||||||
|
if (heapusage > 12000) then
|
||||||
|
syncTimeFromInternet()
|
||||||
|
end
|
||||||
|
heapusage=nil
|
||||||
|
alive = alive + 1
|
||||||
|
else
|
||||||
|
if (colorBg ~= nil) then
|
||||||
|
rgbBuffer:fill(string.byte(colorBg,1), string.byte(colorBg,2), string.byte(colorBg,3)) -- disable all LEDs
|
||||||
|
else
|
||||||
|
rgbBuffer:fill(0,0,0) -- disable all LEDs
|
||||||
|
end
|
||||||
|
displayTime()
|
||||||
|
alive = alive + 1
|
||||||
|
end
|
||||||
|
if (rgbBuffer ~= nil) then
|
||||||
|
-- show Mqtt status
|
||||||
|
if (startMqttClient ~= nil) then
|
||||||
|
if (not connectedMqtt()) then
|
||||||
|
rgbBuffer:set(103, 0, 64,0)
|
||||||
|
-- check every thirty seconds, if reconnecting is necessary
|
||||||
|
if (((tmr.now() / 1000000) % 100) == 30) then
|
||||||
|
print("MQTT reconnecting... ")
|
||||||
|
reConnectMqtt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ws2812.write(rgbBuffer)
|
||||||
|
else
|
||||||
|
-- set FG to fix value: RED
|
||||||
|
local color = string.char(255,0,0)
|
||||||
|
rgbBuffer:fill(0,0,0) -- disable all LEDs
|
||||||
|
for i=108,110, 1 do rgbBuffer:set(i, color) end
|
||||||
|
ws2812.write(rgbBuffer)
|
||||||
|
print("Fallback no time displayed")
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
|
-- Feed the system watchdog.
|
||||||
|
tmr.wdclr()
|
||||||
|
end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Connect to Wifi
|
||||||
|
local connect_counter=0
|
||||||
|
-- Wait to be connect to the WiFi access point.
|
||||||
|
local wifitimer = tmr.create()
|
||||||
|
wifitimer:register(500, tmr.ALARM_AUTO, function (timer)
|
||||||
|
connect_counter=connect_counter+1
|
||||||
|
if wifi.sta.status() ~= 5 then
|
||||||
|
print(connect_counter .. "/60 Connecting to AP...")
|
||||||
|
rgbBuffer:fill(0,0,0) -- clear all LEDs
|
||||||
|
if (connect_counter % 5 ~= 4) then
|
||||||
|
local wlanColor=string.char((connect_counter % 6)*20,(connect_counter % 5)*20,(connect_counter % 3)*20)
|
||||||
|
if ((connect_counter % 5) >= 1) then
|
||||||
|
rgbBuffer:set(7, wlanColor)
|
||||||
|
end
|
||||||
|
if ((connect_counter % 5) >= 3) then
|
||||||
|
rgbBuffer:set(15, wlanColor)
|
||||||
|
end
|
||||||
|
if ((connect_counter % 5) >= 2) then
|
||||||
|
rgbBuffer:set(16, wlanColor)
|
||||||
|
end
|
||||||
|
if ((connect_counter % 5) >= 0) then
|
||||||
|
rgbBuffer:set(17, wlanColor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ws2812.write(rgbBuffer)
|
||||||
|
else
|
||||||
|
wifitimer:unregister()
|
||||||
|
wifitimer=nil
|
||||||
|
connect_counter=nil
|
||||||
|
print('IP: ',wifi.sta.getip(), " heap: ", node.heap())
|
||||||
|
rgbBuffer:fill(0,0,0) -- clear all LEDs
|
||||||
|
rgbBuffer:set(13, color) -- I
|
||||||
|
if ((inv46 ~= nil) and (inv46 == "on")) then
|
||||||
|
rgbBuffer:set(45, color) -- P
|
||||||
|
else
|
||||||
|
rgbBuffer:set(55, color) -- P
|
||||||
|
end
|
||||||
|
ws2812.write(rgbBuffer)
|
||||||
|
mlt:start()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
wifitimer:start()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------main program -----------------------------
|
||||||
|
briPer = 50 -- Default brightness is set to 50%
|
||||||
|
ws2812.init() -- WS2812 LEDs initialized on GPIO2
|
||||||
|
|
||||||
|
----------- button ---------
|
||||||
|
gpio.mode(3, gpio.INPUT)
|
||||||
|
local btnCounter=0
|
||||||
|
-- Start the time Thread handling the button
|
||||||
|
local btntimer = tmr.create()
|
||||||
|
btntimer:register(500, tmr.ALARM_AUTO, function (t)
|
||||||
|
if (gpio.read(3) == 0) then
|
||||||
|
-- stop the main loop
|
||||||
|
if (mlt ~= nil) then
|
||||||
|
mlt:unregister()
|
||||||
|
mlt = nil
|
||||||
|
end
|
||||||
|
print("Button pressed " .. tostring(btnCounter))
|
||||||
|
btnCounter = btnCounter + 5
|
||||||
|
|
||||||
|
if ((web ~= nil) and (btnCounter < 50)) then
|
||||||
|
for i=1,btnCounter do rgbBuffer:set(i, 128, 0, 0) end
|
||||||
|
else
|
||||||
|
for i=1,btnCounter do rgbBuffer:set(i, 0, 128, 0) end
|
||||||
|
end
|
||||||
|
ws2812.write(rgbBuffer)
|
||||||
|
if (btnCounter >= 110) then
|
||||||
|
file.remove("config.lua")
|
||||||
|
file.remove("config.lc")
|
||||||
|
node.restart()
|
||||||
|
elseif (btnCounter == 10) then
|
||||||
|
collectgarbage()
|
||||||
|
mydofile("webserver")
|
||||||
|
-- start the webserver module
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
btntimer:start()
|
277
mqtt.lua
Normal file
277
mqtt.lua
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
-- Module Variables
|
||||||
|
-- Mqtt variable
|
||||||
|
local mMqttClient=nil
|
||||||
|
local mMqttConnected = false
|
||||||
|
|
||||||
|
function handleSingleCommand(client, topic, data)
|
||||||
|
if (data == "ON") then
|
||||||
|
briPer=100
|
||||||
|
mMqttClient:publish(mqttPrefix .. "/clock", "ON", 0, 0)
|
||||||
|
elseif (data == "OFF") then
|
||||||
|
briPer=0
|
||||||
|
mMqttClient: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 * briPer / 100, red * briPer / 100, blue * briPer / 100)
|
||||||
|
print("Updated BG: " .. tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue) )
|
||||||
|
mMqttClient:publish(mqttPrefix .. "/background", tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue), 0, 0)
|
||||||
|
else
|
||||||
|
if (tonumber(data) >= 0 and tonumber(data) <= 100) then
|
||||||
|
briPer=tonumber(data)
|
||||||
|
mMqttClient: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
|
||||||
|
-- @param per percent the color should be dimmed
|
||||||
|
function parseBgColor(data, row, per)
|
||||||
|
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
|
||||||
|
mMqttClient:publish(mqttPrefix .. "/"..row, tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue), 0, 0)
|
||||||
|
if (per ~= nil) then
|
||||||
|
return string.char(green * per / 100, red * per / 100, blue * per / 100)
|
||||||
|
else
|
||||||
|
return string.char(green , red , blue )
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function readTemp(ds18b20)
|
||||||
|
if (ds18b20 ~= nil) then
|
||||||
|
local addrs=ds18b20.addrs()
|
||||||
|
-- Total DS18B20 numbers
|
||||||
|
local sensors=table.getn(addrs)
|
||||||
|
local temp1=0
|
||||||
|
if (sensors >= 1) then
|
||||||
|
temp1=ds18b20.read(addrs[0])
|
||||||
|
else
|
||||||
|
print("No sensor DS18B20 found")
|
||||||
|
end
|
||||||
|
return temp1
|
||||||
|
else
|
||||||
|
print("No DS18B20 lib")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Connect or reconnect to mqtt server
|
||||||
|
function reConnectMqtt()
|
||||||
|
if (not mMqttConnected) then
|
||||||
|
mMqttClient:connect(mqttServer, 1883, false, function(c)
|
||||||
|
print("MQTT is connected")
|
||||||
|
mMqttConnected = true
|
||||||
|
-- subscribe topic with qos = 0
|
||||||
|
mMqttClient:subscribe(mqttPrefix .. "/cmd/#", 0)
|
||||||
|
local tmr1 = tmr.create()
|
||||||
|
tmr1:register(1000, tmr.ALARM_SINGLE, function (dummyTemp)
|
||||||
|
-- publish a message with data = hello, QoS = 0, retain = 0
|
||||||
|
mMqttClient: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)
|
||||||
|
mMqttClient:publish(mqttPrefix .. "/background", tostring(red) .. "," .. tostring(green) .. "," .. tostring(blue), 0, 0)
|
||||||
|
tmr1:unregister()
|
||||||
|
tmr1=nil
|
||||||
|
end)
|
||||||
|
tmr1:start()
|
||||||
|
end,
|
||||||
|
function(client, reason)
|
||||||
|
print("failed reason: " .. reason)
|
||||||
|
mMqttConnected = false
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- MQTT extension
|
||||||
|
function registerMqtt()
|
||||||
|
mMqttClient = mqtt.Client("wordclock", 120)
|
||||||
|
-- on publish message receive event
|
||||||
|
mMqttClient:on("message", function(client, topic, data)
|
||||||
|
collectgarbage()
|
||||||
|
if data ~= nil then
|
||||||
|
local heapusage = node.heap()
|
||||||
|
if (heapusage < 11000) then
|
||||||
|
print("Skip " .. topic .. ":" .. data .. "; heap:" .. heapusage)
|
||||||
|
heapusage=nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
heapusage=nil
|
||||||
|
print("MQTT " .. topic .. ":" .. data)
|
||||||
|
if (topic == (mqttPrefix .. "/cmd/single")) then
|
||||||
|
handleSingleCommand(client, topic, data)
|
||||||
|
elseif (topic == (mqttPrefix .. "/cmd/num/val")) then
|
||||||
|
if (( data == "" ) or (data == nil)) then
|
||||||
|
tw=nil
|
||||||
|
print("MQTT | wordclock failed")
|
||||||
|
else
|
||||||
|
-- generate the temperatur to display, once as it will not change
|
||||||
|
local dispTemp = tonumber(data)
|
||||||
|
collectgarbage()
|
||||||
|
if (dispTemp > 0) then
|
||||||
|
local wc = require("wordclock_diet")
|
||||||
|
if (wc ~= nil) then
|
||||||
|
tw = wc.showText(dw, rgbBuffer, invertRows, dispTemp)
|
||||||
|
print("MQTT | generated words for: " .. tostring(dispTemp))
|
||||||
|
else
|
||||||
|
print("MQTT | wordclock failed")
|
||||||
|
end
|
||||||
|
wc = nil
|
||||||
|
wordclock_diet=nil
|
||||||
|
package.loaded["wordclock_diet"]=nil
|
||||||
|
else
|
||||||
|
tw=nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif (topic == (mqttPrefix .. "/cmd/num/col")) then
|
||||||
|
-- Set number of the color to display
|
||||||
|
if (( data ~= "" ) and (data ~= nil)) then
|
||||||
|
tcol = parseBgColor(data, "num/col")
|
||||||
|
else
|
||||||
|
tcol = nil
|
||||||
|
print("MQTT | Hide number")
|
||||||
|
end
|
||||||
|
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
|
||||||
|
mMqttConnected = false
|
||||||
|
if (mlt ~= nil) then
|
||||||
|
mlt:unregister()
|
||||||
|
else
|
||||||
|
print("main loop unstoppable")
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
|
mydofile("telnet")
|
||||||
|
if (startTelnetServer ~= nil) then
|
||||||
|
startTelnetServer()
|
||||||
|
else
|
||||||
|
print("Cannost start Telnet Server!")
|
||||||
|
end
|
||||||
|
elseif (string.match(topic, "color$")) then
|
||||||
|
color = parseBgColor(data, "color")
|
||||||
|
print("Updated color" )
|
||||||
|
elseif (string.match(topic, "color1$")) then
|
||||||
|
color1 = parseBgColor(data, "color1")
|
||||||
|
print("Updated color1" )
|
||||||
|
elseif (string.match(topic, "color2$")) then
|
||||||
|
color2 = parseBgColor(data, "color2")
|
||||||
|
print("Updated color2" )
|
||||||
|
elseif (string.match(topic, "color3$")) then
|
||||||
|
color3 = parseBgColor(data, "color3")
|
||||||
|
print("Updated color3" )
|
||||||
|
elseif (string.match(topic, "color4$")) then
|
||||||
|
color4 = parseBgColor(data, "color4")
|
||||||
|
print("Updated color4" )
|
||||||
|
--FIXME load here the mqtt2 file
|
||||||
|
else
|
||||||
|
for i=1,10,1 do
|
||||||
|
if (string.match(topic, "row".. tostring(i) .."$")) then
|
||||||
|
rowbgColor[i] = parseBgColor(data, "row" .. tostring(i), briPer)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
-- do something
|
||||||
|
mMqttClient:on("offline", function(client)
|
||||||
|
print("MQTT Disconnected")
|
||||||
|
mMqttConnected = false
|
||||||
|
collectgarbage()
|
||||||
|
end)
|
||||||
|
reConnectMqtt()
|
||||||
|
end
|
||||||
|
|
||||||
|
function connectedMqtt()
|
||||||
|
return mMqttConnected
|
||||||
|
end
|
||||||
|
|
||||||
|
function startMqttClient()
|
||||||
|
if (mqttServer ~= nil and mqttPrefix ~= nil) then
|
||||||
|
registerMqtt()
|
||||||
|
print "Started MQTT client"
|
||||||
|
local lSetupTimer = tmr.create()
|
||||||
|
lSetupTimer:register(123, tmr.ALARM_SINGLE, function (kTemp)
|
||||||
|
if (file.open("ds18b20_diet.lc")) then
|
||||||
|
t=true
|
||||||
|
print "Setup temperatur"
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
lSetupTimer:start()
|
||||||
|
local loldBrightness=0
|
||||||
|
local lMqttTimer = tmr.create()
|
||||||
|
local tempCounter=0
|
||||||
|
-- Check every 12 seconds
|
||||||
|
lMqttTimer:register(6321, tmr.ALARM_AUTO, function (kTemp)
|
||||||
|
if (mMqttConnected) then
|
||||||
|
-- Cleanup the initialization stuff
|
||||||
|
lSetupTimer=nil
|
||||||
|
-- Do the action
|
||||||
|
local heapusage = node.heap()
|
||||||
|
local temperatur = nil
|
||||||
|
if (loldBrightness ~= briPer) then
|
||||||
|
mMqttClient:publish(mqttPrefix .. "/brightness", tostring(briPer), 0, 0)
|
||||||
|
loldBrightness = briPer
|
||||||
|
else
|
||||||
|
if ((t ~= nil) and (heapusage > 11900) and (tempCounter > 10)) then
|
||||||
|
local ds18b20=require("ds18b20_diet")
|
||||||
|
ds18b20.setup(2) -- GPIO4
|
||||||
|
readTemp(ds18b20) -- read once, to setup chip
|
||||||
|
temperatur=readTemp(ds18b20)
|
||||||
|
if (temperatur == 8500) then
|
||||||
|
temperatur=nil
|
||||||
|
end
|
||||||
|
ds18b20=nil
|
||||||
|
ds18b20_diet=nil
|
||||||
|
package.loaded["ds18b20_diet"]=nil
|
||||||
|
tempCounter = 0
|
||||||
|
else
|
||||||
|
tempCounter = tempCounter + 1
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
|
if (temperatur ~= nil) then
|
||||||
|
mMqttClient:publish(mqttPrefix .. "/temp", tostring(temperatur/100)..".".. tostring(temperatur%100), 0, 0)
|
||||||
|
else
|
||||||
|
mMqttClient:publish(mqttPrefix .. "/heap", tostring(heapusage), 0, 0)
|
||||||
|
collectgarbage()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
heapusage=nil
|
||||||
|
temperatur=nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
lMqttTimer:start()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
34
mqtt2.lua
Normal file
34
mqtt2.lua
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
-- Global Variables
|
||||||
|
-- Display other numbers, e.g. Temperatur
|
||||||
|
tw=nil
|
||||||
|
tcol=nil
|
||||||
|
|
||||||
|
function parseMqttSub(client, topic, data)
|
||||||
|
|
||||||
|
if (topic == (mqttPrefix .. "/cmd/num/val")) then
|
||||||
|
if (( data == "" ) or (data == nil)) then
|
||||||
|
tw=nil
|
||||||
|
print("MQTT | wordclock failed")
|
||||||
|
else
|
||||||
|
-- generate the temperatur to display, once as it will not change
|
||||||
|
local dispTemp = tonumber(data)
|
||||||
|
collectgarbage()
|
||||||
|
mydofile("wordclock")
|
||||||
|
if (wc ~= nil) then
|
||||||
|
tw = wc.showText(dw, rgbBuffer, invertRows, dispTemp)
|
||||||
|
wc = nil
|
||||||
|
print("MQTT | generated words for: " .. tostring(dispTemp))
|
||||||
|
else
|
||||||
|
print("MQTT | wordclock failed")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif (topic == (mqttPrefix .. "/cmd/num/col")) then
|
||||||
|
-- Set number of the color to display
|
||||||
|
if (( data ~= "" ) and (data ~= nil)) then
|
||||||
|
tcol = parseBgColor(data, "num/col")
|
||||||
|
else
|
||||||
|
tcol = nil
|
||||||
|
print("MQTT | Hide number")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
31
ntpTest.lua
31
ntpTest.lua
@ -1,31 +0,0 @@
|
|||||||
dofile("wlancfg.lua")
|
|
||||||
|
|
||||||
-- Wait to be connect to the WiFi access point.
|
|
||||||
tmr.alarm(0, 100, 1, function()
|
|
||||||
if wifi.sta.status() ~= 5 then
|
|
||||||
print("Connecting to AP...")
|
|
||||||
else
|
|
||||||
tmr.stop(0)
|
|
||||||
-- Switch of the booting lamp
|
|
||||||
gpio.write(5, gpio.LOW)
|
|
||||||
print('IP: ',wifi.sta.getip())
|
|
||||||
|
|
||||||
--ptbtime1.ptb.de
|
|
||||||
sntp.sync('192.53.103.108',
|
|
||||||
function(sec,usec,server)
|
|
||||||
print('sync', sec, usec, server)
|
|
||||||
end,
|
|
||||||
function()
|
|
||||||
print('failed!')
|
|
||||||
end
|
|
||||||
)
|
|
||||||
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
||||||
tmr.alarm(1, 1000, 1 ,function()
|
|
||||||
sec, usec = rtctime.get()
|
|
||||||
print("Time : " , sec)
|
|
||||||
end)
|
|
||||||
|
|
BIN
os/0x00000.bin
BIN
os/0x00000.bin
Binary file not shown.
BIN
os/0x10000.bin
BIN
os/0x10000.bin
Binary file not shown.
26
os/Readme.md
Normal file
26
os/Readme.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Nodemcu-firmware
|
||||||
|
|
||||||
|
## Release
|
||||||
|
* 3.0-release_20201107
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
Firmware was compiled with the following modules:
|
||||||
|
* LUA_USE_MODULES_ADC
|
||||||
|
* LUA_USE_MODULES_FILE
|
||||||
|
* LUA_USE_MODULES_GPIO
|
||||||
|
* LUA_USE_MODULES_MQTT
|
||||||
|
* LUA_USE_MODULES_NET
|
||||||
|
* LUA_USE_MODULES_NODE
|
||||||
|
* LUA_USE_MODULES_OW
|
||||||
|
* LUA_USE_MODULES_PIPE
|
||||||
|
* LUA_USE_MODULES_RTCTIME
|
||||||
|
* LUA_USE_MODULES_SNTP
|
||||||
|
* LUA_USE_MODULES_TMR
|
||||||
|
* LUA_USE_MODULES_UART
|
||||||
|
* LUA_USE_MODULES_WIFI
|
||||||
|
* LUA_USE_MODULES_WS2812
|
||||||
|
|
||||||
|
## Numbers
|
||||||
|
Floating point calcuation was disabled:
|
||||||
|
* LUA_NUMBER_INTEGRAL
|
||||||
|
|
1
os/blank.bin
Normal file
1
os/blank.bin
Normal 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>
|
BIN
os/esp_init_data_default.bin
Normal file
BIN
os/esp_init_data_default.bin
Normal file
Binary file not shown.
4124
os/esptool.py
4124
os/esptool.py
File diff suppressed because it is too large
Load Diff
28
os/flash.sh
28
os/flash.sh
@ -1,20 +1,15 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [ $# -ne 1 ]; then
|
if [ $# -ne 1 ]; then
|
||||||
echo "One parameter required: the device of the serial interface"
|
DEVICE=$1
|
||||||
echo "$0 <device>"
|
# check the serial connection
|
||||||
echo "e.g.:"
|
if [ ! -c $DEVICE ]; then
|
||||||
echo "$0 ttyUSB0"
|
echo "$DEVICE does not exist"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DEVICE=$1
|
else
|
||||||
|
print "Autodetect serial port"
|
||||||
# check the serial connection
|
|
||||||
|
|
||||||
if [ ! -c /dev/$DEVICE ]; then
|
|
||||||
echo "/dev/$DEVICE does not exist"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f esptool.py ]; then
|
if [ ! -f esptool.py ]; then
|
||||||
@ -23,11 +18,16 @@ if [ ! -f esptool.py ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo ./esptool.py --port /dev/$DEVICE read_mac
|
CMD="python3 esptool.py "
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
|
CMD="python3 esptool.py --port $DEVICE "
|
||||||
|
fi
|
||||||
|
|
||||||
|
$CMD read_mac
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Error reading the MAC -> set the device into the bootloader!"
|
echo "Error reading the MAC -> set the device into the bootloader!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo "Flashing the new firmware"
|
||||||
sudo ./esptool.py --port /dev/$DEVICE write_flash 0x00000 0x00000.bin 0x10000 0x10000.bin
|
$CMD write_flash -fm dio 0x00000 0x00000.bin 0x10000 0x10000.bin 0x3fc000 esp_init_data_default.bin
|
||||||
|
Binary file not shown.
13
simulation/.classpath
Normal file
13
simulation/.classpath
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="module" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<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="lib" path="libs/org.eclipse.paho.client.mqttv3-1.2.5.jar"/>
|
||||||
|
<classpathentry kind="output" path="bin"/>
|
||||||
|
</classpath>
|
2
simulation/.gitignore
vendored
Normal file
2
simulation/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/bin/
|
||||||
|
/LuaSim*/
|
17
simulation/.project
Normal file
17
simulation/.project
Normal 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
12
simulation/Readme.md
Normal 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:libs/org.eclipse.paho.client.mqttv3-1.2.5.jar $(find src -name '*.java')`
|
||||||
|
|
||||||
|
Running:
|
||||||
|
`java -cp libs/luaj-jme-3.0.1.jar:libs/luaj-jse-3.0.1.jar:libs/org.eclipse.paho.client.mqttv3-1.2.5.jar:bin de.c3ma.ollo.WS2812Simulation ../init.lua ws28128ClockLayout.txt config.lua`
|
22
simulation/config.lua
Normal file
22
simulation/config.lua
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
green2=200
|
||||||
|
red=200
|
||||||
|
blue=200
|
||||||
|
|
||||||
|
color=string.char(0, 0, blue)
|
||||||
|
color1=string.char(red, 0, 0)
|
||||||
|
color2=string.char(tonumber(red*0.9), 0, 0)
|
||||||
|
color3=string.char(tonumber(red*0.8), 0, 0)
|
||||||
|
color4=string.char(tonumber(red*0.7), 0, 0)
|
||||||
|
|
||||||
|
colorBg=string.char(0,0,0) -- black is the default background color
|
||||||
|
sntpserverhostname="ptbtime1.ptb.de"
|
||||||
|
timezoneoffset=1
|
||||||
|
dim="on"
|
||||||
|
mqttServer="192.168.1.1"
|
||||||
|
mqttPrefix="test"
|
||||||
|
|
||||||
|
if (file.open("simulation.config.lua")) then
|
||||||
|
dofile("simulation.config.lua")
|
||||||
|
else
|
||||||
|
print("Default configuration, used")
|
||||||
|
end
|
3
simulation/libs/.gitignore
vendored
Normal file
3
simulation/libs/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
luaj-3.0.1.zip
|
||||||
|
luaj-3.0.1/
|
||||||
|
luaj-sources-3.0.1.jar
|
13
simulation/libs/Readme.md
Normal file
13
simulation/libs/Readme.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Dependencies
|
||||||
|
|
||||||
|
## Lua
|
||||||
|
The following file is expected here:
|
||||||
|
`luaj-3.0.1.zip`
|
||||||
|
|
||||||
|
It can be downloaded here:
|
||||||
|
https://sourceforge.net/projects/luaj/files/latest/download
|
||||||
|
|
||||||
|
## MQTT
|
||||||
|
|
||||||
|
It can be downloaded here:
|
||||||
|
https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.eclipse.paho%22%20AND%20a%3A%22org.eclipse.paho.client.mqttv3%22
|
BIN
simulation/libs/luaj-jme-3.0.1.jar
Normal file
BIN
simulation/libs/luaj-jme-3.0.1.jar
Normal file
Binary file not shown.
BIN
simulation/libs/luaj-jse-3.0.1.jar
Normal file
BIN
simulation/libs/luaj-jse-3.0.1.jar
Normal file
Binary file not shown.
BIN
simulation/libs/org.eclipse.paho.client.mqttv3-1.2.5.jar
Normal file
BIN
simulation/libs/org.eclipse.paho.client.mqttv3-1.2.5.jar
Normal file
Binary file not shown.
17
simulation/src/de/c3ma/ollo/LuaSimulation.java
Normal file
17
simulation/src/de/c3ma/ollo/LuaSimulation.java
Normal 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);
|
||||||
|
}
|
85
simulation/src/de/c3ma/ollo/LuaThreadTmr.java
Normal file
85
simulation/src/de/c3ma/ollo/LuaThreadTmr.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package de.c3ma.ollo;
|
||||||
|
|
||||||
|
import org.luaj.vm2.LuaError;
|
||||||
|
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 final int NO_TIMER = -2342;
|
||||||
|
|
||||||
|
private boolean running = true;
|
||||||
|
|
||||||
|
private boolean stopped = false;
|
||||||
|
|
||||||
|
private LuaValue code;
|
||||||
|
|
||||||
|
private int delay;
|
||||||
|
|
||||||
|
private final int timerNumber;
|
||||||
|
|
||||||
|
private LuaValue[] arguments;
|
||||||
|
|
||||||
|
public LuaThreadTmr(int timerNumber, LuaValue code, boolean endlessloop, int delay) {
|
||||||
|
this.code = code;
|
||||||
|
this.running = endlessloop;
|
||||||
|
this.delay = delay;
|
||||||
|
this.timerNumber = timerNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LuaThreadTmr(LuaValue code, LuaValue arg1, LuaValue arg2, LuaValue arg3) {
|
||||||
|
this.code = code;
|
||||||
|
this.running = false;
|
||||||
|
this.delay = 1;
|
||||||
|
this.timerNumber = NO_TIMER;
|
||||||
|
arguments = new LuaValue[3];
|
||||||
|
arguments[0] = arg1;
|
||||||
|
arguments[1] = arg2;
|
||||||
|
arguments[2] = arg3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
Thread.sleep(delay);
|
||||||
|
if (code != null) {
|
||||||
|
if (arguments == null) {
|
||||||
|
code.call();
|
||||||
|
} else {
|
||||||
|
switch (arguments.length) {
|
||||||
|
case 1:
|
||||||
|
code.call(arguments[0]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
code.call(arguments[0], arguments[1]);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
code.call(arguments[0], arguments[1], arguments[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(running);
|
||||||
|
} catch (LuaError le) {
|
||||||
|
System.err.println("[TMR] Timer" + timerNumber + " interrupted, due:" + le.getMessage());
|
||||||
|
} catch(InterruptedException ie) {
|
||||||
|
System.err.println("[TMR] Timer" + timerNumber + " interrupted");
|
||||||
|
}
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStopped() { return stopped; }
|
||||||
|
|
||||||
|
public void stopThread() {
|
||||||
|
running = false;
|
||||||
|
code = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
208
simulation/src/de/c3ma/ollo/WS2812Simulation.java
Normal file
208
simulation/src/de/c3ma/ollo/WS2812Simulation.java
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
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.ESP8266GPIO;
|
||||||
|
import de.c3ma.ollo.mockup.ESP8266Gpio;
|
||||||
|
import de.c3ma.ollo.mockup.ESP8266Mqtt;
|
||||||
|
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;
|
||||||
|
import de.c3ma.ollo.mockup.PrintFunction;
|
||||||
|
import de.c3ma.ollo.mockup.ui.WS2812Layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ESP8266Gpio gpio = new ESP8266Gpio();
|
||||||
|
private ESP8266Mqtt mqtt = new ESP8266Mqtt();
|
||||||
|
private ESP8266Adc adc = new ESP8266Adc();
|
||||||
|
private PrintFunction print = new PrintFunction();
|
||||||
|
private String scriptName;
|
||||||
|
|
||||||
|
public WS2812Simulation(File sourceFolder) {
|
||||||
|
globals.load(new ESP8266Uart());
|
||||||
|
globals.load(ws2812);
|
||||||
|
globals.load(espTmr);
|
||||||
|
globals.load(espFile);
|
||||||
|
globals.load(espNode);
|
||||||
|
globals.load(new ESP8266GPIO());
|
||||||
|
globals.load(adc);
|
||||||
|
globals.load(gpio);
|
||||||
|
globals.load(mqtt);
|
||||||
|
globals.load(new ESP8266Wifi());
|
||||||
|
globals.load(new ESP8266Net());
|
||||||
|
globals.load(new ESP8266Time());
|
||||||
|
globals.set("dofile", doFile);
|
||||||
|
globals.set("print", print);
|
||||||
|
adc.setADC(50);
|
||||||
|
|
||||||
|
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());
|
||||||
|
// create a diet one
|
||||||
|
if (f.isFile() &&
|
||||||
|
(f.getName().endsWith(".lua") &&
|
||||||
|
(!f.getName().equals("config.lua")))) {
|
||||||
|
String dietName = f.getName().replace(".lua", "_diet.lc");
|
||||||
|
Files.copy(f.toPath(), new File(tempDir.getAbsolutePath() + File.separator + dietName).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()) {
|
||||||
|
WS2812Layout ledLayout = ws2812.setLayout(file, this);
|
||||||
|
print.setPrinter(ledLayout);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
57
simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java
Normal file
57
simulation/src/de/c3ma/ollo/mockup/DoFileFunction.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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);
|
||||||
|
try {
|
||||||
|
if (f.exists()) {
|
||||||
|
LuaValue chunk = this.globals.loadfile(f.getAbsolutePath());
|
||||||
|
chunk.call();
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
return LuaValue.valueOf(false);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Cannot load " + f.getName());
|
||||||
|
e.printStackTrace();
|
||||||
|
return LuaValue.valueOf(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWorkingDirectory(File workingDir) {
|
||||||
|
this.workingDir = workingDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWorkingDirectory() {
|
||||||
|
if (workingDir != null) {
|
||||||
|
return workingDir.getAbsolutePath();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
47
simulation/src/de/c3ma/ollo/mockup/ESP8266Adc.java
Normal file
47
simulation/src/de/c3ma/ollo/mockup/ESP8266Adc.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
101
simulation/src/de/c3ma/ollo/mockup/ESP8266File.java
Normal file
101
simulation/src/de/c3ma/ollo/mockup/ESP8266File.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
simulation/src/de/c3ma/ollo/mockup/ESP8266GPIO.java
Normal file
51
simulation/src/de/c3ma/ollo/mockup/ESP8266GPIO.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
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 08.05.2019 - 21:33:04<br />
|
||||||
|
* creator: ollo<br />
|
||||||
|
* project: ESP8266GPIO Emulation<br />
|
||||||
|
* $Id: $<br />
|
||||||
|
* @author ollo<br />
|
||||||
|
*/
|
||||||
|
public class ESP8266GPIO extends TwoArgFunction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue modname, LuaValue env) {
|
||||||
|
env.checkglobals();
|
||||||
|
final LuaTable gpio = new LuaTable();
|
||||||
|
gpio.set("mode", new Mode());
|
||||||
|
gpio.set("read", new Read(this));
|
||||||
|
env.set("gpio", gpio);
|
||||||
|
env.get("package").get("loaded").set("gpio", gpio);
|
||||||
|
return gpio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Read extends VarArgFunction {
|
||||||
|
|
||||||
|
private ESP8266GPIO mGpio = null;
|
||||||
|
|
||||||
|
Read(ESP8266GPIO gpio) {
|
||||||
|
this.mGpio = gpio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
int pin = varargs.toint(0);
|
||||||
|
//FIXME add here something to simulate a pin pressing
|
||||||
|
return LuaValue.valueOf((int)1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Mode extends VarArgFunction {
|
||||||
|
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
System.out.println("[GPIO] mode GPIO" + varargs.toint(0) + "=" + varargs.toint(1));
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
simulation/src/de/c3ma/ollo/mockup/ESP8266Gpio.java
Normal file
90
simulation/src/de/c3ma/ollo/mockup/ESP8266Gpio.java
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package de.c3ma.ollo.mockup;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
import org.luaj.vm2.LuaString;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* created at 18.03.2021 - 21:09:03<br />
|
||||||
|
* creator: ollo<br />
|
||||||
|
* project: Esp8266 GPIO Emulation<br />
|
||||||
|
* $Id: $<br />
|
||||||
|
* @author ollo<br />
|
||||||
|
*/
|
||||||
|
public class ESP8266Gpio extends TwoArgFunction {
|
||||||
|
|
||||||
|
private static final String DIRECTION_INPUT = "input";
|
||||||
|
private HashMap<Integer, Integer> mInputs = new HashMap<Integer, Integer>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue modname, LuaValue env) {
|
||||||
|
env.checkglobals();
|
||||||
|
final LuaTable gpio = new LuaTable();
|
||||||
|
gpio.set("mode", new Mode(this));
|
||||||
|
gpio.set("read", new Read(this));
|
||||||
|
gpio.set("INPUT", DIRECTION_INPUT);
|
||||||
|
env.set("gpio", gpio);
|
||||||
|
env.get("package").get("loaded").set("gpio", gpio);
|
||||||
|
return gpio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Mode extends VarArgFunction {
|
||||||
|
|
||||||
|
private ESP8266Gpio gpio;
|
||||||
|
|
||||||
|
public Mode(ESP8266Gpio a) {
|
||||||
|
this.gpio = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
if (varargs.narg() == 2) {
|
||||||
|
final int pin = varargs.arg(1).toint();
|
||||||
|
final LuaString lsDirection = varargs.arg(2).checkstring();
|
||||||
|
String direction = lsDirection.toString();
|
||||||
|
if (direction.equals(DIRECTION_INPUT)) {
|
||||||
|
gpio.mInputs.put(pin, -1);
|
||||||
|
}
|
||||||
|
System.out.println("[GPIO] PIN" + pin +" as " + direction);
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Read extends OneArgFunction {
|
||||||
|
|
||||||
|
private ESP8266Gpio gpio;
|
||||||
|
|
||||||
|
public Read(ESP8266Gpio a) {
|
||||||
|
this.gpio = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue arg) {
|
||||||
|
int pin = arg.toint();
|
||||||
|
if (mInputs.containsKey(pin)) {
|
||||||
|
return LuaValue.valueOf(mInputs.get(pin));
|
||||||
|
} else {
|
||||||
|
System.out.println("[GPIO] pin" + pin + " not defined (gpio.mode missing)");
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPin(int pin, int newValue) {
|
||||||
|
if (mInputs.containsKey(pin)) {
|
||||||
|
mInputs.put(pin, newValue);
|
||||||
|
} else {
|
||||||
|
System.out.println("[GPIO] PIN" + pin +" not defined (missing gpio.mode)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
210
simulation/src/de/c3ma/ollo/mockup/ESP8266Mqtt.java
Normal file
210
simulation/src/de/c3ma/ollo/mockup/ESP8266Mqtt.java
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package de.c3ma.ollo.mockup;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.eclipse.paho.client.mqttv3.IMqttClient;
|
||||||
|
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttSecurityException;
|
||||||
|
import org.luaj.vm2.LuaString;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import de.c3ma.ollo.LuaThreadTmr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ollo
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ESP8266Mqtt extends TwoArgFunction implements IMqttMessageListener {
|
||||||
|
|
||||||
|
private static final String ON_PREFIX = "on_";
|
||||||
|
private static final String MESSAGE = "message";
|
||||||
|
private IMqttClient mMqttClient = null;
|
||||||
|
final LuaTable onMqtt = new LuaTable();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue modname, LuaValue env) {
|
||||||
|
env.checkglobals();
|
||||||
|
final LuaTable mqtt = new LuaTable();
|
||||||
|
mqtt.set("Client", new LuaMqttClient());
|
||||||
|
env.set("mqtt", mqtt);
|
||||||
|
env.get("package").get("loaded").set("tmr", mqtt);
|
||||||
|
System.out.println("[MQTT] Modlue loaded");
|
||||||
|
return mqtt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LuaMqttClient extends VarArgFunction {
|
||||||
|
public LuaValue invoke(Varargs varargs) {
|
||||||
|
final LuaTable dynMqtt = new LuaTable();
|
||||||
|
if (varargs.narg() == 2) {
|
||||||
|
final String client = varargs.arg(1).toString().toString();
|
||||||
|
final int timeout = varargs.arg(2).toint();
|
||||||
|
dynMqtt.set("on", new OnMqtt(client, timeout));
|
||||||
|
dynMqtt.set("publish", new PublishMqtt());
|
||||||
|
dynMqtt.set("subscribe", new SubscribeMqtt());
|
||||||
|
dynMqtt.set("connect", new ConnectMqtt());
|
||||||
|
System.out.println("[MQTT] New client: " + client + "(" + timeout+ "s)");
|
||||||
|
}
|
||||||
|
return dynMqtt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnMqtt extends VarArgFunction {
|
||||||
|
|
||||||
|
private String client=null;
|
||||||
|
private int timeout = 0;
|
||||||
|
|
||||||
|
private OnMqtt(String client, int timeout) {
|
||||||
|
this.client = client;
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LuaValue invoke(Varargs varargs) {
|
||||||
|
|
||||||
|
if (varargs.narg() == 3) {
|
||||||
|
final LuaTable table = varargs.arg(1).checktable();
|
||||||
|
final String callback = varargs.arg(2).toString().toString();
|
||||||
|
final LuaValue code = varargs.arg(3);
|
||||||
|
System.out.println("[MQTT] on_" + callback + " " + this.client);
|
||||||
|
onMqtt.set(ON_PREFIX + callback, code);
|
||||||
|
} else {
|
||||||
|
for(int i=0; i <= varargs.narg(); i++) {
|
||||||
|
System.err.println("[MQTT] On ["+(i) + "] (" + varargs.arg(i).typename() + ") " + varargs.arg(i).toString() );
|
||||||
|
}
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
return onMqtt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PublishMqtt extends VarArgFunction {
|
||||||
|
|
||||||
|
public LuaValue invoke(Varargs varargs) {
|
||||||
|
final LuaTable onMqtt = new LuaTable();
|
||||||
|
if (varargs.narg() == 5) {
|
||||||
|
final String topic = varargs.arg(2).toString().toString();
|
||||||
|
final String message = varargs.arg(3).toString().toString();
|
||||||
|
final String qos = varargs.arg(4).toString().toString();
|
||||||
|
final String retain = varargs.arg(4).toString().toString();
|
||||||
|
if ( !mMqttClient.isConnected()) {
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
MqttMessage msg = new MqttMessage(message.getBytes());
|
||||||
|
if (qos.equals("0")) {
|
||||||
|
msg.setQos(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.setRetained(!retain.contentEquals("0"));
|
||||||
|
try {
|
||||||
|
mMqttClient.publish(topic,msg);
|
||||||
|
System.out.println("[MQTT] publish " + topic);
|
||||||
|
} catch (MqttPersistenceException e) {
|
||||||
|
System.err.println("[MQTT] publish " + topic + " failed : " + e.getMessage());
|
||||||
|
} catch (MqttException e) {
|
||||||
|
System.err.println("[MQTT] publish " + topic + " failed : " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(int i=0; i <= varargs.narg(); i++) {
|
||||||
|
System.err.println("[MQTT] publish ["+(i) + "] (" + varargs.arg(i).typename() + ") " + varargs.arg(i).toString() );
|
||||||
|
}
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
return onMqtt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SubscribeMqtt extends VarArgFunction {
|
||||||
|
|
||||||
|
public LuaValue invoke(Varargs varargs) {
|
||||||
|
final LuaTable subMqtt = new LuaTable();
|
||||||
|
final int numberArg = varargs.narg();
|
||||||
|
if (numberArg == 3) {
|
||||||
|
final String topic = varargs.arg(2).toString().toString();
|
||||||
|
final int qos = varargs.arg(3).tonumber().toint();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mMqttClient != null) {
|
||||||
|
mMqttClient.subscribe(topic, ESP8266Mqtt.this);
|
||||||
|
System.out.println("[MQTT] subscribe " + topic + " (QoS " + qos + ")");
|
||||||
|
} else {
|
||||||
|
throw new Exception("Client not instantiated");
|
||||||
|
}
|
||||||
|
} catch (MqttSecurityException e) {
|
||||||
|
System.err.println("[MQTT] subscribe " + topic + " (QoS " + qos + ") failed: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (MqttException e) {
|
||||||
|
System.err.println("[MQTT] subscribe " + topic + " (QoS " + qos + ") failed: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("[MQTT] subscribe " + topic + " (QoS " + qos + ") failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(int i=0; i <= numberArg; i++) {
|
||||||
|
System.err.println("[MQTT] subscribe ["+(i) + "/" + numberArg + "] (" + varargs.arg(i).typename() + ") " + varargs.arg(i).toString() );
|
||||||
|
}
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
return subMqtt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConnectMqtt extends VarArgFunction {
|
||||||
|
|
||||||
|
public LuaValue invoke(Varargs varargs) {
|
||||||
|
final LuaTable onMqtt = new LuaTable();
|
||||||
|
if ((varargs.narg() == 6) && (mMqttClient == null)) {
|
||||||
|
final LuaTable table = varargs.arg(1).checktable();
|
||||||
|
final String targetIP = varargs.arg(2).toString().toString();
|
||||||
|
final int portnumber = varargs.arg(3).toint();
|
||||||
|
final boolean secureTLS = varargs.arg(4).toboolean();
|
||||||
|
final LuaValue codeOnConnected = varargs.arg(5);
|
||||||
|
final LuaValue codeOnFailed = varargs.arg(6);
|
||||||
|
String publisherId = "LuaSim" + UUID.randomUUID().toString();
|
||||||
|
try {
|
||||||
|
mMqttClient = new MqttClient("tcp://" + targetIP + ":" + portnumber,publisherId);
|
||||||
|
MqttConnectOptions options = new MqttConnectOptions();
|
||||||
|
options.setAutomaticReconnect(false);
|
||||||
|
options.setCleanSession(true);
|
||||||
|
options.setConnectionTimeout(10);
|
||||||
|
mMqttClient.connect(options);
|
||||||
|
System.out.println("[MQTT] connected to " + targetIP + ":" + portnumber);
|
||||||
|
codeOnConnected.call();
|
||||||
|
} catch (MqttException e) {
|
||||||
|
System.err.println("[MQTT] connect failed : " + e.getMessage());
|
||||||
|
codeOnFailed.call();
|
||||||
|
}
|
||||||
|
} else if (mMqttClient != null) {
|
||||||
|
System.err.println("[MQTT] client already exists : " + mMqttClient);
|
||||||
|
return LuaValue.NIL;
|
||||||
|
} else {
|
||||||
|
for(int i=0; i <= varargs.narg(); i++) {
|
||||||
|
System.err.println("[MQTT] connect ["+(i) + "] (" + varargs.arg(i).typename() + ") " + varargs.arg(i).toString() );
|
||||||
|
}
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
return onMqtt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
||||||
|
LuaValue messageCallback = onMqtt.get(ON_PREFIX + MESSAGE);
|
||||||
|
if (messageCallback != null) {
|
||||||
|
LuaThreadTmr exec = new LuaThreadTmr(messageCallback, LuaValue.NIL, LuaValue.valueOf(topic), LuaValue.valueOf(message.getPayload()));
|
||||||
|
exec.start();
|
||||||
|
System.out.println("[MQTT] message "+ topic + " : " + message + " received");
|
||||||
|
//FIXME call the LUA code
|
||||||
|
} else {
|
||||||
|
System.err.println("[MQTT] message "+ topic + " : " + message + " without callback");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
simulation/src/de/c3ma/ollo/mockup/ESP8266Net.java
Normal file
103
simulation/src/de/c3ma/ollo/mockup/ESP8266Net.java
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package de.c3ma.ollo.mockup;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
protected boolean mServerRunning = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue modname, LuaValue env) {
|
||||||
|
env.checkglobals();
|
||||||
|
LuaValue net = LuaValue.tableOf();
|
||||||
|
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 TwoArgFunction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue modname, LuaValue globalEnv) {
|
||||||
|
LuaValue cs = LuaValue.tableOf();
|
||||||
|
cs.set("listen", new ListenFunction());
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListenFunction extends ThreeArgFunction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue self, LuaValue port, final LuaValue function) {
|
||||||
|
if(!self.istable()) {
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int portnumber = port.checkint();
|
||||||
|
|
||||||
|
if ((portnumber > 0) && ((portnumber + PORTNUMBER_OFFSET) < 65000)) {
|
||||||
|
System.out.println("[Net] listening " + portnumber + "(simulating at " + (PORTNUMBER_OFFSET+ portnumber) + ")");
|
||||||
|
//FIXME open a new Thread to start the TCP connection
|
||||||
|
}
|
||||||
|
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/*FIXME first idea
|
||||||
|
private class TcpServer extends Thread {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
LuaFunction onListenFunction = function.checkfunction();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ServerSocket serverSocket = new ServerSocket(PORTNUMBER_OFFSET+portnumber);
|
||||||
|
serverSocket.setSoTimeout( 60000 ); // Timeout after one minute
|
||||||
|
|
||||||
|
while(mServerRunning) {
|
||||||
|
Socket client = serverSocket.accept();
|
||||||
|
System.out.println("[Net] connection from " + client.getInetAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
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 stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
92
simulation/src/de/c3ma/ollo/mockup/ESP8266Node.java
Normal file
92
simulation/src/de/c3ma/ollo/mockup/ESP8266Node.java
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
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());
|
||||||
|
node.set("heap", new HeapFunction());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeapFunction extends ZeroArgFunction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call() {
|
||||||
|
//System.out.println("[Node] Heap");
|
||||||
|
return LuaValue.valueOf(Runtime.getRuntime().freeMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void setWorkingDirectory(File workingDir) {
|
||||||
|
this.workingDir = workingDir;
|
||||||
|
}
|
||||||
|
}
|
102
simulation/src/de/c3ma/ollo/mockup/ESP8266Time.java
Normal file
102
simulation/src/de/c3ma/ollo/mockup/ESP8266Time.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
178
simulation/src/de/c3ma/ollo/mockup/ESP8266Tmr.java
Normal file
178
simulation/src/de/c3ma/ollo/mockup/ESP8266Tmr.java
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
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 org.luaj.vm2.lib.ZeroArgFunction;
|
||||||
|
|
||||||
|
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 = 10;
|
||||||
|
|
||||||
|
private static LuaThreadTmr[] allThreads = new LuaThreadTmr[MAXTHREADS];
|
||||||
|
private static LuaThreadTmr[] dynamicThreads = new LuaThreadTmr[MAXTHREADS];
|
||||||
|
private static int dynamicThreadCounter=0;
|
||||||
|
|
||||||
|
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());
|
||||||
|
tmr.set("create", new create());
|
||||||
|
tmr.set("wdclr", new watchDog());
|
||||||
|
tmr.set("ALARM_AUTO", "ALARM_AUTO");
|
||||||
|
tmr.set("ALARM_SINGLE", "ALARM_SINGLE");
|
||||||
|
env.set("tmr", tmr);
|
||||||
|
env.get("package").get("loaded").set("tmr", tmr);
|
||||||
|
|
||||||
|
/* initialize the Threads */
|
||||||
|
for (Thread t : allThreads) {
|
||||||
|
t = null;
|
||||||
|
}
|
||||||
|
for (Thread t : dynamicThreads) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class dynRegister extends VarArgFunction {
|
||||||
|
private final int dynIndex;
|
||||||
|
public dynRegister(int index) {
|
||||||
|
this.dynIndex = index;
|
||||||
|
}
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
if (varargs.narg() == 4) {
|
||||||
|
final String endlessloop = varargs.arg(3).toString().toString();
|
||||||
|
final int delay = varargs.arg(2).toint();
|
||||||
|
final LuaValue code = varargs.arg(4);
|
||||||
|
dynamicThreads[dynIndex] = new LuaThreadTmr(dynIndex, code, (endlessloop.contains("AUTO")), Math.max(delay / gTimingFactor, 1));
|
||||||
|
System.out.println("[TMR] DynTimer" + dynIndex + " registered");
|
||||||
|
}
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class dynStart extends ZeroArgFunction {
|
||||||
|
private final int dynIndex;
|
||||||
|
public dynStart(int index) {
|
||||||
|
this.dynIndex = index;
|
||||||
|
}
|
||||||
|
public LuaValue call() {
|
||||||
|
if (dynamicThreads[dynIndex] != null) {
|
||||||
|
dynamicThreads[dynIndex].start();
|
||||||
|
System.out.println("[TMR] DynTimer" + dynIndex + " started");
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
return LuaValue.valueOf(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class watchDog extends ZeroArgFunction {
|
||||||
|
|
||||||
|
public LuaValue call() {
|
||||||
|
//System.out.println("[TMR] Watchdog fed");
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class dynStop extends ZeroArgFunction {
|
||||||
|
private final int dynIndex;
|
||||||
|
public dynStop(int index) {
|
||||||
|
this.dynIndex = index;
|
||||||
|
}
|
||||||
|
public LuaValue call() {
|
||||||
|
boolean status = false;
|
||||||
|
if (dynamicThreads[dynIndex] != null) {
|
||||||
|
dynamicThreads[dynIndex].stopThread();
|
||||||
|
dynamicThreads[dynIndex] = null;
|
||||||
|
System.out.println("[TMR] DynTimer" + dynIndex + " stopped");
|
||||||
|
status = true;
|
||||||
|
}
|
||||||
|
return LuaValue.valueOf(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class create extends ZeroArgFunction {
|
||||||
|
public LuaValue call() {
|
||||||
|
if (dynamicThreadCounter >= MAXTHREADS) {
|
||||||
|
return LuaValue.error("[TMR] DynTimer" + dynamicThreadCounter + " exeeded maximum");
|
||||||
|
}
|
||||||
|
final LuaTable dynTimer = new LuaTable();
|
||||||
|
dynTimer.set("register", new dynRegister(dynamicThreadCounter));
|
||||||
|
dynTimer.set("start", new dynStart(dynamicThreadCounter));
|
||||||
|
dynTimer.set("unregister", new dynStop(dynamicThreadCounter));
|
||||||
|
dynamicThreadCounter++;
|
||||||
|
return dynTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopAllTimer() {
|
||||||
|
for (int i = 0; i < allThreads.length; i++) {
|
||||||
|
stopTmr(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
simulation/src/de/c3ma/ollo/mockup/ESP8266Uart.java
Normal file
38
simulation/src/de/c3ma/ollo/mockup/ESP8266Uart.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
simulation/src/de/c3ma/ollo/mockup/ESP8266Wifi.java
Normal file
81
simulation/src/de/c3ma/ollo/mockup/ESP8266Wifi.java
Normal 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
269
simulation/src/de/c3ma/ollo/mockup/ESP8266Ws2812.java
Normal file
269
simulation/src/de/c3ma/ollo/mockup/ESP8266Ws2812.java
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
package de.c3ma.ollo.mockup;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
import org.luaj.vm2.LuaString;
|
||||||
|
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 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());
|
||||||
|
ws2812.set("newBuffer", new newBuffer());
|
||||||
|
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 (ESP8266Ws2812.layout == null) {
|
||||||
|
System.err.println("[WS2812] Not initialized (" + length + "bytes to be updated)");
|
||||||
|
return LuaValue.valueOf(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
System.out.println("[WS2812] write no string given");
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WS2812Layout setLayout(File file, LuaSimulation nodemcuSimu) {
|
||||||
|
if (ESP8266Ws2812.layout == null) {
|
||||||
|
ESP8266Ws2812.layout = WS2812Layout.parse(file, nodemcuSimu);
|
||||||
|
}
|
||||||
|
return ESP8266Ws2812.layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class newBuffer extends VarArgFunction {
|
||||||
|
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
if (varargs.narg() == 2) {
|
||||||
|
final int leds = varargs.arg(1).toint();
|
||||||
|
final int bytesPerLeds = varargs.arg(2).toint();
|
||||||
|
final LuaTable rgbBuffer = new LuaTable();
|
||||||
|
ArrayList<Color> ledList = new ArrayList<Color>();
|
||||||
|
for(int i=0; i < leds; i++) {
|
||||||
|
ledList.add(new Color(0,0,0));
|
||||||
|
}
|
||||||
|
rgbBuffer.set("fill", new bufferFill(ledList));
|
||||||
|
rgbBuffer.set("set", new bufferWrite(ledList));
|
||||||
|
rgbBuffer.set("get", new bufferRead(ledList));
|
||||||
|
System.out.println("[WS2812] " + leds + "leds (" + bytesPerLeds + "bytes per led)");
|
||||||
|
return rgbBuffer;
|
||||||
|
} else {
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class bufferFill extends VarArgFunction {
|
||||||
|
|
||||||
|
private ArrayList<Color> ledList = null;
|
||||||
|
|
||||||
|
public bufferFill(ArrayList<Color> ledList) {
|
||||||
|
this.ledList = ledList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
if (varargs.narg() == 4) {
|
||||||
|
/* first argument is the object itself */
|
||||||
|
final int red = varargs.arg(2).toint();
|
||||||
|
final int green = varargs.arg(3).toint();
|
||||||
|
final int blue = varargs.arg(4).toint();
|
||||||
|
/* update local buffer */
|
||||||
|
for(int i=0; i < ledList.size(); i++) {
|
||||||
|
ledList.set(i, new Color(red, green, blue));
|
||||||
|
}
|
||||||
|
/* Update GUI */
|
||||||
|
if (ESP8266Ws2812.layout != null) {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ESP8266Ws2812.layout.fillLEDs(red, green, blue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//System.out.println("[WS2812] buffer fill with " + red + "," + green + "," + blue);
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else if (varargs.isstring(2)) {
|
||||||
|
final LuaString color = varargs.arg(2).checkstring();
|
||||||
|
|
||||||
|
final int length = color.rawlen();
|
||||||
|
if ((length == 3) && (ESP8266Ws2812.layout != null)) {
|
||||||
|
|
||||||
|
final byte[] array = color.m_bytes;
|
||||||
|
final int r = array[0]+(Byte.MIN_VALUE*-1);
|
||||||
|
final int b = array[1]+(Byte.MIN_VALUE*-1);
|
||||||
|
final int g = array[2]+(Byte.MIN_VALUE*-1);
|
||||||
|
/* update local buffer */
|
||||||
|
for(int i=0; i < ledList.size(); i++) {
|
||||||
|
ledList.set(i, new Color(r, g, b));
|
||||||
|
}
|
||||||
|
/* Update GUI */
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ESP8266Ws2812.layout.fillLEDs(r, g, b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//System.out.println("[WS2812] buffer fill with " + r + "," + g + "," + b);
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
System.err.println("[WS2812] buffer not initialized ("+varargs.narg() +"args) , length "+ length + ", raw:" + color.toString());
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println("[WS2812] fill with " + varargs.narg() + " arguments undefined.");
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class bufferWrite extends VarArgFunction {
|
||||||
|
private ArrayList<Color> ledList = null;
|
||||||
|
|
||||||
|
public bufferWrite(ArrayList<Color> ledList) {
|
||||||
|
this.ledList = ledList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
if (varargs.narg() == 3) {
|
||||||
|
final int index = varargs.arg(2).toint();
|
||||||
|
final LuaString color = varargs.arg(3).checkstring();
|
||||||
|
final int length = color.rawlen();
|
||||||
|
if (length == 3) {
|
||||||
|
final byte[] array = color.m_bytes;
|
||||||
|
final int r = array[0]+(Byte.MIN_VALUE*-1);
|
||||||
|
final int b = array[1]+(Byte.MIN_VALUE*-1);
|
||||||
|
final int g = array[2]+(Byte.MIN_VALUE*-1);
|
||||||
|
// update buffer
|
||||||
|
ledList.set(index - 1, new Color(r, g, b));
|
||||||
|
|
||||||
|
// update GUI
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ESP8266Ws2812.layout.updateLED(index - 1, r, g, b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
System.err.println("[WS2812] set with " + varargs.narg() + " arguments at index="+ index + " and "+ length + " charactes not matching");
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
} else if (varargs.narg() == 5) {
|
||||||
|
final int index = varargs.arg(2).toint();
|
||||||
|
final int green = varargs.arg(3).toint();
|
||||||
|
final int red = varargs.arg(4).toint();
|
||||||
|
final int blue = varargs.arg(5).toint();
|
||||||
|
// update buffer
|
||||||
|
ledList.set(index - 1, new Color(red, green, blue));
|
||||||
|
|
||||||
|
// update GUI
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ESP8266Ws2812.layout.updateLED(index - 1, red, green, blue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
for(int i=0; i <= varargs.narg(); i++) {
|
||||||
|
System.err.println("[WS2812] write ["+(i) + "] (" + varargs.arg(i).typename() + ") " + varargs.arg(i).toString() );
|
||||||
|
}
|
||||||
|
System.err.println("[WS2812] set with " + varargs.narg() + " arguments undefined.");
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class bufferRead extends VarArgFunction {
|
||||||
|
private ArrayList<Color> ledList = null;
|
||||||
|
|
||||||
|
public bufferRead(ArrayList<Color> ledList) {
|
||||||
|
this.ledList = ledList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Varargs invoke(Varargs varargs) {
|
||||||
|
final int offset = varargs.arg(2).toint();
|
||||||
|
|
||||||
|
if (ledList != null) {
|
||||||
|
// receiver from buffer
|
||||||
|
Color color = ledList.get(offset - 1);
|
||||||
|
final char[] array = new char[3];
|
||||||
|
array[0] = (char) (color.getRed() );
|
||||||
|
array[1] = (char) (color.getGreen() );
|
||||||
|
array[2] = (char) (color.getBlue() );
|
||||||
|
|
||||||
|
// System.err.println("[WS2812] reading " + offset + ":" + ((int)array[0]) +"," + ((int) array[1]) + "," + ((int) array[2]) + " from " + color);
|
||||||
|
return LuaString.valueOf(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.err.println("[WS2812] reading " + offset + " impossible");
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
simulation/src/de/c3ma/ollo/mockup/PrintFunction.java
Normal file
32
simulation/src/de/c3ma/ollo/mockup/PrintFunction.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ollo
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class PrintFunction extends OneArgFunction {
|
||||||
|
|
||||||
|
private Printable mPrinter;
|
||||||
|
|
||||||
|
public void setPrinter(Printable printer) {
|
||||||
|
this.mPrinter = printer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue message) {
|
||||||
|
String msg = message.checkjstring();
|
||||||
|
if (mPrinter != null) {
|
||||||
|
mPrinter.printConsole(msg);
|
||||||
|
return LuaValue.valueOf(true);
|
||||||
|
} else {
|
||||||
|
return LuaValue.valueOf(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
simulation/src/de/c3ma/ollo/mockup/Printable.java
Normal file
5
simulation/src/de/c3ma/ollo/mockup/Printable.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package de.c3ma.ollo.mockup;
|
||||||
|
|
||||||
|
public interface Printable {
|
||||||
|
public void printConsole(String line);
|
||||||
|
}
|
390
simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java
Normal file
390
simulation/src/de/c3ma/ollo/mockup/ui/WS2812Layout.java
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
package de.c3ma.ollo.mockup.ui;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
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.BoxLayout;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
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 javax.swing.text.DefaultCaret;
|
||||||
|
|
||||||
|
import de.c3ma.ollo.LuaSimulation;
|
||||||
|
import de.c3ma.ollo.mockup.Printable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 implements Printable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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 JTextArea mConsole = null;
|
||||||
|
|
||||||
|
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 bottomShell = new JPanel();
|
||||||
|
bottomShell.setLayout(new javax.swing.BoxLayout(bottomShell, BoxLayout.Y_AXIS));
|
||||||
|
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);
|
||||||
|
bottomShell.add(bottomPanel);
|
||||||
|
|
||||||
|
mConsole = new JTextArea("Test");
|
||||||
|
mConsole.setEditable(false);
|
||||||
|
mConsole.setMinimumSize(new Dimension(200, 220));
|
||||||
|
mConsole.setMaximumSize(new Dimension(800, 200));
|
||||||
|
mConsole.setDoubleBuffered(true);
|
||||||
|
JScrollPane scrollPane = new JScrollPane(mConsole);
|
||||||
|
//scrollPane.setSize(100, 200);
|
||||||
|
scrollPane.setMinimumSize(new Dimension(800, 200));
|
||||||
|
bottomShell.add(scrollPane);
|
||||||
|
|
||||||
|
contentPane.add(bottomShell, 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor() {
|
||||||
|
return this.getForeground();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Element getLED(int index) {
|
||||||
|
if (mElements != null) {
|
||||||
|
int i = (index / mColumn);
|
||||||
|
int j = (index % mColumn);
|
||||||
|
if (i % 2 == 1) {
|
||||||
|
j = (mColumn-1) - j;
|
||||||
|
}
|
||||||
|
if (i < 0 || j < 0) {
|
||||||
|
System.err.println("LED index" + index + " results in " + i + "x" + j + " coordinate");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((i < mElements.length) && (j < mElements[i].length) && (mElements[i][j] != null)) {
|
||||||
|
return mElements[i][j];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fillLEDs(int r, int g, int b) {
|
||||||
|
if (mElements != null) {
|
||||||
|
for(int i=0;(i < mElements.length); i++) {
|
||||||
|
for (int j=0; (j < mElements[i].length); j++) {
|
||||||
|
if (mElements[i][j] != null) {
|
||||||
|
Element curlbl = mElements[i][j];
|
||||||
|
curlbl.setColor(r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printConsole(String line) {
|
||||||
|
if (mConsole != null) {
|
||||||
|
mConsole.append(line);
|
||||||
|
mConsole.append("\r\n");
|
||||||
|
mConsole.getCaret().setDot( Integer.MAX_VALUE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
simulation/ws28128ClockLayout.txt
Normal file
17
simulation/ws28128ClockLayout.txt
Normal 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
|
||||||
|
____
|
51
telnet.lua
Normal file
51
telnet.lua
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
-- Telnet client
|
||||||
|
--[[ A telnet server T. Ellison, June 2019
|
||||||
|
|
||||||
|
This version of the telnet server demonstrates the use of the new stdin and stout
|
||||||
|
pipes, which is a C implementation of the Lua fifosock concept moved into the
|
||||||
|
Lua core. These two pipes are referenced in the Lua registry.
|
||||||
|
|
||||||
|
]]
|
||||||
|
--luacheck: no unused args
|
||||||
|
|
||||||
|
local telnetS = nil
|
||||||
|
|
||||||
|
local function telnet_session(socket)
|
||||||
|
local node = node
|
||||||
|
local stdout
|
||||||
|
|
||||||
|
local function output_CB(opipe) -- upval: socket
|
||||||
|
stdout = opipe
|
||||||
|
local rec = opipe:read(1400)
|
||||||
|
if rec and (#rec > 0) then socket:send(rec) end
|
||||||
|
return false -- don't repost as the on:sent will do this
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onsent_CB(skt) -- upval: stdout
|
||||||
|
local rec = stdout:read(1400)
|
||||||
|
if rec and #rec > 0 then skt:send(rec) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function disconnect_CB(skt) -- upval: socket, stdout
|
||||||
|
node.output()
|
||||||
|
socket, stdout = nil, nil -- set upvals to nl to allow GC
|
||||||
|
end
|
||||||
|
|
||||||
|
node.output(output_CB, 0)
|
||||||
|
socket:on("receive", function(_,rec) node.input(rec) end)
|
||||||
|
socket:on("sent", onsent_CB)
|
||||||
|
socket:on("disconnection", disconnect_CB)
|
||||||
|
print( ("Welcome to the Wordclock. (%d mem free, %s)"):format(node.heap(), wifi.sta.getip()))
|
||||||
|
print("- mydofile(\"commands\")")
|
||||||
|
print("- storeConfig()")
|
||||||
|
print("Visite https://github.com/nodemcu/nodemcu-firmware/wiki/nodemcu_api_en for further commands")
|
||||||
|
uart.write(0, "New client connected\r\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Telnet Server
|
||||||
|
function startTelnetServer()
|
||||||
|
telnetS=net.createServer(net.TCP, 180)
|
||||||
|
telnetS:listen(23, telnet_session)
|
||||||
|
print("Telnetserver started")
|
||||||
|
end
|
31
timecore.lua
Normal file → Executable file
31
timecore.lua
Normal file → Executable file
@ -1,3 +1,6 @@
|
|||||||
|
local M
|
||||||
|
do
|
||||||
|
|
||||||
--Summer winter time convertion
|
--Summer winter time convertion
|
||||||
--See: https://arduinodiy.wordpress.com/2015/10/13/the-arduino-and-daylight-saving-time/
|
--See: https://arduinodiy.wordpress.com/2015/10/13/the-arduino-and-daylight-saving-time/
|
||||||
--
|
--
|
||||||
@ -27,7 +30,7 @@
|
|||||||
-- @var dow Current day of week range (1 - 7) (1 is Monday, 7 is Sunday)
|
-- @var dow Current day of week range (1 - 7) (1 is Monday, 7 is Sunday)
|
||||||
-- @return <code>true</code> if we have currently summer time
|
-- @return <code>true</code> if we have currently summer time
|
||||||
-- @return <code>false</code> if we have winter time
|
-- @return <code>false</code> if we have winter time
|
||||||
function isSummerTime(time)
|
local function isSummerTime(time)
|
||||||
-- we are in 100% in the summer time
|
-- we are in 100% in the summer time
|
||||||
if (time.month > 3 and time.month < 10) then
|
if (time.month > 3 and time.month < 10) then
|
||||||
return true
|
return true
|
||||||
@ -56,13 +59,13 @@ end
|
|||||||
-- Source:
|
-- Source:
|
||||||
-- http://www.jbox.dk/sanos/source/lib/time.c.html
|
-- http://www.jbox.dk/sanos/source/lib/time.c.html
|
||||||
|
|
||||||
YEAR0=1900
|
local YEAR0=1900
|
||||||
|
|
||||||
EPOCH_YR=1970
|
local EPOCH_YR=1970
|
||||||
--SECS_DAY=(24L * 60L * 60L)
|
--SECS_DAY=(24L * 60L * 60L)
|
||||||
SECS_DAY=86400
|
local SECS_DAY=86400
|
||||||
|
|
||||||
ytab = {}
|
local ytab = {}
|
||||||
ytab[0] = {}
|
ytab[0] = {}
|
||||||
ytab[1] = {}
|
ytab[1] = {}
|
||||||
ytab[0][0] = 31
|
ytab[0][0] = 31
|
||||||
@ -103,10 +106,10 @@ local yearsize = function(year)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function getUTCtime(unixtimestmp)
|
local function getUTCtime(unixtimestmp)
|
||||||
local year = EPOCH_YR
|
local year = EPOCH_YR
|
||||||
local dayclock = math.floor(unixtimestmp % SECS_DAY)
|
local dayclock = math.floor(unixtimestmp % SECS_DAY)
|
||||||
dayno = math.floor(unixtimestmp / SECS_DAY)
|
local dayno = math.floor(unixtimestmp / SECS_DAY)
|
||||||
|
|
||||||
local sec = math.floor(dayclock % 60)
|
local sec = math.floor(dayclock % 60)
|
||||||
local min = math.floor( (dayclock % 3600) / 60)
|
local min = math.floor( (dayclock % 3600) / 60)
|
||||||
@ -119,22 +122,28 @@ function getUTCtime(unixtimestmp)
|
|||||||
year=year + 1
|
year=year + 1
|
||||||
end
|
end
|
||||||
--Day in whole year: local yday = dayno (Not needed)
|
--Day in whole year: local yday = dayno (Not needed)
|
||||||
mon = 0
|
local mon = 0
|
||||||
while (dayno >= ytab[leapyear(year) and 1 or 0][mon])
|
while (dayno >= ytab[leapyear(year) and 1 or 0][mon])
|
||||||
do
|
do
|
||||||
dayno = dayno - ytab[leapyear(year) and 1 or 0][mon];
|
dayno = dayno - ytab[leapyear(year) and 1 or 0][mon];
|
||||||
mon = mon + 1
|
mon = mon + 1
|
||||||
end
|
end
|
||||||
mday = dayno + 1
|
local mday = dayno + 1
|
||||||
|
|
||||||
return { year = year, month = (mon+1), day = mday, hour = hour, minute = min, second = sec, dow = dow }
|
return { year = year, month = (mon+1), day = mday, hour = hour, minute = min, second = sec, dow = dow }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function getTime(unixtimestmp, timezoneoffset)
|
local getTime = function(unixtimestmp, timezoneoffset)
|
||||||
time = getUTCtime(unixtimestmp + (3600 * timezoneoffset))
|
local time = getUTCtime(unixtimestmp + (3600 * timezoneoffset))
|
||||||
if ( isSummerTime(time) ) then
|
if ( isSummerTime(time) ) then
|
||||||
time = getUTCtime(unixtimestmp + (3600 * (timezoneoffset + 1)) )
|
time = getUTCtime(unixtimestmp + (3600 * (timezoneoffset + 1)) )
|
||||||
end
|
end
|
||||||
return time
|
return time
|
||||||
end
|
end
|
||||||
|
-- Pack everything into a module
|
||||||
|
M = {
|
||||||
|
getTime = getTime
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return M
|
||||||
|
13
tools/Readme.md
Normal file
13
tools/Readme.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# luatool.py
|
||||||
|
Version 0.8.0 upgraded supported with python 3.x
|
||||||
|
## Source:
|
||||||
|
https://github.com/4refr0nt/luatool/tree/master/luatool
|
||||||
|
# LuaSrcDiet
|
||||||
|
LuaSrcDiet reduces the size of Lua 5.1+ source files by aggressively removing all unnecessary whitespace and comments, optimizing constant tokens, and renaming local variables to shorter names.
|
||||||
|
* https://github.com/jirutka/luasrcdiet
|
||||||
|
|
||||||
|
## Source:
|
||||||
|
https://raw.githubusercontent.com/jirutka/luasrcdiet/master/bin/luasrcdiet
|
||||||
|
|
||||||
|
## Example:
|
||||||
|
bin/luasrcdiet ../webserver.lua -o ../webserver_diet.lua
|
653
tools/bin/luasrcdiet
Executable file
653
tools/bin/luasrcdiet
Executable file
@ -0,0 +1,653 @@
|
|||||||
|
#!/usr/bin/env lua
|
||||||
|
---------
|
||||||
|
-- LuaSrcDiet
|
||||||
|
--
|
||||||
|
-- Compresses Lua source code by removing unnecessary characters.
|
||||||
|
-- For Lua 5.1+ source code.
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * Remember to update version and date information below (MSG_TITLE).
|
||||||
|
-- * TODO: passing data tables around is a horrific mess.
|
||||||
|
-- * TODO: to implement pcall() to properly handle lexer etc. errors.
|
||||||
|
-- * TODO: need some automatic testing for a semblance of sanity.
|
||||||
|
-- * TODO: the plugin module is highly experimental and unstable.
|
||||||
|
----
|
||||||
|
local equiv = require "luasrcdiet.equiv"
|
||||||
|
local fs = require "luasrcdiet.fs"
|
||||||
|
local llex = require "luasrcdiet.llex"
|
||||||
|
local lparser = require "luasrcdiet.lparser"
|
||||||
|
local luasrcdiet = require "luasrcdiet.init"
|
||||||
|
local optlex = require "luasrcdiet.optlex"
|
||||||
|
local optparser = require "luasrcdiet.optparser"
|
||||||
|
|
||||||
|
local byte = string.byte
|
||||||
|
local concat = table.concat
|
||||||
|
local find = string.find
|
||||||
|
local fmt = string.format
|
||||||
|
local gmatch = string.gmatch
|
||||||
|
local match = string.match
|
||||||
|
local print = print
|
||||||
|
local rep = string.rep
|
||||||
|
local sub = string.sub
|
||||||
|
|
||||||
|
local plugin
|
||||||
|
|
||||||
|
local LUA_VERSION = match(_VERSION, " (5%.[123])$") or "5.1"
|
||||||
|
|
||||||
|
-- Is --opt-binequiv available for this Lua version?
|
||||||
|
local BIN_EQUIV_AVAIL = LUA_VERSION == "5.1" and not package.loaded.jit
|
||||||
|
|
||||||
|
|
||||||
|
---------------------- Messages and textual data ----------------------
|
||||||
|
|
||||||
|
local MSG_TITLE = fmt([[
|
||||||
|
LuaSrcDiet: Puts your Lua 5.1+ source code on a diet
|
||||||
|
Version %s <%s>
|
||||||
|
]], luasrcdiet._VERSION, luasrcdiet._HOMEPAGE)
|
||||||
|
|
||||||
|
local MSG_USAGE = [[
|
||||||
|
usage: luasrcdiet [options] [filenames]
|
||||||
|
|
||||||
|
example:
|
||||||
|
>luasrcdiet myscript.lua -o myscript_.lua
|
||||||
|
|
||||||
|
options:
|
||||||
|
-v, --version prints version information
|
||||||
|
-h, --help prints usage information
|
||||||
|
-o <file> specify file name to write output
|
||||||
|
-s <suffix> suffix for output files (default '_')
|
||||||
|
--keep <msg> keep block comment with <msg> inside
|
||||||
|
--plugin <module> run <module> in plugin/ directory
|
||||||
|
- stop handling arguments
|
||||||
|
|
||||||
|
(optimization levels)
|
||||||
|
--none all optimizations off (normalizes EOLs only)
|
||||||
|
--basic lexer-based optimizations only
|
||||||
|
--maximum maximize reduction of source
|
||||||
|
|
||||||
|
(informational)
|
||||||
|
--quiet process files quietly
|
||||||
|
--read-only read file and print token stats only
|
||||||
|
--dump-lexer dump raw tokens from lexer to stdout
|
||||||
|
--dump-parser dump variable tracking tables from parser
|
||||||
|
--details extra info (strings, numbers, locals)
|
||||||
|
|
||||||
|
features (to disable, insert 'no' prefix like --noopt-comments):
|
||||||
|
%s
|
||||||
|
default settings:
|
||||||
|
%s]]
|
||||||
|
|
||||||
|
-- Optimization options, for ease of switching on and off.
|
||||||
|
--
|
||||||
|
-- * Positive to enable optimization, negative (no) to disable.
|
||||||
|
-- * These options should follow --opt-* and --noopt-* style for now.
|
||||||
|
local OPTION = [[
|
||||||
|
--opt-comments,'remove comments and block comments'
|
||||||
|
--opt-whitespace,'remove whitespace excluding EOLs'
|
||||||
|
--opt-emptylines,'remove empty lines'
|
||||||
|
--opt-eols,'all above, plus remove unnecessary EOLs'
|
||||||
|
--opt-strings,'optimize strings and long strings'
|
||||||
|
--opt-numbers,'optimize numbers'
|
||||||
|
--opt-locals,'optimize local variable names'
|
||||||
|
--opt-entropy,'tries to reduce symbol entropy of locals'
|
||||||
|
--opt-srcequiv,'insist on source (lexer stream) equivalence'
|
||||||
|
--opt-binequiv,'insist on binary chunk equivalence (only for PUC Lua 5.1)'
|
||||||
|
--opt-experimental,'apply experimental optimizations'
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Preset configuration.
|
||||||
|
local DEFAULT_CONFIG = [[
|
||||||
|
--opt-comments --opt-whitespace --opt-emptylines
|
||||||
|
--opt-numbers --opt-locals
|
||||||
|
--opt-srcequiv --noopt-binequiv
|
||||||
|
]]
|
||||||
|
-- Override configurations: MUST explicitly enable/disable everything.
|
||||||
|
local BASIC_CONFIG = [[
|
||||||
|
--opt-comments --opt-whitespace --opt-emptylines
|
||||||
|
--noopt-eols --noopt-strings --noopt-numbers
|
||||||
|
--noopt-locals --noopt-entropy
|
||||||
|
--opt-srcequiv --noopt-binequiv
|
||||||
|
]]
|
||||||
|
local MAXIMUM_CONFIG = [[
|
||||||
|
--opt-comments --opt-whitespace --opt-emptylines
|
||||||
|
--opt-eols --opt-strings --opt-numbers
|
||||||
|
--opt-locals --opt-entropy
|
||||||
|
--opt-srcequiv
|
||||||
|
]] .. (BIN_EQUIV_AVAIL and ' --opt-binequiv' or ' --noopt-binequiv')
|
||||||
|
|
||||||
|
local NONE_CONFIG = [[
|
||||||
|
--noopt-comments --noopt-whitespace --noopt-emptylines
|
||||||
|
--noopt-eols --noopt-strings --noopt-numbers
|
||||||
|
--noopt-locals --noopt-entropy
|
||||||
|
--opt-srcequiv --noopt-binequiv
|
||||||
|
]]
|
||||||
|
|
||||||
|
local DEFAULT_SUFFIX = "_" -- default suffix for file renaming
|
||||||
|
local PLUGIN_SUFFIX = "luasrcdiet.plugin." -- relative location of plugins
|
||||||
|
|
||||||
|
|
||||||
|
------------- Startup and initialize option list handling -------------
|
||||||
|
|
||||||
|
--- Simple error message handler; change to error if traceback wanted.
|
||||||
|
--
|
||||||
|
-- @tparam string msg The message to print.
|
||||||
|
local function die(msg)
|
||||||
|
print("LuaSrcDiet (error): "..msg); os.exit(1)
|
||||||
|
end
|
||||||
|
--die = error--DEBUG
|
||||||
|
|
||||||
|
-- Prepare text for list of optimizations, prepare lookup table.
|
||||||
|
local MSG_OPTIONS = ""
|
||||||
|
do
|
||||||
|
local WIDTH = 24
|
||||||
|
local o = {}
|
||||||
|
for op, desc in gmatch(OPTION, "%s*([^,]+),'([^']+)'") do
|
||||||
|
local msg = " "..op
|
||||||
|
msg = msg..rep(" ", WIDTH - #msg)..desc.."\n"
|
||||||
|
MSG_OPTIONS = MSG_OPTIONS..msg
|
||||||
|
o[op] = true
|
||||||
|
o["--no"..sub(op, 3)] = true
|
||||||
|
end
|
||||||
|
OPTION = o -- replace OPTION with lookup table
|
||||||
|
end
|
||||||
|
|
||||||
|
MSG_USAGE = fmt(MSG_USAGE, MSG_OPTIONS, DEFAULT_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
--------- Global variable initialization, option set handling ---------
|
||||||
|
|
||||||
|
local suffix = DEFAULT_SUFFIX -- file suffix
|
||||||
|
local option = {} -- program options
|
||||||
|
local stat_c, stat_l -- statistics tables
|
||||||
|
|
||||||
|
--- Sets option lookup table based on a text list of options.
|
||||||
|
--
|
||||||
|
-- Note: additional forced settings for --opt-eols is done in optlex.lua.
|
||||||
|
--
|
||||||
|
-- @tparam string CONFIG
|
||||||
|
local function set_options(CONFIG)
|
||||||
|
for op in gmatch(CONFIG, "(%-%-%S+)") do
|
||||||
|
if sub(op, 3, 4) == "no" and -- handle negative options
|
||||||
|
OPTION["--"..sub(op, 5)] then
|
||||||
|
option[sub(op, 5)] = false
|
||||||
|
else
|
||||||
|
option[sub(op, 3)] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------- Support functions --------------------------
|
||||||
|
|
||||||
|
-- List of token types, parser-significant types are up to TTYPE_GRAMMAR
|
||||||
|
-- while the rest are not used by parsers; arranged for stats display.
|
||||||
|
local TTYPES = {
|
||||||
|
"TK_KEYWORD", "TK_NAME", "TK_NUMBER", -- grammar
|
||||||
|
"TK_STRING", "TK_LSTRING", "TK_OP",
|
||||||
|
"TK_EOS",
|
||||||
|
"TK_COMMENT", "TK_LCOMMENT", -- non-grammar
|
||||||
|
"TK_EOL", "TK_SPACE",
|
||||||
|
}
|
||||||
|
local TTYPE_GRAMMAR = 7
|
||||||
|
|
||||||
|
local EOLTYPES = { -- EOL names for token dump
|
||||||
|
["\n"] = "LF", ["\r"] = "CR",
|
||||||
|
["\n\r"] = "LFCR", ["\r\n"] = "CRLF",
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Reads source code from the file.
|
||||||
|
--
|
||||||
|
-- @tparam string fname Path of the file to read.
|
||||||
|
-- @treturn string Content of the file.
|
||||||
|
local function load_file(fname)
|
||||||
|
local data, err = fs.read_file(fname, "rb")
|
||||||
|
if not data then die(err) end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Saves source code to the file.
|
||||||
|
--
|
||||||
|
-- @tparam string fname Path of the destination file.
|
||||||
|
-- @tparam string dat The data to write into the file.
|
||||||
|
local function save_file(fname, dat)
|
||||||
|
local ok, err = fs.write_file(fname, dat, "wb")
|
||||||
|
if not ok then die(err) end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
------------------ Functions to deal with statistics ------------------
|
||||||
|
|
||||||
|
--- Initializes the statistics table.
|
||||||
|
local function stat_init()
|
||||||
|
stat_c, stat_l = {}, {}
|
||||||
|
for i = 1, #TTYPES do
|
||||||
|
local ttype = TTYPES[i]
|
||||||
|
stat_c[ttype], stat_l[ttype] = 0, 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Adds a token to the statistics table.
|
||||||
|
--
|
||||||
|
-- @tparam string tok The token.
|
||||||
|
-- @param seminfo
|
||||||
|
local function stat_add(tok, seminfo)
|
||||||
|
stat_c[tok] = stat_c[tok] + 1
|
||||||
|
stat_l[tok] = stat_l[tok] + #seminfo
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Computes totals for the statistics table, returns average table.
|
||||||
|
--
|
||||||
|
-- @treturn table
|
||||||
|
local function stat_calc()
|
||||||
|
local function avg(c, l) -- safe average function
|
||||||
|
if c == 0 then return 0 end
|
||||||
|
return l / c
|
||||||
|
end
|
||||||
|
local stat_a = {}
|
||||||
|
local c, l = 0, 0
|
||||||
|
for i = 1, TTYPE_GRAMMAR do -- total grammar tokens
|
||||||
|
local ttype = TTYPES[i]
|
||||||
|
c = c + stat_c[ttype]; l = l + stat_l[ttype]
|
||||||
|
end
|
||||||
|
stat_c.TOTAL_TOK, stat_l.TOTAL_TOK = c, l
|
||||||
|
stat_a.TOTAL_TOK = avg(c, l)
|
||||||
|
c, l = 0, 0
|
||||||
|
for i = 1, #TTYPES do -- total all tokens
|
||||||
|
local ttype = TTYPES[i]
|
||||||
|
c = c + stat_c[ttype]; l = l + stat_l[ttype]
|
||||||
|
stat_a[ttype] = avg(stat_c[ttype], stat_l[ttype])
|
||||||
|
end
|
||||||
|
stat_c.TOTAL_ALL, stat_l.TOTAL_ALL = c, l
|
||||||
|
stat_a.TOTAL_ALL = avg(c, l)
|
||||||
|
return stat_a
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------- Main tasks -----------------------------
|
||||||
|
|
||||||
|
--- A simple token dumper, minimal translation of seminfo data.
|
||||||
|
--
|
||||||
|
-- @tparam string srcfl Path of the source file.
|
||||||
|
local function dump_tokens(srcfl)
|
||||||
|
-- Load file and process source input into tokens.
|
||||||
|
local z = load_file(srcfl)
|
||||||
|
local toklist, seminfolist = llex.lex(z)
|
||||||
|
|
||||||
|
-- Display output.
|
||||||
|
for i = 1, #toklist do
|
||||||
|
local tok, seminfo = toklist[i], seminfolist[i]
|
||||||
|
if tok == "TK_OP" and byte(seminfo) < 32 then
|
||||||
|
seminfo = "("..byte(seminfo)..")"
|
||||||
|
elseif tok == "TK_EOL" then
|
||||||
|
seminfo = EOLTYPES[seminfo]
|
||||||
|
else
|
||||||
|
seminfo = "'"..seminfo.."'"
|
||||||
|
end
|
||||||
|
print(tok.." "..seminfo)
|
||||||
|
end--for
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Dumps globalinfo and localinfo tables.
|
||||||
|
--
|
||||||
|
-- @tparam string srcfl Path of the source file.
|
||||||
|
local function dump_parser(srcfl)
|
||||||
|
-- Load file and process source input into tokens,
|
||||||
|
local z = load_file(srcfl)
|
||||||
|
local toklist, seminfolist, toklnlist = llex.lex(z)
|
||||||
|
|
||||||
|
-- Do parser optimization here.
|
||||||
|
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
|
||||||
|
local globalinfo, localinfo = xinfo.globalinfo, xinfo.localinfo
|
||||||
|
|
||||||
|
-- Display output.
|
||||||
|
local hl = rep("-", 72)
|
||||||
|
print("*** Local/Global Variable Tracker Tables ***")
|
||||||
|
print(hl.."\n GLOBALS\n"..hl)
|
||||||
|
-- global tables have a list of xref numbers only
|
||||||
|
for i = 1, #globalinfo do
|
||||||
|
local obj = globalinfo[i]
|
||||||
|
local msg = "("..i..") '"..obj.name.."' -> "
|
||||||
|
local xref = obj.xref
|
||||||
|
for j = 1, #xref do msg = msg..xref[j].." " end
|
||||||
|
print(msg)
|
||||||
|
end
|
||||||
|
-- Local tables have xref numbers and a few other special
|
||||||
|
-- numbers that are specially named: decl (declaration xref),
|
||||||
|
-- act (activation xref), rem (removal xref).
|
||||||
|
print(hl.."\n LOCALS (decl=declared act=activated rem=removed)\n"..hl)
|
||||||
|
for i = 1, #localinfo do
|
||||||
|
local obj = localinfo[i]
|
||||||
|
local msg = "("..i..") '"..obj.name.."' decl:"..obj.decl..
|
||||||
|
" act:"..obj.act.." rem:"..obj.rem
|
||||||
|
if obj.is_special then
|
||||||
|
msg = msg.." is_special"
|
||||||
|
end
|
||||||
|
msg = msg.." -> "
|
||||||
|
local xref = obj.xref
|
||||||
|
for j = 1, #xref do msg = msg..xref[j].." " end
|
||||||
|
print(msg)
|
||||||
|
end
|
||||||
|
print(hl.."\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Reads source file(s) and reports some statistics.
|
||||||
|
--
|
||||||
|
-- @tparam string srcfl Path of the source file.
|
||||||
|
local function read_only(srcfl)
|
||||||
|
-- Load file and process source input into tokens.
|
||||||
|
local z = load_file(srcfl)
|
||||||
|
local toklist, seminfolist = llex.lex(z)
|
||||||
|
print(MSG_TITLE)
|
||||||
|
print("Statistics for: "..srcfl.."\n")
|
||||||
|
|
||||||
|
-- Collect statistics.
|
||||||
|
stat_init()
|
||||||
|
for i = 1, #toklist do
|
||||||
|
local tok, seminfo = toklist[i], seminfolist[i]
|
||||||
|
stat_add(tok, seminfo)
|
||||||
|
end--for
|
||||||
|
local stat_a = stat_calc()
|
||||||
|
|
||||||
|
-- Display output.
|
||||||
|
local function figures(tt)
|
||||||
|
return stat_c[tt], stat_l[tt], stat_a[tt]
|
||||||
|
end
|
||||||
|
local tabf1, tabf2 = "%-16s%8s%8s%10s", "%-16s%8d%8d%10.2f"
|
||||||
|
local hl = rep("-", 42)
|
||||||
|
print(fmt(tabf1, "Lexical", "Input", "Input", "Input"))
|
||||||
|
print(fmt(tabf1, "Elements", "Count", "Bytes", "Average"))
|
||||||
|
print(hl)
|
||||||
|
for i = 1, #TTYPES do
|
||||||
|
local ttype = TTYPES[i]
|
||||||
|
print(fmt(tabf2, ttype, figures(ttype)))
|
||||||
|
if ttype == "TK_EOS" then print(hl) end
|
||||||
|
end
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
|
||||||
|
print(hl.."\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Processes source file(s), writes output and reports some statistics.
|
||||||
|
--
|
||||||
|
-- @tparam string srcfl Path of the source file.
|
||||||
|
-- @tparam string destfl Path of the destination file where to write optimized source.
|
||||||
|
local function process_file(srcfl, destfl)
|
||||||
|
-- handle quiet option
|
||||||
|
local function print(...) --luacheck: ignore 431
|
||||||
|
if option.QUIET then return end
|
||||||
|
_G.print(...)
|
||||||
|
end
|
||||||
|
if plugin and plugin.init then -- plugin init
|
||||||
|
option.EXIT = false
|
||||||
|
plugin.init(option, srcfl, destfl)
|
||||||
|
if option.EXIT then return end
|
||||||
|
end
|
||||||
|
print(MSG_TITLE) -- title message
|
||||||
|
|
||||||
|
-- Load file and process source input into tokens.
|
||||||
|
local z = load_file(srcfl)
|
||||||
|
if plugin and plugin.post_load then -- plugin post-load
|
||||||
|
z = plugin.post_load(z) or z
|
||||||
|
if option.EXIT then return end
|
||||||
|
end
|
||||||
|
local toklist, seminfolist, toklnlist = llex.lex(z)
|
||||||
|
if plugin and plugin.post_lex then -- plugin post-lex
|
||||||
|
plugin.post_lex(toklist, seminfolist, toklnlist)
|
||||||
|
if option.EXIT then return end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Collect 'before' statistics.
|
||||||
|
stat_init()
|
||||||
|
for i = 1, #toklist do
|
||||||
|
local tok, seminfo = toklist[i], seminfolist[i]
|
||||||
|
stat_add(tok, seminfo)
|
||||||
|
end--for
|
||||||
|
local stat1_a = stat_calc()
|
||||||
|
local stat1_c, stat1_l = stat_c, stat_l
|
||||||
|
|
||||||
|
-- Do parser optimization here.
|
||||||
|
optparser.print = print -- hack
|
||||||
|
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
|
||||||
|
if plugin and plugin.post_parse then -- plugin post-parse
|
||||||
|
plugin.post_parse(xinfo.globalinfo, xinfo.localinfo)
|
||||||
|
if option.EXIT then return end
|
||||||
|
end
|
||||||
|
optparser.optimize(option, toklist, seminfolist, xinfo)
|
||||||
|
if plugin and plugin.post_optparse then -- plugin post-optparse
|
||||||
|
plugin.post_optparse()
|
||||||
|
if option.EXIT then return end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do lexer optimization here, save output file.
|
||||||
|
local warn = optlex.warn -- use this as a general warning lookup
|
||||||
|
optlex.print = print -- hack
|
||||||
|
toklist, seminfolist, toklnlist
|
||||||
|
= optlex.optimize(option, toklist, seminfolist, toklnlist)
|
||||||
|
if plugin and plugin.post_optlex then -- plugin post-optlex
|
||||||
|
plugin.post_optlex(toklist, seminfolist, toklnlist)
|
||||||
|
if option.EXIT then return end
|
||||||
|
end
|
||||||
|
local dat = concat(seminfolist)
|
||||||
|
-- Depending on options selected, embedded EOLs in long strings and
|
||||||
|
-- long comments may not have been translated to \n, tack a warning.
|
||||||
|
if find(dat, "\r\n", 1, 1) or
|
||||||
|
find(dat, "\n\r", 1, 1) then
|
||||||
|
warn.MIXEDEOL = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test source and binary chunk equivalence.
|
||||||
|
equiv.init(option, llex, warn)
|
||||||
|
equiv.source(z, dat)
|
||||||
|
if BIN_EQUIV_AVAIL then
|
||||||
|
equiv.binary(z, dat)
|
||||||
|
end
|
||||||
|
local smsg = "before and after lexer streams are NOT equivalent!"
|
||||||
|
local bmsg = "before and after binary chunks are NOT equivalent!"
|
||||||
|
-- for reporting, die if option was selected, else just warn
|
||||||
|
if warn.SRC_EQUIV then
|
||||||
|
if option["opt-srcequiv"] then die(smsg) end
|
||||||
|
else
|
||||||
|
print("*** SRCEQUIV: token streams are sort of equivalent")
|
||||||
|
if option["opt-locals"] then
|
||||||
|
print("(but no identifier comparisons since --opt-locals enabled)")
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
if warn.BIN_EQUIV then
|
||||||
|
if option["opt-binequiv"] then die(bmsg) end
|
||||||
|
elseif BIN_EQUIV_AVAIL then
|
||||||
|
print("*** BINEQUIV: binary chunks are sort of equivalent")
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save optimized source stream to output file.
|
||||||
|
save_file(destfl, dat)
|
||||||
|
|
||||||
|
-- Collect 'after' statistics.
|
||||||
|
stat_init()
|
||||||
|
for i = 1, #toklist do
|
||||||
|
local tok, seminfo = toklist[i], seminfolist[i]
|
||||||
|
stat_add(tok, seminfo)
|
||||||
|
end--for
|
||||||
|
local stat_a = stat_calc()
|
||||||
|
|
||||||
|
-- Display output.
|
||||||
|
print("Statistics for: "..srcfl.." -> "..destfl.."\n")
|
||||||
|
local function figures(tt)
|
||||||
|
return stat1_c[tt], stat1_l[tt], stat1_a[tt],
|
||||||
|
stat_c[tt], stat_l[tt], stat_a[tt]
|
||||||
|
end
|
||||||
|
local tabf1, tabf2 = "%-16s%8s%8s%10s%8s%8s%10s",
|
||||||
|
"%-16s%8d%8d%10.2f%8d%8d%10.2f"
|
||||||
|
local hl = rep("-", 68)
|
||||||
|
print("*** lexer-based optimizations summary ***\n"..hl)
|
||||||
|
print(fmt(tabf1, "Lexical",
|
||||||
|
"Input", "Input", "Input",
|
||||||
|
"Output", "Output", "Output"))
|
||||||
|
print(fmt(tabf1, "Elements",
|
||||||
|
"Count", "Bytes", "Average",
|
||||||
|
"Count", "Bytes", "Average"))
|
||||||
|
print(hl)
|
||||||
|
for i = 1, #TTYPES do
|
||||||
|
local ttype = TTYPES[i]
|
||||||
|
print(fmt(tabf2, ttype, figures(ttype)))
|
||||||
|
if ttype == "TK_EOS" then print(hl) end
|
||||||
|
end
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
|
||||||
|
print(hl)
|
||||||
|
|
||||||
|
-- Report warning flags from optimizing process.
|
||||||
|
if warn.LSTRING then
|
||||||
|
print("* WARNING: "..warn.LSTRING)
|
||||||
|
elseif warn.MIXEDEOL then
|
||||||
|
print("* WARNING: ".."output still contains some CRLF or LFCR line endings")
|
||||||
|
elseif warn.SRC_EQUIV then
|
||||||
|
print("* WARNING: "..smsg)
|
||||||
|
elseif warn.BIN_EQUIV then
|
||||||
|
print("* WARNING: "..bmsg)
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---------------------------- Main functions ---------------------------
|
||||||
|
|
||||||
|
local arg = {...} -- program arguments
|
||||||
|
set_options(DEFAULT_CONFIG) -- set to default options at beginning
|
||||||
|
|
||||||
|
--- Does per-file handling, ship off to tasks.
|
||||||
|
--
|
||||||
|
-- @tparam {string,...} fspec List of source files.
|
||||||
|
local function do_files(fspec)
|
||||||
|
for i = 1, #fspec do
|
||||||
|
local srcfl = fspec[i]
|
||||||
|
local destfl
|
||||||
|
|
||||||
|
-- Find and replace extension for filenames.
|
||||||
|
local extb, exte = find(srcfl, "%.[^%.%\\%/]*$")
|
||||||
|
local basename, extension = srcfl, ""
|
||||||
|
if extb and extb > 1 then
|
||||||
|
basename = sub(srcfl, 1, extb - 1)
|
||||||
|
extension = sub(srcfl, extb, exte)
|
||||||
|
end
|
||||||
|
destfl = basename..suffix..extension
|
||||||
|
if #fspec == 1 and option.OUTPUT_FILE then
|
||||||
|
destfl = option.OUTPUT_FILE
|
||||||
|
end
|
||||||
|
if srcfl == destfl then
|
||||||
|
die("output filename identical to input filename")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Perform requested operations.
|
||||||
|
if option.DUMP_LEXER then
|
||||||
|
dump_tokens(srcfl)
|
||||||
|
elseif option.DUMP_PARSER then
|
||||||
|
dump_parser(srcfl)
|
||||||
|
elseif option.READ_ONLY then
|
||||||
|
read_only(srcfl)
|
||||||
|
else
|
||||||
|
process_file(srcfl, destfl)
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The main function.
|
||||||
|
local function main()
|
||||||
|
local fspec = {}
|
||||||
|
local argn, i = #arg, 1
|
||||||
|
if argn == 0 then
|
||||||
|
option.HELP = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle arguments.
|
||||||
|
while i <= argn do
|
||||||
|
local o, p = arg[i], arg[i + 1]
|
||||||
|
local dash = match(o, "^%-%-?")
|
||||||
|
if dash == "-" then -- single-dash options
|
||||||
|
if o == "-h" then
|
||||||
|
option.HELP = true; break
|
||||||
|
elseif o == "-v" then
|
||||||
|
option.VERSION = true; break
|
||||||
|
elseif o == "-s" then
|
||||||
|
if not p then die("-s option needs suffix specification") end
|
||||||
|
suffix = p
|
||||||
|
i = i + 1
|
||||||
|
elseif o == "-o" then
|
||||||
|
if not p then die("-o option needs a file name") end
|
||||||
|
option.OUTPUT_FILE = p
|
||||||
|
i = i + 1
|
||||||
|
elseif o == "-" then
|
||||||
|
break -- ignore rest of args
|
||||||
|
else
|
||||||
|
die("unrecognized option "..o)
|
||||||
|
end
|
||||||
|
elseif dash == "--" then -- double-dash options
|
||||||
|
if o == "--help" then
|
||||||
|
option.HELP = true; break
|
||||||
|
elseif o == "--version" then
|
||||||
|
option.VERSION = true; break
|
||||||
|
elseif o == "--keep" then
|
||||||
|
if not p then die("--keep option needs a string to match for") end
|
||||||
|
option.KEEP = p
|
||||||
|
i = i + 1
|
||||||
|
elseif o == "--plugin" then
|
||||||
|
if not p then die("--plugin option needs a module name") end
|
||||||
|
if option.PLUGIN then die("only one plugin can be specified") end
|
||||||
|
option.PLUGIN = p
|
||||||
|
plugin = require(PLUGIN_SUFFIX..p)
|
||||||
|
i = i + 1
|
||||||
|
elseif o == "--quiet" then
|
||||||
|
option.QUIET = true
|
||||||
|
elseif o == "--read-only" then
|
||||||
|
option.READ_ONLY = true
|
||||||
|
elseif o == "--basic" then
|
||||||
|
set_options(BASIC_CONFIG)
|
||||||
|
elseif o == "--maximum" then
|
||||||
|
set_options(MAXIMUM_CONFIG)
|
||||||
|
elseif o == "--none" then
|
||||||
|
set_options(NONE_CONFIG)
|
||||||
|
elseif o == "--dump-lexer" then
|
||||||
|
option.DUMP_LEXER = true
|
||||||
|
elseif o == "--dump-parser" then
|
||||||
|
option.DUMP_PARSER = true
|
||||||
|
elseif o == "--details" then
|
||||||
|
option.DETAILS = true
|
||||||
|
elseif OPTION[o] then -- lookup optimization options
|
||||||
|
set_options(o)
|
||||||
|
else
|
||||||
|
die("unrecognized option "..o)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fspec[#fspec + 1] = o -- potential filename
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end--while
|
||||||
|
if option.HELP then
|
||||||
|
print(MSG_TITLE..MSG_USAGE); return true
|
||||||
|
elseif option.VERSION then
|
||||||
|
print(MSG_TITLE); return true
|
||||||
|
end
|
||||||
|
if option["opt-binequiv"] and not BIN_EQUIV_AVAIL then
|
||||||
|
die("--opt-binequiv is available only for PUC Lua 5.1!")
|
||||||
|
end
|
||||||
|
if #fspec > 0 then
|
||||||
|
if #fspec > 1 and option.OUTPUT_FILE then
|
||||||
|
die("with -o, only one source file can be specified")
|
||||||
|
end
|
||||||
|
do_files(fspec)
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
die("nothing to do!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- entry point -> main() -> do_files()
|
||||||
|
if not main() then
|
||||||
|
die("Please run with option -h or --help for usage information")
|
||||||
|
end
|
106
tools/initialDietFlash.sh
Executable file
106
tools/initialDietFlash.sh
Executable file
@ -0,0 +1,106 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TOOLDIR=tools/
|
||||||
|
LUATOOL=${TOOLDIR}luatool.py
|
||||||
|
|
||||||
|
#DIET=bin/luasrcdiet --maximum
|
||||||
|
DIET=bin/luasrcdiet
|
||||||
|
|
||||||
|
DEVICE=$1
|
||||||
|
BAUD=115200
|
||||||
|
|
||||||
|
LUASCRIPT_STOP=${TOOLDIR}/stopController.lua
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
if [ ! -c $DEVICE ]; then
|
||||||
|
echo "Serial target: $DEVICE does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "e.g. usage $0 <device> [<files to upoad>]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Convert files, if necessary
|
||||||
|
if [ "$FILES" != "config.lua" ]; then
|
||||||
|
echo "Generate DIET version of the files"
|
||||||
|
OUTFILES=""
|
||||||
|
ROOTDIR=$PWD
|
||||||
|
cd $TOOLDIR
|
||||||
|
for f in $FILES; do
|
||||||
|
if [[ "$f" == *.lua ]] && [[ "$f" != init.lua ]]; then
|
||||||
|
echo "Compress $f ..."
|
||||||
|
out=$(echo "$f" | sed 's/.lua/_diet.lua/g')
|
||||||
|
$DIET ../$f -o ../diet/$out
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Failed to generate file"
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
OUTFILES="$OUTFILES diet/$out"
|
||||||
|
else
|
||||||
|
OUTFILES="$OUTFILES $f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
FILES=$OUTFILES
|
||||||
|
cd $ROOTDIR
|
||||||
|
fi
|
||||||
|
echo "Reboot ESP and stop init timer"
|
||||||
|
if [ ! -f $LUASCRIPT_STOP ]; then
|
||||||
|
echo "Cannot find $LUASCRIPT_STOP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
python3 $LUATOOL -p $DEVICE -f $LUASCRIPT_STOP -b $BAUD --volatile --delay 2
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Could not reboot"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
|
# Format filesystem first
|
||||||
|
echo "Format the complete ESP"
|
||||||
|
python3 $LUATOOL -p $DEVICE -w -b $BAUD
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "STOOOOP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
espFile=$(echo "$f" | sed 's;diet/;;g')
|
||||||
|
echo "------------- $espFile ------------"
|
||||||
|
python3 $LUATOOL -p $DEVICE -f $f -b $BAUD -t $espFile
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "STOOOOP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
|
echo "Reboot the ESP"
|
||||||
|
echo "node.restart()" >> $DEVICE
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
465
tools/luasrcdiet/equiv.lua
Normal file
465
tools/luasrcdiet/equiv.lua
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
---------
|
||||||
|
-- Source and binary equivalency comparisons
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * Intended as an extra safety check for mission-critical code,
|
||||||
|
-- should give affirmative results if everything works.
|
||||||
|
-- * Heavy on load() and string.dump(), which may be slowish,
|
||||||
|
-- and may cause problems for cross-compiled applications.
|
||||||
|
-- * Optional detailed information dump is mainly for debugging,
|
||||||
|
-- reason being, if the two are not equivalent when they should be,
|
||||||
|
-- then some form of optimization has failed.
|
||||||
|
-- * source: IMPORTANT: TK_NAME not compared if opt-locals enabled.
|
||||||
|
-- * binary: IMPORTANT: Some shortcuts are taken with int and size_t
|
||||||
|
-- value reading -- if the functions break, then the binary chunk
|
||||||
|
-- is very large indeed.
|
||||||
|
-- * binary: There is a lack of diagnostic information when a compare
|
||||||
|
-- fails; you can use ChunkSpy and compare using visual diff.
|
||||||
|
----
|
||||||
|
local byte = string.byte
|
||||||
|
local dump = string.dump
|
||||||
|
local load = loadstring or load --luacheck: ignore 113
|
||||||
|
local sub = string.sub
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local is_realtoken = { -- significant (grammar) tokens
|
||||||
|
TK_KEYWORD = true,
|
||||||
|
TK_NAME = true,
|
||||||
|
TK_NUMBER = true,
|
||||||
|
TK_STRING = true,
|
||||||
|
TK_LSTRING = true,
|
||||||
|
TK_OP = true,
|
||||||
|
TK_EOS = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local option, llex, warn
|
||||||
|
|
||||||
|
|
||||||
|
--- The initialization function.
|
||||||
|
--
|
||||||
|
-- @tparam {[string]=bool,...} _option
|
||||||
|
-- @tparam luasrcdiet.llex _llex
|
||||||
|
-- @tparam table _warn
|
||||||
|
function M.init(_option, _llex, _warn)
|
||||||
|
option = _option
|
||||||
|
llex = _llex
|
||||||
|
warn = _warn
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Builds lists containing a 'normal' lexer stream.
|
||||||
|
--
|
||||||
|
-- @tparam string s The source code.
|
||||||
|
-- @treturn table
|
||||||
|
-- @treturn table
|
||||||
|
local function build_stream(s)
|
||||||
|
local stok, sseminfo = llex.lex(s) -- source list (with whitespace elements)
|
||||||
|
local tok, seminfo -- processed list (real elements only)
|
||||||
|
= {}, {}
|
||||||
|
for i = 1, #stok do
|
||||||
|
local t = stok[i]
|
||||||
|
if is_realtoken[t] then
|
||||||
|
tok[#tok + 1] = t
|
||||||
|
seminfo[#seminfo + 1] = sseminfo[i]
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
return tok, seminfo
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Tests source (lexer stream) equivalence.
|
||||||
|
--
|
||||||
|
-- @tparam string z
|
||||||
|
-- @tparam string dat
|
||||||
|
function M.source(z, dat)
|
||||||
|
|
||||||
|
-- Returns a dumped string for seminfo compares.
|
||||||
|
local function dumpsem(s)
|
||||||
|
local sf = load("return "..s, "z")
|
||||||
|
if sf then
|
||||||
|
return dump(sf)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Marks and optionally reports non-equivalence.
|
||||||
|
local function bork(msg)
|
||||||
|
if option.DETAILS then print("SRCEQUIV: "..msg) end
|
||||||
|
warn.SRC_EQUIV = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get lexer streams for both source strings, compare.
|
||||||
|
local tok1, seminfo1 = build_stream(z) -- original
|
||||||
|
local tok2, seminfo2 = build_stream(dat) -- compressed
|
||||||
|
|
||||||
|
-- Compare shbang lines ignoring EOL.
|
||||||
|
local sh1 = z:match("^(#[^\r\n]*)")
|
||||||
|
local sh2 = dat:match("^(#[^\r\n]*)")
|
||||||
|
if sh1 or sh2 then
|
||||||
|
if not sh1 or not sh2 or sh1 ~= sh2 then
|
||||||
|
bork("shbang lines different")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Compare by simple count.
|
||||||
|
if #tok1 ~= #tok2 then
|
||||||
|
bork("count "..#tok1.." "..#tok2)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Compare each element the best we can.
|
||||||
|
for i = 1, #tok1 do
|
||||||
|
local t1, t2 = tok1[i], tok2[i]
|
||||||
|
local s1, s2 = seminfo1[i], seminfo2[i]
|
||||||
|
if t1 ~= t2 then -- by type
|
||||||
|
bork("type ["..i.."] "..t1.." "..t2)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if t1 == "TK_KEYWORD" or t1 == "TK_NAME" or t1 == "TK_OP" then
|
||||||
|
if t1 == "TK_NAME" and option["opt-locals"] then
|
||||||
|
-- can't compare identifiers of locals that are optimized
|
||||||
|
elseif s1 ~= s2 then -- by semantic info (simple)
|
||||||
|
bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif t1 == "TK_EOS" then
|
||||||
|
-- no seminfo to compare
|
||||||
|
else-- "TK_NUMBER" or "TK_STRING" or "TK_LSTRING"
|
||||||
|
-- compare 'binary' form, so dump a function
|
||||||
|
local s1b,s2b = dumpsem(s1), dumpsem(s2)
|
||||||
|
if not s1b or not s2b or s1b ~= s2b then
|
||||||
|
bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
|
||||||
|
-- Successful comparison if end is reached with no borks.
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Tests binary chunk equivalence (only for PUC Lua 5.1).
|
||||||
|
--
|
||||||
|
-- @tparam string z
|
||||||
|
-- @tparam string dat
|
||||||
|
function M.binary(z, dat)
|
||||||
|
local TNIL = 0 --luacheck: ignore 211
|
||||||
|
local TBOOLEAN = 1
|
||||||
|
local TNUMBER = 3
|
||||||
|
local TSTRING = 4
|
||||||
|
|
||||||
|
-- sizes of data types
|
||||||
|
local endian
|
||||||
|
local sz_int
|
||||||
|
local sz_sizet
|
||||||
|
local sz_inst
|
||||||
|
local sz_number
|
||||||
|
local getint
|
||||||
|
local getsizet
|
||||||
|
|
||||||
|
-- Marks and optionally reports non-equivalence.
|
||||||
|
local function bork(msg)
|
||||||
|
if option.DETAILS then print("BINEQUIV: "..msg) end
|
||||||
|
warn.BIN_EQUIV = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Checks if bytes exist.
|
||||||
|
local function ensure(c, sz)
|
||||||
|
if c.i + sz - 1 > c.len then return end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Skips some bytes.
|
||||||
|
local function skip(c, sz)
|
||||||
|
if not sz then sz = 1 end
|
||||||
|
c.i = c.i + sz
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a byte value.
|
||||||
|
local function getbyte(c)
|
||||||
|
local i = c.i
|
||||||
|
if i > c.len then return end
|
||||||
|
local d = sub(c.dat, i, i)
|
||||||
|
c.i = i + 1
|
||||||
|
return byte(d)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return an int value (little-endian).
|
||||||
|
local function getint_l(c)
|
||||||
|
local n, scale = 0, 1
|
||||||
|
if not ensure(c, sz_int) then return end
|
||||||
|
for _ = 1, sz_int do
|
||||||
|
n = n + scale * getbyte(c)
|
||||||
|
scale = scale * 256
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns an int value (big-endian).
|
||||||
|
local function getint_b(c)
|
||||||
|
local n = 0
|
||||||
|
if not ensure(c, sz_int) then return end
|
||||||
|
for _ = 1, sz_int do
|
||||||
|
n = n * 256 + getbyte(c)
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a size_t value (little-endian).
|
||||||
|
local function getsizet_l(c)
|
||||||
|
local n, scale = 0, 1
|
||||||
|
if not ensure(c, sz_sizet) then return end
|
||||||
|
for _ = 1, sz_sizet do
|
||||||
|
n = n + scale * getbyte(c)
|
||||||
|
scale = scale * 256
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a size_t value (big-endian).
|
||||||
|
local function getsizet_b(c)
|
||||||
|
local n = 0
|
||||||
|
if not ensure(c, sz_sizet) then return end
|
||||||
|
for _ = 1, sz_sizet do
|
||||||
|
n = n * 256 + getbyte(c)
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a block (as a string).
|
||||||
|
local function getblock(c, sz)
|
||||||
|
local i = c.i
|
||||||
|
local j = i + sz - 1
|
||||||
|
if j > c.len then return end
|
||||||
|
local d = sub(c.dat, i, j)
|
||||||
|
c.i = i + sz
|
||||||
|
return d
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a string.
|
||||||
|
local function getstring(c)
|
||||||
|
local n = getsizet(c)
|
||||||
|
if not n then return end
|
||||||
|
if n == 0 then return "" end
|
||||||
|
return getblock(c, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Compares byte value.
|
||||||
|
local function goodbyte(c1, c2)
|
||||||
|
local b1, b2 = getbyte(c1), getbyte(c2)
|
||||||
|
if not b1 or not b2 or b1 ~= b2 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return b1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Compares byte value.
|
||||||
|
local function badbyte(c1, c2)
|
||||||
|
local b = goodbyte(c1, c2)
|
||||||
|
if not b then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Compares int value.
|
||||||
|
local function goodint(c1, c2)
|
||||||
|
local i1, i2 = getint(c1), getint(c2)
|
||||||
|
if not i1 or not i2 or i1 ~= i2 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return i1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Recursively-called function to compare function prototypes.
|
||||||
|
local function getfunc(c1, c2)
|
||||||
|
-- source name (ignored)
|
||||||
|
if not getstring(c1) or not getstring(c2) then
|
||||||
|
bork("bad source name"); return
|
||||||
|
end
|
||||||
|
-- linedefined (ignored)
|
||||||
|
if not getint(c1) or not getint(c2) then
|
||||||
|
bork("bad linedefined"); return
|
||||||
|
end
|
||||||
|
-- lastlinedefined (ignored)
|
||||||
|
if not getint(c1) or not getint(c2) then
|
||||||
|
bork("bad lastlinedefined"); return
|
||||||
|
end
|
||||||
|
if not (ensure(c1, 4) and ensure(c2, 4)) then
|
||||||
|
bork("prototype header broken")
|
||||||
|
end
|
||||||
|
-- nups (compared)
|
||||||
|
if badbyte(c1, c2) then
|
||||||
|
bork("bad nups"); return
|
||||||
|
end
|
||||||
|
-- numparams (compared)
|
||||||
|
if badbyte(c1, c2) then
|
||||||
|
bork("bad numparams"); return
|
||||||
|
end
|
||||||
|
-- is_vararg (compared)
|
||||||
|
if badbyte(c1, c2) then
|
||||||
|
bork("bad is_vararg"); return
|
||||||
|
end
|
||||||
|
-- maxstacksize (compared)
|
||||||
|
if badbyte(c1, c2) then
|
||||||
|
bork("bad maxstacksize"); return
|
||||||
|
end
|
||||||
|
-- code (compared)
|
||||||
|
local ncode = goodint(c1, c2)
|
||||||
|
if not ncode then
|
||||||
|
bork("bad ncode"); return
|
||||||
|
end
|
||||||
|
local code1 = getblock(c1, ncode * sz_inst)
|
||||||
|
local code2 = getblock(c2, ncode * sz_inst)
|
||||||
|
if not code1 or not code2 or code1 ~= code2 then
|
||||||
|
bork("bad code block"); return
|
||||||
|
end
|
||||||
|
-- constants (compared)
|
||||||
|
local nconst = goodint(c1, c2)
|
||||||
|
if not nconst then
|
||||||
|
bork("bad nconst"); return
|
||||||
|
end
|
||||||
|
for _ = 1, nconst do
|
||||||
|
local ctype = goodbyte(c1, c2)
|
||||||
|
if not ctype then
|
||||||
|
bork("bad const type"); return
|
||||||
|
end
|
||||||
|
if ctype == TBOOLEAN then
|
||||||
|
if badbyte(c1, c2) then
|
||||||
|
bork("bad boolean value"); return
|
||||||
|
end
|
||||||
|
elseif ctype == TNUMBER then
|
||||||
|
local num1 = getblock(c1, sz_number)
|
||||||
|
local num2 = getblock(c2, sz_number)
|
||||||
|
if not num1 or not num2 or num1 ~= num2 then
|
||||||
|
bork("bad number value"); return
|
||||||
|
end
|
||||||
|
elseif ctype == TSTRING then
|
||||||
|
local str1 = getstring(c1)
|
||||||
|
local str2 = getstring(c2)
|
||||||
|
if not str1 or not str2 or str1 ~= str2 then
|
||||||
|
bork("bad string value"); return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- prototypes (compared recursively)
|
||||||
|
local nproto = goodint(c1, c2)
|
||||||
|
if not nproto then
|
||||||
|
bork("bad nproto"); return
|
||||||
|
end
|
||||||
|
for _ = 1, nproto do
|
||||||
|
if not getfunc(c1, c2) then
|
||||||
|
bork("bad function prototype"); return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- debug information (ignored)
|
||||||
|
-- lineinfo (ignored)
|
||||||
|
local sizelineinfo1 = getint(c1)
|
||||||
|
if not sizelineinfo1 then
|
||||||
|
bork("bad sizelineinfo1"); return
|
||||||
|
end
|
||||||
|
local sizelineinfo2 = getint(c2)
|
||||||
|
if not sizelineinfo2 then
|
||||||
|
bork("bad sizelineinfo2"); return
|
||||||
|
end
|
||||||
|
if not getblock(c1, sizelineinfo1 * sz_int) then
|
||||||
|
bork("bad lineinfo1"); return
|
||||||
|
end
|
||||||
|
if not getblock(c2, sizelineinfo2 * sz_int) then
|
||||||
|
bork("bad lineinfo2"); return
|
||||||
|
end
|
||||||
|
-- locvars (ignored)
|
||||||
|
local sizelocvars1 = getint(c1)
|
||||||
|
if not sizelocvars1 then
|
||||||
|
bork("bad sizelocvars1"); return
|
||||||
|
end
|
||||||
|
local sizelocvars2 = getint(c2)
|
||||||
|
if not sizelocvars2 then
|
||||||
|
bork("bad sizelocvars2"); return
|
||||||
|
end
|
||||||
|
for _ = 1, sizelocvars1 do
|
||||||
|
if not getstring(c1) or not getint(c1) or not getint(c1) then
|
||||||
|
bork("bad locvars1"); return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _ = 1, sizelocvars2 do
|
||||||
|
if not getstring(c2) or not getint(c2) or not getint(c2) then
|
||||||
|
bork("bad locvars2"); return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- upvalues (ignored)
|
||||||
|
local sizeupvalues1 = getint(c1)
|
||||||
|
if not sizeupvalues1 then
|
||||||
|
bork("bad sizeupvalues1"); return
|
||||||
|
end
|
||||||
|
local sizeupvalues2 = getint(c2)
|
||||||
|
if not sizeupvalues2 then
|
||||||
|
bork("bad sizeupvalues2"); return
|
||||||
|
end
|
||||||
|
for _ = 1, sizeupvalues1 do
|
||||||
|
if not getstring(c1) then bork("bad upvalues1"); return end
|
||||||
|
end
|
||||||
|
for _ = 1, sizeupvalues2 do
|
||||||
|
if not getstring(c2) then bork("bad upvalues2"); return end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Removes shbang line so that load runs.
|
||||||
|
local function zap_shbang(s)
|
||||||
|
local shbang = s:match("^(#[^\r\n]*\r?\n?)")
|
||||||
|
if shbang then -- cut out shbang
|
||||||
|
s = sub(s, #shbang + 1)
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Attempt to compile, then dump to get binary chunk string.
|
||||||
|
local cz = load(zap_shbang(z), "z")
|
||||||
|
if not cz then
|
||||||
|
bork("failed to compile original sources for binary chunk comparison")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cdat = load(zap_shbang(dat), "z")
|
||||||
|
if not cdat then
|
||||||
|
bork("failed to compile compressed result for binary chunk comparison")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if load() works, dump assuming string.dump() is error-free
|
||||||
|
local c1 = { i = 1, dat = dump(cz) }
|
||||||
|
c1.len = #c1.dat
|
||||||
|
|
||||||
|
local c2 = { i = 1, dat = dump(cdat) }
|
||||||
|
c2.len = #c2.dat
|
||||||
|
|
||||||
|
-- Parse binary chunks to verify equivalence.
|
||||||
|
-- * For headers, handle sizes to allow a degree of flexibility.
|
||||||
|
-- * Assume a valid binary chunk is generated, since it was not
|
||||||
|
-- generated via external means.
|
||||||
|
if not (ensure(c1, 12) and ensure(c2, 12)) then
|
||||||
|
bork("header broken")
|
||||||
|
end
|
||||||
|
skip(c1, 6) -- skip signature(4), version, format
|
||||||
|
endian = getbyte(c1) -- 1 = little endian
|
||||||
|
sz_int = getbyte(c1) -- get data type sizes
|
||||||
|
sz_sizet = getbyte(c1)
|
||||||
|
sz_inst = getbyte(c1)
|
||||||
|
sz_number = getbyte(c1)
|
||||||
|
skip(c1) -- skip integral flag
|
||||||
|
skip(c2, 12) -- skip other header (assume similar)
|
||||||
|
|
||||||
|
if endian == 1 then -- set for endian sensitive data we need
|
||||||
|
getint = getint_l
|
||||||
|
getsizet = getsizet_l
|
||||||
|
else
|
||||||
|
getint = getint_b
|
||||||
|
getsizet = getsizet_b
|
||||||
|
end
|
||||||
|
getfunc(c1, c2) -- get prototype at root
|
||||||
|
|
||||||
|
if c1.i ~= c1.len + 1 then
|
||||||
|
bork("inconsistent binary chunk1"); return
|
||||||
|
elseif c2.i ~= c2.len + 1 then
|
||||||
|
bork("inconsistent binary chunk2"); return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Successful comparison if end is reached with no borks.
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
74
tools/luasrcdiet/fs.lua
Normal file
74
tools/luasrcdiet/fs.lua
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
---------
|
||||||
|
-- Utility functions for operations on a file system.
|
||||||
|
--
|
||||||
|
-- **Note: This module is not part of public API!**
|
||||||
|
----
|
||||||
|
local fmt = string.format
|
||||||
|
local open = io.open
|
||||||
|
|
||||||
|
local UTF8_BOM = '\239\187\191'
|
||||||
|
|
||||||
|
local function normalize_io_error (name, err)
|
||||||
|
if err:sub(1, #name + 2) == name..': ' then
|
||||||
|
err = err:sub(#name + 3)
|
||||||
|
end
|
||||||
|
return err
|
||||||
|
end
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Reads the specified file and returns its content as string.
|
||||||
|
--
|
||||||
|
-- @tparam string filename Path of the file to read.
|
||||||
|
-- @tparam string mode The mode in which to open the file, see @{io.open} (default: "r").
|
||||||
|
-- @treturn[1] string A content of the file.
|
||||||
|
-- @treturn[2] nil
|
||||||
|
-- @treturn[2] string An error message.
|
||||||
|
function M.read_file (filename, mode)
|
||||||
|
local handler, err = open(filename, mode or 'r')
|
||||||
|
if not handler then
|
||||||
|
return nil, fmt('Could not open %s for reading: %s',
|
||||||
|
filename, normalize_io_error(filename, err))
|
||||||
|
end
|
||||||
|
|
||||||
|
local content, err = handler:read('*a') --luacheck: ignore 411
|
||||||
|
if not content then
|
||||||
|
return nil, fmt('Could not read %s: %s', filename, normalize_io_error(filename, err))
|
||||||
|
end
|
||||||
|
|
||||||
|
handler:close()
|
||||||
|
|
||||||
|
if content:sub(1, #UTF8_BOM) == UTF8_BOM then
|
||||||
|
content = content:sub(#UTF8_BOM + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return content
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Writes the given data to the specified file.
|
||||||
|
--
|
||||||
|
-- @tparam string filename Path of the file to write.
|
||||||
|
-- @tparam string data The data to write.
|
||||||
|
-- @tparam ?string mode The mode in which to open the file, see @{io.open} (default: "w").
|
||||||
|
-- @treturn[1] true
|
||||||
|
-- @treturn[2] nil
|
||||||
|
-- @treturn[2] string An error message.
|
||||||
|
function M.write_file (filename, data, mode)
|
||||||
|
local handler, err = open(filename, mode or 'w')
|
||||||
|
if not handler then
|
||||||
|
return nil, fmt('Could not open %s for writing: %s',
|
||||||
|
filename, normalize_io_error(filename, err))
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, err = handler:write(data) --luacheck: ignore 411
|
||||||
|
if err then
|
||||||
|
return nil, fmt('Could not write %s: %s', filename, normalize_io_error(filename, err))
|
||||||
|
end
|
||||||
|
|
||||||
|
handler:flush()
|
||||||
|
handler:close()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
117
tools/luasrcdiet/init.lua
Normal file
117
tools/luasrcdiet/init.lua
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
---------
|
||||||
|
-- LuaSrcDiet API
|
||||||
|
----
|
||||||
|
local equiv = require 'luasrcdiet.equiv'
|
||||||
|
local llex = require 'luasrcdiet.llex'
|
||||||
|
local lparser = require 'luasrcdiet.lparser'
|
||||||
|
local optlex = require 'luasrcdiet.optlex'
|
||||||
|
local optparser = require 'luasrcdiet.optparser'
|
||||||
|
local utils = require 'luasrcdiet.utils'
|
||||||
|
|
||||||
|
local concat = table.concat
|
||||||
|
local merge = utils.merge
|
||||||
|
|
||||||
|
local _ -- placeholder
|
||||||
|
|
||||||
|
|
||||||
|
local function noop ()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function opts_to_legacy (opts)
|
||||||
|
local res = {}
|
||||||
|
for key, val in pairs(opts) do
|
||||||
|
res['opt-'..key] = val
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- The module's name.
|
||||||
|
M._NAME = 'luasrcdiet'
|
||||||
|
|
||||||
|
--- The module's version number.
|
||||||
|
M._VERSION = '1.0.0'
|
||||||
|
|
||||||
|
--- The module's homepage.
|
||||||
|
M._HOMEPAGE = 'https://github.com/jirutka/luasrcdiet'
|
||||||
|
|
||||||
|
--- All optimizations disabled.
|
||||||
|
M.NONE_OPTS = {
|
||||||
|
binequiv = false,
|
||||||
|
comments = false,
|
||||||
|
emptylines = false,
|
||||||
|
entropy = false,
|
||||||
|
eols = false,
|
||||||
|
experimental = false,
|
||||||
|
locals = false,
|
||||||
|
numbers = false,
|
||||||
|
srcequiv = false,
|
||||||
|
strings = false,
|
||||||
|
whitespace = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Basic optimizations enabled.
|
||||||
|
-- @table BASIC_OPTS
|
||||||
|
M.BASIC_OPTS = merge(M.NONE_OPTS, {
|
||||||
|
comments = true,
|
||||||
|
emptylines = true,
|
||||||
|
srcequiv = true,
|
||||||
|
whitespace = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
--- Defaults.
|
||||||
|
-- @table DEFAULT_OPTS
|
||||||
|
M.DEFAULT_OPTS = merge(M.BASIC_OPTS, {
|
||||||
|
locals = true,
|
||||||
|
numbers = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
--- Maximum optimizations enabled (all except experimental).
|
||||||
|
-- @table MAXIMUM_OPTS
|
||||||
|
M.MAXIMUM_OPTS = merge(M.DEFAULT_OPTS, {
|
||||||
|
entropy = true,
|
||||||
|
eols = true,
|
||||||
|
strings = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
--- Optimizes the given Lua source code.
|
||||||
|
--
|
||||||
|
-- @tparam ?{[string]=bool,...} opts Optimizations to do (default is @{DEFAULT_OPTS}).
|
||||||
|
-- @tparam string source The Lua source code to optimize.
|
||||||
|
-- @treturn string Optimized source.
|
||||||
|
-- @raise if the source is malformed, source equivalence test failed, or some
|
||||||
|
-- other error occured.
|
||||||
|
function M.optimize (opts, source)
|
||||||
|
assert(source and type(source) == 'string',
|
||||||
|
'bad argument #2: expected string, got a '..type(source))
|
||||||
|
|
||||||
|
opts = opts and merge(M.NONE_OPTS, opts) or M.DEFAULT_OPTS
|
||||||
|
local legacy_opts = opts_to_legacy(opts)
|
||||||
|
|
||||||
|
local toklist, seminfolist, toklnlist = llex.lex(source)
|
||||||
|
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
|
||||||
|
|
||||||
|
optparser.print = noop
|
||||||
|
optparser.optimize(legacy_opts, toklist, seminfolist, xinfo)
|
||||||
|
|
||||||
|
local warn = optlex.warn -- use this as a general warning lookup
|
||||||
|
optlex.print = noop
|
||||||
|
_, seminfolist = optlex.optimize(legacy_opts, toklist, seminfolist, toklnlist)
|
||||||
|
local optim_source = concat(seminfolist)
|
||||||
|
|
||||||
|
if opts.srcequiv and not opts.experimental then
|
||||||
|
equiv.init(legacy_opts, llex, warn)
|
||||||
|
equiv.source(source, optim_source)
|
||||||
|
|
||||||
|
if warn.SRC_EQUIV then
|
||||||
|
error('Source equivalence test failed!')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return optim_source
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
350
tools/luasrcdiet/llex.lua
Normal file
350
tools/luasrcdiet/llex.lua
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
---------
|
||||||
|
-- Lua 5.1+ lexical analyzer written in Lua.
|
||||||
|
--
|
||||||
|
-- This file is part of LuaSrcDiet, based on Yueliang material.
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * This is a version of the native 5.1.x lexer from Yueliang 0.4.0,
|
||||||
|
-- with significant modifications to handle LuaSrcDiet's needs:
|
||||||
|
-- (1) llex.error is an optional error function handler,
|
||||||
|
-- (2) seminfo for strings include their delimiters and no
|
||||||
|
-- translation operations are performed on them.
|
||||||
|
-- * ADDED shbang handling has been added to support executable scripts.
|
||||||
|
-- * NO localized decimal point replacement magic.
|
||||||
|
-- * NO limit to number of lines.
|
||||||
|
-- * NO support for compatible long strings (LUA\_COMPAT_LSTR).
|
||||||
|
-- * Added goto keyword and double-colon operator (Lua 5.2+).
|
||||||
|
----
|
||||||
|
local find = string.find
|
||||||
|
local fmt = string.format
|
||||||
|
local match = string.match
|
||||||
|
local sub = string.sub
|
||||||
|
local tonumber = tonumber
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local kw = {}
|
||||||
|
for v in ([[
|
||||||
|
and break do else elseif end false for function goto if in
|
||||||
|
local nil not or repeat return then true until while]]):gmatch("%S+") do
|
||||||
|
kw[v] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local z, -- source stream
|
||||||
|
sourceid, -- name of source
|
||||||
|
I, -- position of lexer
|
||||||
|
buff, -- buffer for strings
|
||||||
|
ln, -- line number
|
||||||
|
tok, -- lexed token list
|
||||||
|
seminfo, -- lexed semantic information list
|
||||||
|
tokln -- line numbers for messages
|
||||||
|
|
||||||
|
|
||||||
|
--- Adds information to token listing.
|
||||||
|
--
|
||||||
|
-- @tparam string token
|
||||||
|
-- @tparam string info
|
||||||
|
local function addtoken(token, info)
|
||||||
|
local i = #tok + 1
|
||||||
|
tok[i] = token
|
||||||
|
seminfo[i] = info
|
||||||
|
tokln[i] = ln
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Handles line number incrementation and end-of-line characters.
|
||||||
|
--
|
||||||
|
-- @tparam int i Position of lexer in the source stream.
|
||||||
|
-- @tparam bool is_tok
|
||||||
|
-- @treturn int
|
||||||
|
local function inclinenumber(i, is_tok)
|
||||||
|
local old = sub(z, i, i)
|
||||||
|
i = i + 1 -- skip '\n' or '\r'
|
||||||
|
local c = sub(z, i, i)
|
||||||
|
if (c == "\n" or c == "\r") and (c ~= old) then
|
||||||
|
i = i + 1 -- skip '\n\r' or '\r\n'
|
||||||
|
old = old..c
|
||||||
|
end
|
||||||
|
if is_tok then addtoken("TK_EOL", old) end
|
||||||
|
ln = ln + 1
|
||||||
|
I = i
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns a chunk name or id, no truncation for long names.
|
||||||
|
--
|
||||||
|
-- @treturn string
|
||||||
|
local function chunkid()
|
||||||
|
if sourceid and match(sourceid, "^[=@]") then
|
||||||
|
return sub(sourceid, 2) -- remove first char
|
||||||
|
end
|
||||||
|
return "[string]"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Formats error message and throws error.
|
||||||
|
--
|
||||||
|
-- A simplified version, does not report what token was responsible.
|
||||||
|
--
|
||||||
|
-- @tparam string s
|
||||||
|
-- @tparam int line The line number.
|
||||||
|
-- @raise
|
||||||
|
local function errorline(s, line)
|
||||||
|
local e = M.error or error
|
||||||
|
e(fmt("%s:%d: %s", chunkid(), line or ln, s))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Counts separators (`="` in a long string delimiter.
|
||||||
|
--
|
||||||
|
-- @tparam int i Position of lexer in the source stream.
|
||||||
|
-- @treturn int
|
||||||
|
local function skip_sep(i)
|
||||||
|
local s = sub(z, i, i)
|
||||||
|
i = i + 1
|
||||||
|
local count = #match(z, "=*", i)
|
||||||
|
i = i + count
|
||||||
|
I = i
|
||||||
|
return (sub(z, i, i) == s) and count or (-count) - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Reads a long string or long comment.
|
||||||
|
--
|
||||||
|
-- @tparam bool is_str
|
||||||
|
-- @tparam string sep
|
||||||
|
-- @treturn string
|
||||||
|
-- @raise if unfinished long string or comment.
|
||||||
|
local function read_long_string(is_str, sep)
|
||||||
|
local i = I + 1 -- skip 2nd '['
|
||||||
|
local c = sub(z, i, i)
|
||||||
|
if c == "\r" or c == "\n" then -- string starts with a newline?
|
||||||
|
i = inclinenumber(i) -- skip it
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
local p, _, r = find(z, "([\r\n%]])", i) -- (long range match)
|
||||||
|
if not p then
|
||||||
|
errorline(is_str and "unfinished long string" or
|
||||||
|
"unfinished long comment")
|
||||||
|
end
|
||||||
|
i = p
|
||||||
|
if r == "]" then -- delimiter test
|
||||||
|
if skip_sep(i) == sep then
|
||||||
|
buff = sub(z, buff, I)
|
||||||
|
I = I + 1 -- skip 2nd ']'
|
||||||
|
return buff
|
||||||
|
end
|
||||||
|
i = I
|
||||||
|
else -- newline
|
||||||
|
buff = buff.."\n"
|
||||||
|
i = inclinenumber(i)
|
||||||
|
end
|
||||||
|
end--while
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Reads a string.
|
||||||
|
--
|
||||||
|
-- @tparam string del The delimiter.
|
||||||
|
-- @treturn string
|
||||||
|
-- @raise if unfinished string or too large escape sequence.
|
||||||
|
local function read_string(del)
|
||||||
|
local i = I
|
||||||
|
while true do
|
||||||
|
local p, _, r = find(z, "([\n\r\\\"\'])", i) -- (long range match)
|
||||||
|
if p then
|
||||||
|
if r == "\n" or r == "\r" then
|
||||||
|
errorline("unfinished string")
|
||||||
|
end
|
||||||
|
i = p
|
||||||
|
if r == "\\" then -- handle escapes
|
||||||
|
i = i + 1
|
||||||
|
r = sub(z, i, i)
|
||||||
|
if r == "" then break end -- (EOZ error)
|
||||||
|
p = find("abfnrtv\n\r", r, 1, true)
|
||||||
|
|
||||||
|
if p then -- special escapes
|
||||||
|
if p > 7 then
|
||||||
|
i = inclinenumber(i)
|
||||||
|
else
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif find(r, "%D") then -- other non-digits
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
else -- \xxx sequence
|
||||||
|
local _, q, s = find(z, "^(%d%d?%d?)", i)
|
||||||
|
i = q + 1
|
||||||
|
if s + 1 > 256 then -- UCHAR_MAX
|
||||||
|
errorline("escape sequence too large")
|
||||||
|
end
|
||||||
|
|
||||||
|
end--if p
|
||||||
|
else
|
||||||
|
i = i + 1
|
||||||
|
if r == del then -- ending delimiter
|
||||||
|
I = i
|
||||||
|
return sub(z, buff, i - 1) -- return string
|
||||||
|
end
|
||||||
|
end--if r
|
||||||
|
else
|
||||||
|
break -- (error)
|
||||||
|
end--if p
|
||||||
|
end--while
|
||||||
|
errorline("unfinished string")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--- Initializes lexer for given source _z and source name _sourceid.
|
||||||
|
--
|
||||||
|
-- @tparam string _z The source code.
|
||||||
|
-- @tparam string _sourceid Name of the source.
|
||||||
|
local function init(_z, _sourceid)
|
||||||
|
z = _z -- source
|
||||||
|
sourceid = _sourceid -- name of source
|
||||||
|
I = 1 -- lexer's position in source
|
||||||
|
ln = 1 -- line number
|
||||||
|
tok = {} -- lexed token list*
|
||||||
|
seminfo = {} -- lexed semantic information list*
|
||||||
|
tokln = {} -- line numbers for messages*
|
||||||
|
|
||||||
|
-- Initial processing (shbang handling).
|
||||||
|
local p, _, q, r = find(z, "^(#[^\r\n]*)(\r?\n?)")
|
||||||
|
if p then -- skip first line
|
||||||
|
I = I + #q
|
||||||
|
addtoken("TK_COMMENT", q)
|
||||||
|
if #r > 0 then inclinenumber(I, true) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Runs lexer on the given source code.
|
||||||
|
--
|
||||||
|
-- @tparam string source The Lua source to scan.
|
||||||
|
-- @tparam ?string source_name Name of the source (optional).
|
||||||
|
-- @treturn {string,...} A list of lexed tokens.
|
||||||
|
-- @treturn {string,...} A list of semantic information (lexed strings).
|
||||||
|
-- @treturn {int,...} A list of line numbers.
|
||||||
|
function M.lex(source, source_name)
|
||||||
|
init(source, source_name)
|
||||||
|
|
||||||
|
while true do--outer
|
||||||
|
local i = I
|
||||||
|
-- inner loop allows break to be used to nicely section tests
|
||||||
|
while true do --luacheck: ignore 512
|
||||||
|
|
||||||
|
local p, _, r = find(z, "^([_%a][_%w]*)", i)
|
||||||
|
if p then
|
||||||
|
I = i + #r
|
||||||
|
if kw[r] then
|
||||||
|
addtoken("TK_KEYWORD", r) -- reserved word (keyword)
|
||||||
|
else
|
||||||
|
addtoken("TK_NAME", r) -- identifier
|
||||||
|
end
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
|
||||||
|
local p, _, r = find(z, "^(%.?)%d", i)
|
||||||
|
if p then -- numeral
|
||||||
|
if r == "." then i = i + 1 end
|
||||||
|
local _, q, r = find(z, "^%d*[%.%d]*([eE]?)", i) --luacheck: ignore 421
|
||||||
|
i = q + 1
|
||||||
|
if #r == 1 then -- optional exponent
|
||||||
|
if match(z, "^[%+%-]", i) then -- optional sign
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local _, q = find(z, "^[_%w]*", i)
|
||||||
|
I = q + 1
|
||||||
|
local v = sub(z, p, q) -- string equivalent
|
||||||
|
if not tonumber(v) then -- handles hex test also
|
||||||
|
errorline("malformed number")
|
||||||
|
end
|
||||||
|
addtoken("TK_NUMBER", v)
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
|
||||||
|
local p, q, r, t = find(z, "^((%s)[ \t\v\f]*)", i)
|
||||||
|
if p then
|
||||||
|
if t == "\n" or t == "\r" then -- newline
|
||||||
|
inclinenumber(i, true)
|
||||||
|
else
|
||||||
|
I = q + 1 -- whitespace
|
||||||
|
addtoken("TK_SPACE", r)
|
||||||
|
end
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, q = find(z, "^::", i)
|
||||||
|
if q then
|
||||||
|
I = q + 1
|
||||||
|
addtoken("TK_OP", "::")
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
|
||||||
|
local r = match(z, "^%p", i)
|
||||||
|
if r then
|
||||||
|
buff = i
|
||||||
|
local p = find("-[\"\'.=<>~", r, 1, true) --luacheck: ignore 421
|
||||||
|
if p then
|
||||||
|
|
||||||
|
-- two-level if block for punctuation/symbols
|
||||||
|
if p <= 2 then
|
||||||
|
if p == 1 then -- minus
|
||||||
|
local c = match(z, "^%-%-(%[?)", i)
|
||||||
|
if c then
|
||||||
|
i = i + 2
|
||||||
|
local sep = -1
|
||||||
|
if c == "[" then
|
||||||
|
sep = skip_sep(i)
|
||||||
|
end
|
||||||
|
if sep >= 0 then -- long comment
|
||||||
|
addtoken("TK_LCOMMENT", read_long_string(false, sep))
|
||||||
|
else -- short comment
|
||||||
|
I = find(z, "[\n\r]", i) or (#z + 1)
|
||||||
|
addtoken("TK_COMMENT", sub(z, buff, I - 1))
|
||||||
|
end
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
-- (fall through for "-")
|
||||||
|
else -- [ or long string
|
||||||
|
local sep = skip_sep(i)
|
||||||
|
if sep >= 0 then
|
||||||
|
addtoken("TK_LSTRING", read_long_string(true, sep))
|
||||||
|
elseif sep == -1 then
|
||||||
|
addtoken("TK_OP", "[")
|
||||||
|
else
|
||||||
|
errorline("invalid long string delimiter")
|
||||||
|
end
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif p <= 5 then
|
||||||
|
if p < 5 then -- strings
|
||||||
|
I = i + 1
|
||||||
|
addtoken("TK_STRING", read_string(r))
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
r = match(z, "^%.%.?%.?", i) -- .|..|... dots
|
||||||
|
-- (fall through)
|
||||||
|
|
||||||
|
else -- relational
|
||||||
|
r = match(z, "^%p=?", i)
|
||||||
|
-- (fall through)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
I = i + #r
|
||||||
|
addtoken("TK_OP", r) -- for other symbols, fall through
|
||||||
|
break -- (continue)
|
||||||
|
end
|
||||||
|
|
||||||
|
local r = sub(z, i, i)
|
||||||
|
if r ~= "" then
|
||||||
|
I = i + 1
|
||||||
|
addtoken("TK_OP", r) -- other single-char tokens
|
||||||
|
break
|
||||||
|
end
|
||||||
|
addtoken("TK_EOS", "") -- end of stream,
|
||||||
|
return tok, seminfo, tokln -- exit here
|
||||||
|
|
||||||
|
end--while inner
|
||||||
|
end--while outer
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
1286
tools/luasrcdiet/lparser.lua
Normal file
1286
tools/luasrcdiet/lparser.lua
Normal file
File diff suppressed because it is too large
Load Diff
852
tools/luasrcdiet/optlex.lua
Normal file
852
tools/luasrcdiet/optlex.lua
Normal file
@ -0,0 +1,852 @@
|
|||||||
|
---------
|
||||||
|
-- This module does lexer-based optimizations.
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * TODO: General string delimiter conversion optimizer.
|
||||||
|
-- * TODO: (numbers) warn if overly significant digit.
|
||||||
|
----
|
||||||
|
local char = string.char
|
||||||
|
local find = string.find
|
||||||
|
local match = string.match
|
||||||
|
local rep = string.rep
|
||||||
|
local sub = string.sub
|
||||||
|
local tonumber = tonumber
|
||||||
|
local tostring = tostring
|
||||||
|
|
||||||
|
local print -- set in optimize()
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
-- error function, can override by setting own function into module
|
||||||
|
M.error = error
|
||||||
|
|
||||||
|
M.warn = {} -- table for warning flags
|
||||||
|
|
||||||
|
local stoks, sinfos, stoklns -- source lists
|
||||||
|
|
||||||
|
local is_realtoken = { -- significant (grammar) tokens
|
||||||
|
TK_KEYWORD = true,
|
||||||
|
TK_NAME = true,
|
||||||
|
TK_NUMBER = true,
|
||||||
|
TK_STRING = true,
|
||||||
|
TK_LSTRING = true,
|
||||||
|
TK_OP = true,
|
||||||
|
TK_EOS = true,
|
||||||
|
}
|
||||||
|
local is_faketoken = { -- whitespace (non-grammar) tokens
|
||||||
|
TK_COMMENT = true,
|
||||||
|
TK_LCOMMENT = true,
|
||||||
|
TK_EOL = true,
|
||||||
|
TK_SPACE = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local opt_details -- for extra information
|
||||||
|
|
||||||
|
--- Returns true if current token is at the start of a line.
|
||||||
|
--
|
||||||
|
-- It skips over deleted tokens via recursion.
|
||||||
|
--
|
||||||
|
-- @tparam int i
|
||||||
|
-- @treturn bool
|
||||||
|
local function atlinestart(i)
|
||||||
|
local tok = stoks[i - 1]
|
||||||
|
if i <= 1 or tok == "TK_EOL" then
|
||||||
|
return true
|
||||||
|
elseif tok == "" then
|
||||||
|
return atlinestart(i - 1)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns true if current token is at the end of a line.
|
||||||
|
--
|
||||||
|
-- It skips over deleted tokens via recursion.
|
||||||
|
--
|
||||||
|
-- @tparam int i
|
||||||
|
-- @treturn bool
|
||||||
|
local function atlineend(i)
|
||||||
|
local tok = stoks[i + 1]
|
||||||
|
if i >= #stoks or tok == "TK_EOL" or tok == "TK_EOS" then
|
||||||
|
return true
|
||||||
|
elseif tok == "" then
|
||||||
|
return atlineend(i + 1)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Counts comment EOLs inside a long comment.
|
||||||
|
--
|
||||||
|
-- In order to keep line numbering, EOLs need to be reinserted.
|
||||||
|
--
|
||||||
|
-- @tparam string lcomment
|
||||||
|
-- @treturn int
|
||||||
|
local function commenteols(lcomment)
|
||||||
|
local sep = #match(lcomment, "^%-%-%[=*%[")
|
||||||
|
local z = sub(lcomment, sep + 1, -(sep - 1)) -- remove delims
|
||||||
|
local i, c = 1, 0
|
||||||
|
while true do
|
||||||
|
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
|
||||||
|
if not p then break end -- if no matches, done
|
||||||
|
i = p + 1
|
||||||
|
c = c + 1
|
||||||
|
if #s > 0 and r ~= s then -- skip CRLF or LFCR
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Compares two tokens (i, j) and returns the whitespace required.
|
||||||
|
--
|
||||||
|
-- See documentation for a reference table of interactions.
|
||||||
|
--
|
||||||
|
-- Only two grammar/real tokens are being considered:
|
||||||
|
--
|
||||||
|
-- * if `""`, no separation is needed,
|
||||||
|
-- * if `" "`, then at least one whitespace (or EOL) is required.
|
||||||
|
--
|
||||||
|
-- Note: This doesn't work at the start or the end or for EOS!
|
||||||
|
--
|
||||||
|
-- @tparam int i
|
||||||
|
-- @tparam int j
|
||||||
|
-- @treturn string
|
||||||
|
local function checkpair(i, j)
|
||||||
|
local t1, t2 = stoks[i], stoks[j]
|
||||||
|
|
||||||
|
if t1 == "TK_STRING" or t1 == "TK_LSTRING" or
|
||||||
|
t2 == "TK_STRING" or t2 == "TK_LSTRING" then
|
||||||
|
return ""
|
||||||
|
|
||||||
|
elseif t1 == "TK_OP" or t2 == "TK_OP" then
|
||||||
|
if (t1 == "TK_OP" and (t2 == "TK_KEYWORD" or t2 == "TK_NAME")) or
|
||||||
|
(t2 == "TK_OP" and (t1 == "TK_KEYWORD" or t1 == "TK_NAME")) then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
if t1 == "TK_OP" and t2 == "TK_OP" then
|
||||||
|
-- for TK_OP/TK_OP pairs, see notes in technotes.txt
|
||||||
|
local op, op2 = sinfos[i], sinfos[j]
|
||||||
|
if (match(op, "^%.%.?$") and match(op2, "^%.")) or
|
||||||
|
(match(op, "^[~=<>]$") and op2 == "=") or
|
||||||
|
(op == "[" and (op2 == "[" or op2 == "=")) then
|
||||||
|
return " "
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
-- "TK_OP" + "TK_NUMBER" case
|
||||||
|
local op = sinfos[i]
|
||||||
|
if t2 == "TK_OP" then op = sinfos[j] end
|
||||||
|
if match(op, "^%.%.?%.?$") then
|
||||||
|
return " "
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
|
||||||
|
else-- "TK_KEYWORD" | "TK_NAME" | "TK_NUMBER" then
|
||||||
|
return " "
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Repack tokens, removing deletions caused by optimization process.
|
||||||
|
local function repack_tokens()
|
||||||
|
local dtoks, dinfos, dtoklns = {}, {}, {}
|
||||||
|
local j = 1
|
||||||
|
for i = 1, #stoks do
|
||||||
|
local tok = stoks[i]
|
||||||
|
if tok ~= "" then
|
||||||
|
dtoks[j], dinfos[j], dtoklns[j] = tok, sinfos[i], stoklns[i]
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stoks, sinfos, stoklns = dtoks, dinfos, dtoklns
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Does number optimization.
|
||||||
|
--
|
||||||
|
-- Optimization using string formatting functions is one way of doing this,
|
||||||
|
-- but here, we consider all cases and handle them separately (possibly an
|
||||||
|
-- idiotic approach...).
|
||||||
|
--
|
||||||
|
-- Scientific notation being generated is not in canonical form, this may or
|
||||||
|
-- may not be a bad thing.
|
||||||
|
--
|
||||||
|
-- Note: Intermediate portions need to fit into a normal number range.
|
||||||
|
--
|
||||||
|
-- Optimizations can be divided based on number patterns:
|
||||||
|
--
|
||||||
|
-- * hexadecimal:
|
||||||
|
-- (1) no need to remove leading zeros, just skip to (2)
|
||||||
|
-- (2) convert to integer if size equal or smaller
|
||||||
|
-- * change if equal size -> lose the 'x' to reduce entropy
|
||||||
|
-- (3) number is then processed as an integer
|
||||||
|
-- (4) note: does not make 0[xX] consistent
|
||||||
|
-- * integer:
|
||||||
|
-- (1) reduce useless fractional part, if present, e.g. 123.000 -> 123.
|
||||||
|
-- (2) remove leading zeros, e.g. 000123
|
||||||
|
-- * float:
|
||||||
|
-- (1) split into digits dot digits
|
||||||
|
-- (2) if no integer portion, take as zero (can omit later)
|
||||||
|
-- (3) handle degenerate .000 case, after which the fractional part
|
||||||
|
-- must be non-zero (if zero, it's matched as float .0)
|
||||||
|
-- (4) remove trailing zeros for fractional portion
|
||||||
|
-- (5) p.q where p > 0 and q > 0 cannot be shortened any more
|
||||||
|
-- (6) otherwise p == 0 and the form is .q, e.g. .000123
|
||||||
|
-- (7) if scientific shorter, convert, e.g. .000123 -> 123e-6
|
||||||
|
-- * scientific:
|
||||||
|
-- (1) split into (digits dot digits) [eE] ([+-] digits)
|
||||||
|
-- (2) if significand is zero, just use .0
|
||||||
|
-- (3) remove leading zeros for significand
|
||||||
|
-- (4) shift out trailing zeros for significand
|
||||||
|
-- (5) examine exponent and determine which format is best:
|
||||||
|
-- number with fraction, or scientific
|
||||||
|
--
|
||||||
|
-- Note: Number with fraction and scientific number is never converted
|
||||||
|
-- to integer, because Lua 5.3 distinguishes between integers and floats.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- @tparam int i
|
||||||
|
local function do_number(i)
|
||||||
|
local before = sinfos[i] -- 'before'
|
||||||
|
local z = before -- working representation
|
||||||
|
local y -- 'after', if better
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
if match(z, "^0[xX]") then -- hexadecimal number
|
||||||
|
local v = tostring(tonumber(z))
|
||||||
|
if #v <= #z then
|
||||||
|
z = v -- change to integer, AND continue
|
||||||
|
else
|
||||||
|
return -- no change; stick to hex
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if match(z, "^%d+$") then -- integer
|
||||||
|
if tonumber(z) > 0 then
|
||||||
|
y = match(z, "^0*([1-9]%d*)$") -- remove leading zeros
|
||||||
|
else
|
||||||
|
y = "0" -- basic zero
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif not match(z, "[eE]") then -- float
|
||||||
|
local p, q = match(z, "^(%d*)%.(%d*)$") -- split
|
||||||
|
if p == "" then p = 0 end -- int part zero
|
||||||
|
if q == "" then q = "0" end -- fraction part zero
|
||||||
|
if tonumber(q) == 0 and p == 0 then
|
||||||
|
y = ".0" -- degenerate .000 to .0
|
||||||
|
else
|
||||||
|
-- now, q > 0 holds and p is a number
|
||||||
|
local zeros_cnt = #match(q, "0*$") -- remove trailing zeros
|
||||||
|
if zeros_cnt > 0 then
|
||||||
|
q = sub(q, 1, #q - zeros_cnt)
|
||||||
|
end
|
||||||
|
-- if p > 0, nothing else we can do to simplify p.q case
|
||||||
|
if tonumber(p) > 0 then
|
||||||
|
y = p.."."..q
|
||||||
|
else
|
||||||
|
y = "."..q -- tentative, e.g. .000123
|
||||||
|
local v = #match(q, "^0*") -- # leading spaces
|
||||||
|
local w = #q - v -- # significant digits
|
||||||
|
local nv = tostring(#q)
|
||||||
|
-- e.g. compare 123e-6 versus .000123
|
||||||
|
if w + 2 + #nv < 1 + #q then
|
||||||
|
y = sub(q, -w).."e-"..nv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else -- scientific number
|
||||||
|
local sig, ex = match(z, "^([^eE]+)[eE]([%+%-]?%d+)$")
|
||||||
|
ex = tonumber(ex)
|
||||||
|
-- if got ".", shift out fractional portion of significand
|
||||||
|
local p, q = match(sig, "^(%d*)%.(%d*)$")
|
||||||
|
if p then
|
||||||
|
ex = ex - #q
|
||||||
|
sig = p..q
|
||||||
|
end
|
||||||
|
if tonumber(sig) == 0 then
|
||||||
|
y = ".0" -- basic float zero
|
||||||
|
else
|
||||||
|
local v = #match(sig, "^0*") -- remove leading zeros
|
||||||
|
sig = sub(sig, v + 1)
|
||||||
|
v = #match(sig, "0*$") -- shift out trailing zeros
|
||||||
|
if v > 0 then
|
||||||
|
sig = sub(sig, 1, #sig - v)
|
||||||
|
ex = ex + v
|
||||||
|
end
|
||||||
|
-- examine exponent and determine which format is best
|
||||||
|
local nex = tostring(ex)
|
||||||
|
if ex >= 0 and (ex <= 1 + #nex) then -- a float
|
||||||
|
y = sig..rep("0", ex).."."
|
||||||
|
elseif ex < 0 and (ex >= -#sig) then -- fraction, e.g. .123
|
||||||
|
v = #sig + ex
|
||||||
|
y = sub(sig, 1, v).."."..sub(sig, v + 1)
|
||||||
|
elseif ex < 0 and (#nex >= -ex - #sig) then
|
||||||
|
-- e.g. compare 1234e-5 versus .01234
|
||||||
|
-- gives: #sig + 1 + #nex >= 1 + (-ex - #sig) + #sig
|
||||||
|
-- -> #nex >= -ex - #sig
|
||||||
|
v = -ex - #sig
|
||||||
|
y = "."..rep("0", v)..sig
|
||||||
|
else -- non-canonical scientific representation
|
||||||
|
y = sig.."e"..ex
|
||||||
|
end
|
||||||
|
end--if sig
|
||||||
|
end
|
||||||
|
|
||||||
|
if y and y ~= sinfos[i] then
|
||||||
|
if opt_details then
|
||||||
|
print("<number> (line "..stoklns[i]..") "..sinfos[i].." -> "..y)
|
||||||
|
opt_details = opt_details + 1
|
||||||
|
end
|
||||||
|
sinfos[i] = y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Does string optimization.
|
||||||
|
--
|
||||||
|
-- Note: It works on well-formed strings only!
|
||||||
|
--
|
||||||
|
-- Optimizations on characters can be summarized as follows:
|
||||||
|
--
|
||||||
|
-- \a\b\f\n\r\t\v -- no change
|
||||||
|
-- \\ -- no change
|
||||||
|
-- \"\' -- depends on delim, other can remove \
|
||||||
|
-- \[\] -- remove \
|
||||||
|
-- \<char> -- general escape, remove \ (Lua 5.1 only)
|
||||||
|
-- \<eol> -- normalize the EOL only
|
||||||
|
-- \ddd -- if \a\b\f\n\r\t\v, change to latter
|
||||||
|
-- if other < ascii 32, keep ddd but zap leading zeros
|
||||||
|
-- but cannot have following digits
|
||||||
|
-- if >= ascii 32, translate it into the literal, then also
|
||||||
|
-- do escapes for \\,\",\' cases
|
||||||
|
-- <other> -- no change
|
||||||
|
--
|
||||||
|
-- Switch delimiters if string becomes shorter.
|
||||||
|
--
|
||||||
|
-- @tparam int I
|
||||||
|
local function do_string(I)
|
||||||
|
local info = sinfos[I]
|
||||||
|
local delim = sub(info, 1, 1) -- delimiter used
|
||||||
|
local ndelim = (delim == "'") and '"' or "'" -- opposite " <-> '
|
||||||
|
local z = sub(info, 2, -2) -- actual string
|
||||||
|
local i = 1
|
||||||
|
local c_delim, c_ndelim = 0, 0 -- "/' counts
|
||||||
|
|
||||||
|
while i <= #z do
|
||||||
|
local c = sub(z, i, i)
|
||||||
|
|
||||||
|
if c == "\\" then -- escaped stuff
|
||||||
|
local j = i + 1
|
||||||
|
local d = sub(z, j, j)
|
||||||
|
local p = find("abfnrtv\\\n\r\"\'0123456789", d, 1, true)
|
||||||
|
|
||||||
|
if not p then -- \<char> -- remove \ (Lua 5.1 only)
|
||||||
|
z = sub(z, 1, i - 1)..sub(z, j)
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
elseif p <= 8 then -- \a\b\f\n\r\t\v\\
|
||||||
|
i = i + 2 -- no change
|
||||||
|
|
||||||
|
elseif p <= 10 then -- \<eol> -- normalize EOL
|
||||||
|
local eol = sub(z, j, j + 1)
|
||||||
|
if eol == "\r\n" or eol == "\n\r" then
|
||||||
|
z = sub(z, 1, i).."\n"..sub(z, j + 2)
|
||||||
|
elseif p == 10 then -- \r case
|
||||||
|
z = sub(z, 1, i).."\n"..sub(z, j + 1)
|
||||||
|
end
|
||||||
|
i = i + 2
|
||||||
|
|
||||||
|
elseif p <= 12 then -- \"\' -- remove \ for ndelim
|
||||||
|
if d == delim then
|
||||||
|
c_delim = c_delim + 1
|
||||||
|
i = i + 2
|
||||||
|
else
|
||||||
|
c_ndelim = c_ndelim + 1
|
||||||
|
z = sub(z, 1, i - 1)..sub(z, j)
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
else -- \ddd -- various steps
|
||||||
|
local s = match(z, "^(%d%d?%d?)", j)
|
||||||
|
j = i + 1 + #s -- skip to location
|
||||||
|
local cv = tonumber(s)
|
||||||
|
local cc = char(cv)
|
||||||
|
p = find("\a\b\f\n\r\t\v", cc, 1, true)
|
||||||
|
if p then -- special escapes
|
||||||
|
s = "\\"..sub("abfnrtv", p, p)
|
||||||
|
elseif cv < 32 then -- normalized \ddd
|
||||||
|
if match(sub(z, j, j), "%d") then
|
||||||
|
-- if a digit follows, \ddd cannot be shortened
|
||||||
|
s = "\\"..s
|
||||||
|
else
|
||||||
|
s = "\\"..cv
|
||||||
|
end
|
||||||
|
elseif cc == delim then -- \<delim>
|
||||||
|
s = "\\"..cc
|
||||||
|
c_delim = c_delim + 1
|
||||||
|
elseif cc == "\\" then -- \\
|
||||||
|
s = "\\\\"
|
||||||
|
else -- literal character
|
||||||
|
s = cc
|
||||||
|
if cc == ndelim then
|
||||||
|
c_ndelim = c_ndelim + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
z = sub(z, 1, i - 1)..s..sub(z, j)
|
||||||
|
i = i + #s
|
||||||
|
|
||||||
|
end--if p
|
||||||
|
|
||||||
|
else-- c ~= "\\" -- <other> -- no change
|
||||||
|
i = i + 1
|
||||||
|
if c == ndelim then -- count ndelim, for switching delimiters
|
||||||
|
c_ndelim = c_ndelim + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
end--if c
|
||||||
|
end--while
|
||||||
|
|
||||||
|
-- Switching delimiters, a long-winded derivation:
|
||||||
|
-- (1) delim takes 2+2*c_delim bytes, ndelim takes c_ndelim bytes
|
||||||
|
-- (2) delim becomes c_delim bytes, ndelim becomes 2+2*c_ndelim bytes
|
||||||
|
-- simplifying the condition (1)>(2) --> c_delim > c_ndelim
|
||||||
|
if c_delim > c_ndelim then
|
||||||
|
i = 1
|
||||||
|
while i <= #z do
|
||||||
|
local p, _, r = find(z, "([\'\"])", i)
|
||||||
|
if not p then break end
|
||||||
|
if r == delim then -- \<delim> -> <delim>
|
||||||
|
z = sub(z, 1, p - 2)..sub(z, p)
|
||||||
|
i = p
|
||||||
|
else-- r == ndelim -- <ndelim> -> \<ndelim>
|
||||||
|
z = sub(z, 1, p - 1).."\\"..sub(z, p)
|
||||||
|
i = p + 2
|
||||||
|
end
|
||||||
|
end--while
|
||||||
|
delim = ndelim -- actually change delimiters
|
||||||
|
end
|
||||||
|
|
||||||
|
z = delim..z..delim
|
||||||
|
if z ~= sinfos[I] then
|
||||||
|
if opt_details then
|
||||||
|
print("<string> (line "..stoklns[I]..") "..sinfos[I].." -> "..z)
|
||||||
|
opt_details = opt_details + 1
|
||||||
|
end
|
||||||
|
sinfos[I] = z
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Does long string optimization.
|
||||||
|
--
|
||||||
|
-- * remove first optional newline
|
||||||
|
-- * normalize embedded newlines
|
||||||
|
-- * reduce '=' separators in delimiters if possible
|
||||||
|
--
|
||||||
|
-- Note: warning flagged if trailing whitespace found, not trimmed.
|
||||||
|
--
|
||||||
|
-- @tparam int I
|
||||||
|
local function do_lstring(I)
|
||||||
|
local info = sinfos[I]
|
||||||
|
local delim1 = match(info, "^%[=*%[") -- cut out delimiters
|
||||||
|
local sep = #delim1
|
||||||
|
local delim2 = sub(info, -sep, -1)
|
||||||
|
local z = sub(info, sep + 1, -(sep + 1)) -- lstring without delims
|
||||||
|
local y = ""
|
||||||
|
local i = 1
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
|
||||||
|
-- deal with a single line
|
||||||
|
local ln
|
||||||
|
if not p then
|
||||||
|
ln = sub(z, i)
|
||||||
|
elseif p >= i then
|
||||||
|
ln = sub(z, i, p - 1)
|
||||||
|
end
|
||||||
|
if ln ~= "" then
|
||||||
|
-- flag a warning if there are trailing spaces, won't optimize!
|
||||||
|
if match(ln, "%s+$") then
|
||||||
|
M.warn.LSTRING = "trailing whitespace in long string near line "..stoklns[I]
|
||||||
|
end
|
||||||
|
y = y..ln
|
||||||
|
end
|
||||||
|
if not p then -- done if no more EOLs
|
||||||
|
break
|
||||||
|
end
|
||||||
|
-- deal with line endings, normalize them
|
||||||
|
i = p + 1
|
||||||
|
if p then
|
||||||
|
if #s > 0 and r ~= s then -- skip CRLF or LFCR
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
-- skip first newline, which can be safely deleted
|
||||||
|
if not(i == 1 and i == p) then
|
||||||
|
y = y.."\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end--while
|
||||||
|
|
||||||
|
-- handle possible deletion of one or more '=' separators
|
||||||
|
if sep >= 3 then
|
||||||
|
local chk, okay = sep - 1
|
||||||
|
-- loop to test ending delimiter with less of '=' down to zero
|
||||||
|
while chk >= 2 do
|
||||||
|
local delim = "%]"..rep("=", chk - 2).."%]"
|
||||||
|
if not match(y.."]", delim) then okay = chk end
|
||||||
|
chk = chk - 1
|
||||||
|
end
|
||||||
|
if okay then -- change delimiters
|
||||||
|
sep = rep("=", okay - 2)
|
||||||
|
delim1, delim2 = "["..sep.."[", "]"..sep.."]"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sinfos[I] = delim1..y..delim2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Does long comment optimization.
|
||||||
|
--
|
||||||
|
-- * trim trailing whitespace
|
||||||
|
-- * normalize embedded newlines
|
||||||
|
-- * reduce '=' separators in delimiters if possible
|
||||||
|
--
|
||||||
|
-- Note: It does not remove first optional newline.
|
||||||
|
--
|
||||||
|
-- @tparam int I
|
||||||
|
local function do_lcomment(I)
|
||||||
|
local info = sinfos[I]
|
||||||
|
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
|
||||||
|
local sep = #delim1
|
||||||
|
local delim2 = sub(info, -(sep - 2), -1)
|
||||||
|
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
|
||||||
|
local y = ""
|
||||||
|
local i = 1
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
|
||||||
|
-- deal with a single line, extract and check trailing whitespace
|
||||||
|
local ln
|
||||||
|
if not p then
|
||||||
|
ln = sub(z, i)
|
||||||
|
elseif p >= i then
|
||||||
|
ln = sub(z, i, p - 1)
|
||||||
|
end
|
||||||
|
if ln ~= "" then
|
||||||
|
-- trim trailing whitespace if non-empty line
|
||||||
|
local ws = match(ln, "%s*$")
|
||||||
|
if #ws > 0 then ln = sub(ln, 1, -(ws + 1)) end
|
||||||
|
y = y..ln
|
||||||
|
end
|
||||||
|
if not p then -- done if no more EOLs
|
||||||
|
break
|
||||||
|
end
|
||||||
|
-- deal with line endings, normalize them
|
||||||
|
i = p + 1
|
||||||
|
if p then
|
||||||
|
if #s > 0 and r ~= s then -- skip CRLF or LFCR
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
y = y.."\n"
|
||||||
|
end
|
||||||
|
end--while
|
||||||
|
|
||||||
|
-- handle possible deletion of one or more '=' separators
|
||||||
|
sep = sep - 2
|
||||||
|
if sep >= 3 then
|
||||||
|
local chk, okay = sep - 1
|
||||||
|
-- loop to test ending delimiter with less of '=' down to zero
|
||||||
|
while chk >= 2 do
|
||||||
|
local delim = "%]"..rep("=", chk - 2).."%]"
|
||||||
|
if not match(y, delim) then okay = chk end
|
||||||
|
chk = chk - 1
|
||||||
|
end
|
||||||
|
if okay then -- change delimiters
|
||||||
|
sep = rep("=", okay - 2)
|
||||||
|
delim1, delim2 = "--["..sep.."[", "]"..sep.."]"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sinfos[I] = delim1..y..delim2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Does short comment optimization.
|
||||||
|
--
|
||||||
|
-- * trim trailing whitespace
|
||||||
|
--
|
||||||
|
-- @tparam int i
|
||||||
|
local function do_comment(i)
|
||||||
|
local info = sinfos[i]
|
||||||
|
local ws = match(info, "%s*$") -- just look from end of string
|
||||||
|
if #ws > 0 then
|
||||||
|
info = sub(info, 1, -(ws + 1)) -- trim trailing whitespace
|
||||||
|
end
|
||||||
|
sinfos[i] = info
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns true if string found in long comment.
|
||||||
|
--
|
||||||
|
-- This is a feature to keep copyright or license texts.
|
||||||
|
--
|
||||||
|
-- @tparam bool opt_keep
|
||||||
|
-- @tparam string info
|
||||||
|
-- @treturn bool
|
||||||
|
local function keep_lcomment(opt_keep, info)
|
||||||
|
if not opt_keep then return false end -- option not set
|
||||||
|
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
|
||||||
|
local sep = #delim1
|
||||||
|
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
|
||||||
|
if find(z, opt_keep, 1, true) then -- try to match
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The main entry point.
|
||||||
|
--
|
||||||
|
-- * currently, lexer processing has 2 passes
|
||||||
|
-- * processing is done on a line-oriented basis, which is easier to
|
||||||
|
-- grok due to the next point...
|
||||||
|
-- * since there are various options that can be enabled or disabled,
|
||||||
|
-- processing is a little messy or convoluted
|
||||||
|
--
|
||||||
|
-- @tparam {[string]=bool,...} option
|
||||||
|
-- @tparam {string,...} toklist
|
||||||
|
-- @tparam {string,...} semlist
|
||||||
|
-- @tparam {int,...} toklnlist
|
||||||
|
-- @treturn {string,...} toklist
|
||||||
|
-- @treturn {string,...} semlist
|
||||||
|
-- @treturn {int,...} toklnlist
|
||||||
|
function M.optimize(option, toklist, semlist, toklnlist)
|
||||||
|
-- Set option flags.
|
||||||
|
local opt_comments = option["opt-comments"]
|
||||||
|
local opt_whitespace = option["opt-whitespace"]
|
||||||
|
local opt_emptylines = option["opt-emptylines"]
|
||||||
|
local opt_eols = option["opt-eols"]
|
||||||
|
local opt_strings = option["opt-strings"]
|
||||||
|
local opt_numbers = option["opt-numbers"]
|
||||||
|
local opt_x = option["opt-experimental"]
|
||||||
|
local opt_keep = option.KEEP
|
||||||
|
opt_details = option.DETAILS and 0 -- upvalues for details display
|
||||||
|
print = M.print or _G.print
|
||||||
|
if opt_eols then -- forced settings, otherwise won't work properly
|
||||||
|
opt_comments = true
|
||||||
|
opt_whitespace = true
|
||||||
|
opt_emptylines = true
|
||||||
|
elseif opt_x then
|
||||||
|
opt_whitespace = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Variable initialization.
|
||||||
|
stoks, sinfos, stoklns -- set source lists
|
||||||
|
= toklist, semlist, toklnlist
|
||||||
|
local i = 1 -- token position
|
||||||
|
local tok, info -- current token
|
||||||
|
local prev -- position of last grammar token
|
||||||
|
-- on same line (for TK_SPACE stuff)
|
||||||
|
|
||||||
|
-- Changes a token, info pair.
|
||||||
|
local function settoken(tok, info, I) --luacheck: ignore 431
|
||||||
|
I = I or i
|
||||||
|
stoks[I] = tok or ""
|
||||||
|
sinfos[I] = info or ""
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Experimental optimization for ';' operator.
|
||||||
|
if opt_x then
|
||||||
|
while true do
|
||||||
|
tok, info = stoks[i], sinfos[i]
|
||||||
|
if tok == "TK_EOS" then -- end of stream/pass
|
||||||
|
break
|
||||||
|
elseif tok == "TK_OP" and info == ";" then
|
||||||
|
-- ';' operator found, since it is entirely optional, set it
|
||||||
|
-- as a space to let whitespace optimization do the rest
|
||||||
|
settoken("TK_SPACE", " ")
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
repack_tokens()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Processing loop (PASS 1)
|
||||||
|
i = 1
|
||||||
|
while true do
|
||||||
|
tok, info = stoks[i], sinfos[i]
|
||||||
|
|
||||||
|
local atstart = atlinestart(i) -- set line begin flag
|
||||||
|
if atstart then prev = nil end
|
||||||
|
|
||||||
|
if tok == "TK_EOS" then -- end of stream/pass
|
||||||
|
break
|
||||||
|
|
||||||
|
elseif tok == "TK_KEYWORD" or -- keywords, identifiers,
|
||||||
|
tok == "TK_NAME" or -- operators
|
||||||
|
tok == "TK_OP" then
|
||||||
|
-- TK_KEYWORD and TK_OP can't be optimized without a big
|
||||||
|
-- optimization framework; it would be more of an optimizing
|
||||||
|
-- compiler, not a source code compressor
|
||||||
|
-- TK_NAME that are locals needs parser to analyze/optimize
|
||||||
|
prev = i
|
||||||
|
|
||||||
|
elseif tok == "TK_NUMBER" then -- numbers
|
||||||
|
if opt_numbers then
|
||||||
|
do_number(i) -- optimize
|
||||||
|
end
|
||||||
|
prev = i
|
||||||
|
|
||||||
|
elseif tok == "TK_STRING" or -- strings, long strings
|
||||||
|
tok == "TK_LSTRING" then
|
||||||
|
if opt_strings then
|
||||||
|
if tok == "TK_STRING" then
|
||||||
|
do_string(i) -- optimize
|
||||||
|
else
|
||||||
|
do_lstring(i) -- optimize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
prev = i
|
||||||
|
|
||||||
|
elseif tok == "TK_COMMENT" then -- short comments
|
||||||
|
if opt_comments then
|
||||||
|
if i == 1 and sub(info, 1, 1) == "#" then
|
||||||
|
-- keep shbang comment, trim whitespace
|
||||||
|
do_comment(i)
|
||||||
|
else
|
||||||
|
-- safe to delete, as a TK_EOL (or TK_EOS) always follows
|
||||||
|
settoken() -- remove entirely
|
||||||
|
end
|
||||||
|
elseif opt_whitespace then -- trim whitespace only
|
||||||
|
do_comment(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif tok == "TK_LCOMMENT" then -- long comments
|
||||||
|
if keep_lcomment(opt_keep, info) then
|
||||||
|
-- if --keep, we keep a long comment if <msg> is found;
|
||||||
|
-- this is a feature to keep copyright or license texts
|
||||||
|
if opt_whitespace then -- trim whitespace only
|
||||||
|
do_lcomment(i)
|
||||||
|
end
|
||||||
|
prev = i
|
||||||
|
elseif opt_comments then
|
||||||
|
local eols = commenteols(info)
|
||||||
|
|
||||||
|
-- prepare opt_emptylines case first, if a disposable token
|
||||||
|
-- follows, current one is safe to dump, else keep a space;
|
||||||
|
-- it is implied that the operation is safe for '-', because
|
||||||
|
-- current is a TK_LCOMMENT, and must be separate from a '-'
|
||||||
|
if is_faketoken[stoks[i + 1]] then
|
||||||
|
settoken() -- remove entirely
|
||||||
|
tok = ""
|
||||||
|
else
|
||||||
|
settoken("TK_SPACE", " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if there are embedded EOLs to keep and opt_emptylines is
|
||||||
|
-- disabled, then switch the token into one or more EOLs
|
||||||
|
if not opt_emptylines and eols > 0 then
|
||||||
|
settoken("TK_EOL", rep("\n", eols))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if optimizing whitespaces, force reinterpretation of the
|
||||||
|
-- token to give a chance for the space to be optimized away
|
||||||
|
if opt_whitespace and tok ~= "" then
|
||||||
|
i = i - 1 -- to reinterpret
|
||||||
|
end
|
||||||
|
else -- disabled case
|
||||||
|
if opt_whitespace then -- trim whitespace only
|
||||||
|
do_lcomment(i)
|
||||||
|
end
|
||||||
|
prev = i
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif tok == "TK_EOL" then -- line endings
|
||||||
|
if atstart and opt_emptylines then
|
||||||
|
settoken() -- remove entirely
|
||||||
|
elseif info == "\r\n" or info == "\n\r" then
|
||||||
|
-- normalize the rest of the EOLs for CRLF/LFCR only
|
||||||
|
-- (note that TK_LCOMMENT can change into several EOLs)
|
||||||
|
settoken("TK_EOL", "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif tok == "TK_SPACE" then -- whitespace
|
||||||
|
if opt_whitespace then
|
||||||
|
if atstart or atlineend(i) then
|
||||||
|
-- delete leading and trailing whitespace
|
||||||
|
settoken() -- remove entirely
|
||||||
|
else
|
||||||
|
|
||||||
|
-- at this point, since leading whitespace have been removed,
|
||||||
|
-- there should be a either a real token or a TK_LCOMMENT
|
||||||
|
-- prior to hitting this whitespace; the TK_LCOMMENT case
|
||||||
|
-- only happens if opt_comments is disabled; so prev ~= nil
|
||||||
|
local ptok = stoks[prev]
|
||||||
|
if ptok == "TK_LCOMMENT" then
|
||||||
|
-- previous TK_LCOMMENT can abut with anything
|
||||||
|
settoken() -- remove entirely
|
||||||
|
else
|
||||||
|
-- prev must be a grammar token; consecutive TK_SPACE
|
||||||
|
-- tokens is impossible when optimizing whitespace
|
||||||
|
local ntok = stoks[i + 1]
|
||||||
|
if is_faketoken[ntok] then
|
||||||
|
-- handle special case where a '-' cannot abut with
|
||||||
|
-- either a short comment or a long comment
|
||||||
|
if (ntok == "TK_COMMENT" or ntok == "TK_LCOMMENT") and
|
||||||
|
ptok == "TK_OP" and sinfos[prev] == "-" then
|
||||||
|
-- keep token
|
||||||
|
else
|
||||||
|
settoken() -- remove entirely
|
||||||
|
end
|
||||||
|
else--is_realtoken
|
||||||
|
-- check a pair of grammar tokens, if can abut, then
|
||||||
|
-- delete space token entirely, otherwise keep one space
|
||||||
|
local s = checkpair(prev, i + 1)
|
||||||
|
if s == "" then
|
||||||
|
settoken() -- remove entirely
|
||||||
|
else
|
||||||
|
settoken("TK_SPACE", " ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
error("unidentified token encountered")
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end--while
|
||||||
|
repack_tokens()
|
||||||
|
|
||||||
|
-- Processing loop (PASS 2)
|
||||||
|
if opt_eols then
|
||||||
|
i = 1
|
||||||
|
-- Aggressive EOL removal only works with most non-grammar tokens
|
||||||
|
-- optimized away because it is a rather simple scheme -- basically
|
||||||
|
-- it just checks 'real' token pairs around EOLs.
|
||||||
|
if stoks[1] == "TK_COMMENT" then
|
||||||
|
-- first comment still existing must be shbang, skip whole line
|
||||||
|
i = 3
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
tok = stoks[i]
|
||||||
|
|
||||||
|
if tok == "TK_EOS" then -- end of stream/pass
|
||||||
|
break
|
||||||
|
|
||||||
|
elseif tok == "TK_EOL" then -- consider each TK_EOL
|
||||||
|
local t1, t2 = stoks[i - 1], stoks[i + 1]
|
||||||
|
if is_realtoken[t1] and is_realtoken[t2] then -- sanity check
|
||||||
|
local s = checkpair(i - 1, i + 1)
|
||||||
|
if s == "" or t2 == "TK_EOS" then
|
||||||
|
settoken() -- remove entirely
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end--if tok
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end--while
|
||||||
|
repack_tokens()
|
||||||
|
end
|
||||||
|
|
||||||
|
if opt_details and opt_details > 0 then print() end -- spacing
|
||||||
|
return stoks, sinfos, stoklns
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
644
tools/luasrcdiet/optparser.lua
Normal file
644
tools/luasrcdiet/optparser.lua
Normal file
@ -0,0 +1,644 @@
|
|||||||
|
---------
|
||||||
|
-- This module does parser-based optimizations.
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * The processing load is quite significant, but since this is an
|
||||||
|
-- off-line text processor, I believe we can wait a few seconds.
|
||||||
|
-- * TODO: Might process "local a,a,a" wrongly... need tests!
|
||||||
|
-- * TODO: Remove position handling if overlapped locals (rem < 0)
|
||||||
|
-- needs more study, to check behaviour.
|
||||||
|
-- * TODO: There are probably better ways to do allocation, e.g. by
|
||||||
|
-- choosing better methods to sort and pick locals...
|
||||||
|
-- * TODO: We don't need 53*63 two-letter identifiers; we can make
|
||||||
|
-- do with significantly less depending on how many that are really
|
||||||
|
-- needed and improve entropy; e.g. 13 needed -> choose 4*4 instead.
|
||||||
|
----
|
||||||
|
local byte = string.byte
|
||||||
|
local char = string.char
|
||||||
|
local concat = table.concat
|
||||||
|
local fmt = string.format
|
||||||
|
local pairs = pairs
|
||||||
|
local rep = string.rep
|
||||||
|
local sort = table.sort
|
||||||
|
local sub = string.sub
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
-- Letter frequencies for reducing symbol entropy (fixed version)
|
||||||
|
-- * Might help a wee bit when the output file is compressed
|
||||||
|
-- * See Wikipedia: http://en.wikipedia.org/wiki/Letter_frequencies
|
||||||
|
-- * We use letter frequencies according to a Linotype keyboard, plus
|
||||||
|
-- the underscore, and both lower case and upper case letters.
|
||||||
|
-- * The arrangement below (LC, underscore, %d, UC) is arbitrary.
|
||||||
|
-- * This is certainly not optimal, but is quick-and-dirty and the
|
||||||
|
-- process has no significant overhead
|
||||||
|
local LETTERS = "etaoinshrdlucmfwypvbgkqjxz_ETAOINSHRDLUCMFWYPVBGKQJXZ"
|
||||||
|
local ALPHANUM = "etaoinshrdlucmfwypvbgkqjxz_0123456789ETAOINSHRDLUCMFWYPVBGKQJXZ"
|
||||||
|
|
||||||
|
-- Names or identifiers that must be skipped.
|
||||||
|
-- (The first two lines are for keywords.)
|
||||||
|
local SKIP_NAME = {}
|
||||||
|
for v in ([[
|
||||||
|
and break do else elseif end false for function if in
|
||||||
|
local nil not or repeat return then true until while
|
||||||
|
self _ENV]]):gmatch("%S+") do
|
||||||
|
SKIP_NAME[v] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local toklist, seminfolist, -- token lists (lexer output)
|
||||||
|
tokpar, seminfopar, xrefpar, -- token lists (parser output)
|
||||||
|
globalinfo, localinfo, -- variable information tables
|
||||||
|
statinfo, -- statment type table
|
||||||
|
globaluniq, localuniq, -- unique name tables
|
||||||
|
var_new, -- index of new variable names
|
||||||
|
varlist -- list of output variables
|
||||||
|
|
||||||
|
--- Preprocesses information table to get lists of unique names.
|
||||||
|
--
|
||||||
|
-- @tparam {table,...} infotable
|
||||||
|
-- @treturn table
|
||||||
|
local function preprocess(infotable)
|
||||||
|
local uniqtable = {}
|
||||||
|
for i = 1, #infotable do -- enumerate info table
|
||||||
|
local obj = infotable[i]
|
||||||
|
local name = obj.name
|
||||||
|
|
||||||
|
if not uniqtable[name] then -- not found, start an entry
|
||||||
|
uniqtable[name] = {
|
||||||
|
decl = 0, token = 0, size = 0,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local uniq = uniqtable[name] -- count declarations, tokens, size
|
||||||
|
uniq.decl = uniq.decl + 1
|
||||||
|
local xref = obj.xref
|
||||||
|
local xcount = #xref
|
||||||
|
uniq.token = uniq.token + xcount
|
||||||
|
uniq.size = uniq.size + xcount * #name
|
||||||
|
|
||||||
|
if obj.decl then -- if local table, create first,last pairs
|
||||||
|
obj.id = i
|
||||||
|
obj.xcount = xcount
|
||||||
|
if xcount > 1 then -- if ==1, means local never accessed
|
||||||
|
obj.first = xref[2]
|
||||||
|
obj.last = xref[xcount]
|
||||||
|
end
|
||||||
|
|
||||||
|
else -- if global table, add a back ref
|
||||||
|
uniq.id = i
|
||||||
|
end
|
||||||
|
|
||||||
|
end--for
|
||||||
|
return uniqtable
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculates actual symbol frequencies, in order to reduce entropy.
|
||||||
|
--
|
||||||
|
-- * This may help further reduce the size of compressed sources.
|
||||||
|
-- * Note that since parsing optimizations is put before lexing
|
||||||
|
-- optimizations, the frequency table is not exact!
|
||||||
|
-- * Yes, this will miss --keep block comments too...
|
||||||
|
--
|
||||||
|
-- @tparam table option
|
||||||
|
local function recalc_for_entropy(option)
|
||||||
|
-- table of token classes to accept in calculating symbol frequency
|
||||||
|
local ACCEPT = {
|
||||||
|
TK_KEYWORD = true, TK_NAME = true, TK_NUMBER = true,
|
||||||
|
TK_STRING = true, TK_LSTRING = true,
|
||||||
|
}
|
||||||
|
if not option["opt-comments"] then
|
||||||
|
ACCEPT.TK_COMMENT = true
|
||||||
|
ACCEPT.TK_LCOMMENT = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a new table and remove any original locals by filtering.
|
||||||
|
local filtered = {}
|
||||||
|
for i = 1, #toklist do
|
||||||
|
filtered[i] = seminfolist[i]
|
||||||
|
end
|
||||||
|
for i = 1, #localinfo do -- enumerate local info table
|
||||||
|
local obj = localinfo[i]
|
||||||
|
local xref = obj.xref
|
||||||
|
for j = 1, obj.xcount do
|
||||||
|
local p = xref[j]
|
||||||
|
filtered[p] = "" -- remove locals
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local freq = {} -- reset symbol frequency table
|
||||||
|
for i = 0, 255 do freq[i] = 0 end
|
||||||
|
for i = 1, #toklist do -- gather symbol frequency
|
||||||
|
local tok, info = toklist[i], filtered[i]
|
||||||
|
if ACCEPT[tok] then
|
||||||
|
for j = 1, #info do
|
||||||
|
local c = byte(info, j)
|
||||||
|
freq[c] = freq[c] + 1
|
||||||
|
end
|
||||||
|
end--if
|
||||||
|
end--for
|
||||||
|
|
||||||
|
-- Re-sorts symbols according to actual frequencies.
|
||||||
|
--
|
||||||
|
-- @tparam string symbols
|
||||||
|
-- @treturn string
|
||||||
|
local function resort(symbols)
|
||||||
|
local symlist = {}
|
||||||
|
for i = 1, #symbols do -- prepare table to sort
|
||||||
|
local c = byte(symbols, i)
|
||||||
|
symlist[i] = { c = c, freq = freq[c], }
|
||||||
|
end
|
||||||
|
sort(symlist, function(v1, v2) -- sort selected symbols
|
||||||
|
return v1.freq > v2.freq
|
||||||
|
end)
|
||||||
|
local charlist = {} -- reconstitute the string
|
||||||
|
for i = 1, #symlist do
|
||||||
|
charlist[i] = char(symlist[i].c)
|
||||||
|
end
|
||||||
|
return concat(charlist)
|
||||||
|
end
|
||||||
|
|
||||||
|
LETTERS = resort(LETTERS) -- change letter arrangement
|
||||||
|
ALPHANUM = resort(ALPHANUM)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns a string containing a new local variable name to use, and
|
||||||
|
-- a flag indicating whether it collides with a global variable.
|
||||||
|
--
|
||||||
|
-- Trapping keywords and other names like 'self' is done elsewhere.
|
||||||
|
--
|
||||||
|
-- @treturn string A new local variable name.
|
||||||
|
-- @treturn bool Whether the name collides with a global variable.
|
||||||
|
local function new_var_name()
|
||||||
|
local var
|
||||||
|
local cletters, calphanum = #LETTERS, #ALPHANUM
|
||||||
|
local v = var_new
|
||||||
|
if v < cletters then -- single char
|
||||||
|
v = v + 1
|
||||||
|
var = sub(LETTERS, v, v)
|
||||||
|
else -- longer names
|
||||||
|
local range, sz = cletters, 1 -- calculate # chars fit
|
||||||
|
repeat
|
||||||
|
v = v - range
|
||||||
|
range = range * calphanum
|
||||||
|
sz = sz + 1
|
||||||
|
until range > v
|
||||||
|
local n = v % cletters -- left side cycles faster
|
||||||
|
v = (v - n) / cletters -- do first char first
|
||||||
|
n = n + 1
|
||||||
|
var = sub(LETTERS, n, n)
|
||||||
|
while sz > 1 do
|
||||||
|
local m = v % calphanum
|
||||||
|
v = (v - m) / calphanum
|
||||||
|
m = m + 1
|
||||||
|
var = var..sub(ALPHANUM, m, m)
|
||||||
|
sz = sz - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
var_new = var_new + 1
|
||||||
|
return var, globaluniq[var] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculates and prints some statistics.
|
||||||
|
--
|
||||||
|
-- Note: probably better in main source, put here for now.
|
||||||
|
--
|
||||||
|
-- @tparam table globaluniq
|
||||||
|
-- @tparam table localuniq
|
||||||
|
-- @tparam table afteruniq
|
||||||
|
-- @tparam table option
|
||||||
|
local function stats_summary(globaluniq, localuniq, afteruniq, option) --luacheck: ignore 431
|
||||||
|
local print = M.print or print
|
||||||
|
local opt_details = option.DETAILS
|
||||||
|
if option.QUIET then return end
|
||||||
|
|
||||||
|
local uniq_g , uniq_li, uniq_lo = 0, 0, 0
|
||||||
|
local decl_g, decl_li, decl_lo = 0, 0, 0
|
||||||
|
local token_g, token_li, token_lo = 0, 0, 0
|
||||||
|
local size_g, size_li, size_lo = 0, 0, 0
|
||||||
|
|
||||||
|
local function avg(c, l) -- safe average function
|
||||||
|
if c == 0 then return 0 end
|
||||||
|
return l / c
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Collect statistics (Note: globals do not have declarations!)
|
||||||
|
for _, uniq in pairs(globaluniq) do
|
||||||
|
uniq_g = uniq_g + 1
|
||||||
|
token_g = token_g + uniq.token
|
||||||
|
size_g = size_g + uniq.size
|
||||||
|
end
|
||||||
|
for _, uniq in pairs(localuniq) do
|
||||||
|
uniq_li = uniq_li + 1
|
||||||
|
decl_li = decl_li + uniq.decl
|
||||||
|
token_li = token_li + uniq.token
|
||||||
|
size_li = size_li + uniq.size
|
||||||
|
end
|
||||||
|
for _, uniq in pairs(afteruniq) do
|
||||||
|
uniq_lo = uniq_lo + 1
|
||||||
|
decl_lo = decl_lo + uniq.decl
|
||||||
|
token_lo = token_lo + uniq.token
|
||||||
|
size_lo = size_lo + uniq.size
|
||||||
|
end
|
||||||
|
local uniq_ti = uniq_g + uniq_li
|
||||||
|
local decl_ti = decl_g + decl_li
|
||||||
|
local token_ti = token_g + token_li
|
||||||
|
local size_ti = size_g + size_li
|
||||||
|
local uniq_to = uniq_g + uniq_lo
|
||||||
|
local decl_to = decl_g + decl_lo
|
||||||
|
local token_to = token_g + token_lo
|
||||||
|
local size_to = size_g + size_lo
|
||||||
|
|
||||||
|
-- Detailed stats: global list
|
||||||
|
if opt_details then
|
||||||
|
local sorted = {} -- sort table of unique global names by size
|
||||||
|
for name, uniq in pairs(globaluniq) do
|
||||||
|
uniq.name = name
|
||||||
|
sorted[#sorted + 1] = uniq
|
||||||
|
end
|
||||||
|
sort(sorted, function(v1, v2)
|
||||||
|
return v1.size > v2.size
|
||||||
|
end)
|
||||||
|
|
||||||
|
do
|
||||||
|
local tabf1, tabf2 = "%8s%8s%10s %s", "%8d%8d%10.2f %s"
|
||||||
|
local hl = rep("-", 44)
|
||||||
|
print("*** global variable list (sorted by size) ***\n"..hl)
|
||||||
|
print(fmt(tabf1, "Token", "Input", "Input", "Global"))
|
||||||
|
print(fmt(tabf1, "Count", "Bytes", "Average", "Name"))
|
||||||
|
print(hl)
|
||||||
|
for i = 1, #sorted do
|
||||||
|
local uniq = sorted[i]
|
||||||
|
print(fmt(tabf2, uniq.token, uniq.size, avg(uniq.token, uniq.size), uniq.name))
|
||||||
|
end
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, token_g, size_g, avg(token_g, size_g), "TOTAL"))
|
||||||
|
print(hl.."\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Detailed stats: local list
|
||||||
|
do
|
||||||
|
local tabf1, tabf2 = "%8s%8s%8s%10s%8s%10s %s", "%8d%8d%8d%10.2f%8d%10.2f %s"
|
||||||
|
local hl = rep("-", 70)
|
||||||
|
print("*** local variable list (sorted by allocation order) ***\n"..hl)
|
||||||
|
print(fmt(tabf1, "Decl.", "Token", "Input", "Input", "Output", "Output", "Global"))
|
||||||
|
print(fmt(tabf1, "Count", "Count", "Bytes", "Average", "Bytes", "Average", "Name"))
|
||||||
|
print(hl)
|
||||||
|
for i = 1, #varlist do -- iterate according to order assigned
|
||||||
|
local name = varlist[i]
|
||||||
|
local uniq = afteruniq[name]
|
||||||
|
local old_t, old_s = 0, 0
|
||||||
|
for j = 1, #localinfo do -- find corresponding old names and calculate
|
||||||
|
local obj = localinfo[j]
|
||||||
|
if obj.name == name then
|
||||||
|
old_t = old_t + obj.xcount
|
||||||
|
old_s = old_s + obj.xcount * #obj.oldname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print(fmt(tabf2, uniq.decl, uniq.token, old_s, avg(old_t, old_s),
|
||||||
|
uniq.size, avg(uniq.token, uniq.size), name))
|
||||||
|
end
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, decl_lo, token_lo, size_li, avg(token_li, size_li),
|
||||||
|
size_lo, avg(token_lo, size_lo), "TOTAL"))
|
||||||
|
print(hl.."\n")
|
||||||
|
end
|
||||||
|
end--if opt_details
|
||||||
|
|
||||||
|
-- Display output
|
||||||
|
do
|
||||||
|
local tabf1, tabf2 = "%-16s%8s%8s%8s%8s%10s", "%-16s%8d%8d%8d%8d%10.2f"
|
||||||
|
local hl = rep("-", 58)
|
||||||
|
print("*** local variable optimization summary ***\n"..hl)
|
||||||
|
print(fmt(tabf1, "Variable", "Unique", "Decl.", "Token", "Size", "Average"))
|
||||||
|
print(fmt(tabf1, "Types", "Names", "Count", "Count", "Bytes", "Bytes"))
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, "Global", uniq_g, decl_g, token_g, size_g, avg(token_g, size_g)))
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, "Local (in)", uniq_li, decl_li, token_li, size_li, avg(token_li, size_li)))
|
||||||
|
print(fmt(tabf2, "TOTAL (in)", uniq_ti, decl_ti, token_ti, size_ti, avg(token_ti, size_ti)))
|
||||||
|
print(hl)
|
||||||
|
print(fmt(tabf2, "Local (out)", uniq_lo, decl_lo, token_lo, size_lo, avg(token_lo, size_lo)))
|
||||||
|
print(fmt(tabf2, "TOTAL (out)", uniq_to, decl_to, token_to, size_to, avg(token_to, size_to)))
|
||||||
|
print(hl.."\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Does experimental optimization for f("string") statements.
|
||||||
|
--
|
||||||
|
-- It's safe to delete parentheses without adding whitespace, as both
|
||||||
|
-- kinds of strings can abut with anything else.
|
||||||
|
local function optimize_func1()
|
||||||
|
|
||||||
|
local function is_strcall(j) -- find f("string") pattern
|
||||||
|
local t1 = tokpar[j + 1] or ""
|
||||||
|
local t2 = tokpar[j + 2] or ""
|
||||||
|
local t3 = tokpar[j + 3] or ""
|
||||||
|
if t1 == "(" and t2 == "<string>" and t3 == ")" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local del_list = {} -- scan for function pattern,
|
||||||
|
local i = 1 -- tokens to be deleted are marked
|
||||||
|
while i <= #tokpar do
|
||||||
|
local id = statinfo[i]
|
||||||
|
if id == "call" and is_strcall(i) then -- found & mark ()
|
||||||
|
del_list[i + 1] = true -- '('
|
||||||
|
del_list[i + 3] = true -- ')'
|
||||||
|
i = i + 3
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Delete a token and adjust all relevant tables.
|
||||||
|
-- * Currently invalidates globalinfo and localinfo (not updated),
|
||||||
|
-- so any other optimization is done after processing locals
|
||||||
|
-- (of course, we can also lex the source data again...).
|
||||||
|
-- * Faster one-pass token deletion.
|
||||||
|
local del_list2 = {}
|
||||||
|
do
|
||||||
|
local i, dst, idend = 1, 1, #tokpar
|
||||||
|
while dst <= idend do -- process parser tables
|
||||||
|
if del_list[i] then -- found a token to delete?
|
||||||
|
del_list2[xrefpar[i]] = true
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
if i > dst then
|
||||||
|
if i <= idend then -- shift table items lower
|
||||||
|
tokpar[dst] = tokpar[i]
|
||||||
|
seminfopar[dst] = seminfopar[i]
|
||||||
|
xrefpar[dst] = xrefpar[i] - (i - dst)
|
||||||
|
statinfo[dst] = statinfo[i]
|
||||||
|
else -- nil out excess entries
|
||||||
|
tokpar[dst] = nil
|
||||||
|
seminfopar[dst] = nil
|
||||||
|
xrefpar[dst] = nil
|
||||||
|
statinfo[dst] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
dst = dst + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local i, dst, idend = 1, 1, #toklist
|
||||||
|
while dst <= idend do -- process lexer tables
|
||||||
|
if del_list2[i] then -- found a token to delete?
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
if i > dst then
|
||||||
|
if i <= idend then -- shift table items lower
|
||||||
|
toklist[dst] = toklist[i]
|
||||||
|
seminfolist[dst] = seminfolist[i]
|
||||||
|
else -- nil out excess entries
|
||||||
|
toklist[dst] = nil
|
||||||
|
seminfolist[dst] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
dst = dst + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Does local variable optimization.
|
||||||
|
--
|
||||||
|
-- @tparam {[string]=bool,...} option
|
||||||
|
local function optimize_locals(option)
|
||||||
|
var_new = 0 -- reset variable name allocator
|
||||||
|
varlist = {}
|
||||||
|
|
||||||
|
-- Preprocess global/local tables, handle entropy reduction.
|
||||||
|
globaluniq = preprocess(globalinfo)
|
||||||
|
localuniq = preprocess(localinfo)
|
||||||
|
if option["opt-entropy"] then -- for entropy improvement
|
||||||
|
recalc_for_entropy(option)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build initial declared object table, then sort according to
|
||||||
|
-- token count, this might help assign more tokens to more common
|
||||||
|
-- variable names such as 'e' thus possibly reducing entropy.
|
||||||
|
-- * An object knows its localinfo index via its 'id' field.
|
||||||
|
-- * Special handling for "self" and "_ENV" special local (parameter) here.
|
||||||
|
local object = {}
|
||||||
|
for i = 1, #localinfo do
|
||||||
|
object[i] = localinfo[i]
|
||||||
|
end
|
||||||
|
sort(object, function(v1, v2) -- sort largest first
|
||||||
|
return v1.xcount > v2.xcount
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- The special "self" and "_ENV" function parameters must be preserved.
|
||||||
|
-- * The allocator below will never use "self", so it is safe to
|
||||||
|
-- keep those implicit declarations as-is.
|
||||||
|
local temp, j, used_specials = {}, 1, {}
|
||||||
|
for i = 1, #object do
|
||||||
|
local obj = object[i]
|
||||||
|
if not obj.is_special then
|
||||||
|
temp[j] = obj
|
||||||
|
j = j + 1
|
||||||
|
else
|
||||||
|
used_specials[#used_specials + 1] = obj.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
object = temp
|
||||||
|
|
||||||
|
-- A simple first-come first-served heuristic name allocator,
|
||||||
|
-- note that this is in no way optimal...
|
||||||
|
-- * Each object is a local variable declaration plus existence.
|
||||||
|
-- * The aim is to assign short names to as many tokens as possible,
|
||||||
|
-- so the following tries to maximize name reuse.
|
||||||
|
-- * Note that we preserve sort order.
|
||||||
|
local nobject = #object
|
||||||
|
while nobject > 0 do
|
||||||
|
local varname, gcollide
|
||||||
|
repeat
|
||||||
|
varname, gcollide = new_var_name() -- collect a variable name
|
||||||
|
until not SKIP_NAME[varname] -- skip all special names
|
||||||
|
varlist[#varlist + 1] = varname -- keep a list
|
||||||
|
local oleft = nobject
|
||||||
|
|
||||||
|
-- If variable name collides with an existing global, the name
|
||||||
|
-- cannot be used by a local when the name is accessed as a global
|
||||||
|
-- during which the local is alive (between 'act' to 'rem'), so
|
||||||
|
-- we drop objects that collides with the corresponding global.
|
||||||
|
if gcollide then
|
||||||
|
-- find the xref table of the global
|
||||||
|
local gref = globalinfo[globaluniq[varname].id].xref
|
||||||
|
local ngref = #gref
|
||||||
|
-- enumerate for all current objects; all are valid at this point
|
||||||
|
for i = 1, nobject do
|
||||||
|
local obj = object[i]
|
||||||
|
local act, rem = obj.act, obj.rem -- 'live' range of local
|
||||||
|
-- if rem < 0, it is a -id to a local that had the same name
|
||||||
|
-- so follow rem to extend it; does this make sense?
|
||||||
|
while rem < 0 do
|
||||||
|
rem = localinfo[-rem].rem
|
||||||
|
end
|
||||||
|
local drop
|
||||||
|
for j = 1, ngref do
|
||||||
|
local p = gref[j]
|
||||||
|
if p >= act and p <= rem then drop = true end -- in range?
|
||||||
|
end
|
||||||
|
if drop then
|
||||||
|
obj.skip = true
|
||||||
|
oleft = oleft - 1
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
end--if gcollide
|
||||||
|
|
||||||
|
-- Now the first unassigned local (since it's sorted) will be the
|
||||||
|
-- one with the most tokens to rename, so we set this one and then
|
||||||
|
-- eliminate all others that collides, then any locals that left
|
||||||
|
-- can then reuse the same variable name; this is repeated until
|
||||||
|
-- all local declaration that can use this name is assigned.
|
||||||
|
--
|
||||||
|
-- The criteria for local-local reuse/collision is:
|
||||||
|
-- A is the local with a name already assigned
|
||||||
|
-- B is the unassigned local under consideration
|
||||||
|
-- => anytime A is accessed, it cannot be when B is 'live'
|
||||||
|
-- => to speed up things, we have first/last accesses noted
|
||||||
|
while oleft > 0 do
|
||||||
|
local i = 1
|
||||||
|
while object[i].skip do -- scan for first object
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- First object is free for assignment of the variable name
|
||||||
|
-- [first,last] gives the access range for collision checking.
|
||||||
|
oleft = oleft - 1
|
||||||
|
local obja = object[i]
|
||||||
|
i = i + 1
|
||||||
|
obja.newname = varname
|
||||||
|
obja.skip = true
|
||||||
|
obja.done = true
|
||||||
|
local first, last = obja.first, obja.last
|
||||||
|
local xref = obja.xref
|
||||||
|
|
||||||
|
-- Then, scan all the rest and drop those colliding.
|
||||||
|
-- If A was never accessed then it'll never collide with anything
|
||||||
|
-- otherwise trivial skip if:
|
||||||
|
-- * B was activated after A's last access (last < act),
|
||||||
|
-- * B was removed before A's first access (first > rem),
|
||||||
|
-- if not, see detailed skip below...
|
||||||
|
if first and oleft > 0 then -- must have at least 1 access
|
||||||
|
local scanleft = oleft
|
||||||
|
while scanleft > 0 do
|
||||||
|
while object[i].skip do -- next valid object
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
scanleft = scanleft - 1
|
||||||
|
local objb = object[i]
|
||||||
|
i = i + 1
|
||||||
|
local act, rem = objb.act, objb.rem -- live range of B
|
||||||
|
-- if rem < 0, extend range of rem thru' following local
|
||||||
|
while rem < 0 do
|
||||||
|
rem = localinfo[-rem].rem
|
||||||
|
end
|
||||||
|
|
||||||
|
if not(last < act or first > rem) then -- possible collision
|
||||||
|
|
||||||
|
-- B is activated later than A or at the same statement,
|
||||||
|
-- this means for no collision, A cannot be accessed when B
|
||||||
|
-- is alive, since B overrides A (or is a peer).
|
||||||
|
if act >= obja.act then
|
||||||
|
for j = 1, obja.xcount do -- ... then check every access
|
||||||
|
local p = xref[j]
|
||||||
|
if p >= act and p <= rem then -- A accessed when B live!
|
||||||
|
oleft = oleft - 1
|
||||||
|
objb.skip = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
|
||||||
|
-- A is activated later than B, this means for no collision,
|
||||||
|
-- A's access is okay since it overrides B, but B's last
|
||||||
|
-- access need to be earlier than A's activation time.
|
||||||
|
else
|
||||||
|
if objb.last and objb.last >= obja.act then
|
||||||
|
oleft = oleft - 1
|
||||||
|
objb.skip = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if oleft == 0 then break end
|
||||||
|
end
|
||||||
|
end--if first
|
||||||
|
|
||||||
|
end--while
|
||||||
|
|
||||||
|
-- After assigning all possible locals to one variable name, the
|
||||||
|
-- unassigned locals/objects have the skip field reset and the table
|
||||||
|
-- is compacted, to hopefully reduce iteration time.
|
||||||
|
local temp, j = {}, 1
|
||||||
|
for i = 1, nobject do
|
||||||
|
local obj = object[i]
|
||||||
|
if not obj.done then
|
||||||
|
obj.skip = false
|
||||||
|
temp[j] = obj
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
object = temp -- new compacted object table
|
||||||
|
nobject = #object -- objects left to process
|
||||||
|
|
||||||
|
end--while
|
||||||
|
|
||||||
|
-- After assigning all locals with new variable names, we can
|
||||||
|
-- patch in the new names, and reprocess to get 'after' stats.
|
||||||
|
for i = 1, #localinfo do -- enumerate all locals
|
||||||
|
local obj = localinfo[i]
|
||||||
|
local xref = obj.xref
|
||||||
|
if obj.newname then -- if got new name, patch it in
|
||||||
|
for j = 1, obj.xcount do
|
||||||
|
local p = xref[j] -- xrefs indexes the token list
|
||||||
|
seminfolist[p] = obj.newname
|
||||||
|
end
|
||||||
|
obj.name, obj.oldname -- adjust names
|
||||||
|
= obj.newname, obj.name
|
||||||
|
else
|
||||||
|
obj.oldname = obj.name -- for cases like 'self'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Deal with statistics output.
|
||||||
|
for _, name in ipairs(used_specials) do
|
||||||
|
varlist[#varlist + 1] = name
|
||||||
|
end
|
||||||
|
local afteruniq = preprocess(localinfo)
|
||||||
|
stats_summary(globaluniq, localuniq, afteruniq, option)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The main entry point.
|
||||||
|
--
|
||||||
|
-- @tparam table option
|
||||||
|
-- @tparam {string,...} _toklist
|
||||||
|
-- @tparam {string,...} _seminfolist
|
||||||
|
-- @tparam table xinfo
|
||||||
|
function M.optimize(option, _toklist, _seminfolist, xinfo)
|
||||||
|
-- set tables
|
||||||
|
toklist, seminfolist -- from lexer
|
||||||
|
= _toklist, _seminfolist
|
||||||
|
tokpar, seminfopar, xrefpar -- from parser
|
||||||
|
= xinfo.toklist, xinfo.seminfolist, xinfo.xreflist
|
||||||
|
globalinfo, localinfo, statinfo -- from parser
|
||||||
|
= xinfo.globalinfo, xinfo.localinfo, xinfo.statinfo
|
||||||
|
|
||||||
|
-- Optimize locals.
|
||||||
|
if option["opt-locals"] then
|
||||||
|
optimize_locals(option)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Other optimizations.
|
||||||
|
if option["opt-experimental"] then -- experimental
|
||||||
|
optimize_func1()
|
||||||
|
-- WARNING globalinfo and localinfo now invalidated!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
90
tools/luasrcdiet/plugin/example.lua
Normal file
90
tools/luasrcdiet/plugin/example.lua
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
---------
|
||||||
|
-- Example of a plugin for LuaSrcDiet.
|
||||||
|
--
|
||||||
|
-- WARNING: highly experimental! interface liable to change
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * Any function can be omitted and LuaSrcDiet won't call it.
|
||||||
|
-- * The functions are:
|
||||||
|
-- (1) init(_option, _srcfl, _destfl)
|
||||||
|
-- (2) post_load(z) can return z
|
||||||
|
-- (3) post_lex(toklist, seminfolist, toklnlist)
|
||||||
|
-- (4) post_parse(globalinfo, localinfo)
|
||||||
|
-- (5) post_optparse()
|
||||||
|
-- (6) post_optlex(toklist, seminfolist, toklnlist)
|
||||||
|
-- * Older tables can be copied and kept in the plugin and used later.
|
||||||
|
-- * If you modify 'option', remember that LuaSrcDiet might be
|
||||||
|
-- processing more than one file.
|
||||||
|
-- * Arrangement of the functions is not final!
|
||||||
|
-- * TODO: can't process additional options from command line yet
|
||||||
|
----
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local option -- local reference to list of options
|
||||||
|
local srcfl, destfl -- filenames
|
||||||
|
local old_quiet
|
||||||
|
|
||||||
|
local function print(...) -- handle quiet option
|
||||||
|
if option.QUIET then return end
|
||||||
|
_G.print(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Initialization.
|
||||||
|
--
|
||||||
|
-- @tparam {[string]=bool,...} _option
|
||||||
|
-- @tparam string _srcfl Path of the source file.
|
||||||
|
-- @tparam string _destfl Path of the destination file.
|
||||||
|
function M.init(_option, _srcfl, _destfl)
|
||||||
|
option = _option
|
||||||
|
srcfl, destfl = _srcfl, _destfl
|
||||||
|
-- plugin can impose its own option starting from here
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Message display, post-load processing, can return z.
|
||||||
|
function M.post_load(z)
|
||||||
|
-- this message will print after the LuaSrcDiet title message
|
||||||
|
print([[
|
||||||
|
Example plugin module for LuaSrcDiet
|
||||||
|
]])
|
||||||
|
print("Example: source file name is '"..srcfl.."'")
|
||||||
|
print("Example: destination file name is '"..destfl.."'")
|
||||||
|
print("Example: the size of the source file is "..#z.." bytes")
|
||||||
|
-- returning z is optional; this allows optional replacement of
|
||||||
|
-- the source data prior to lexing
|
||||||
|
return z
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Post-lexing processing, can work on lexer table output.
|
||||||
|
function M.post_lex(toklist, seminfolist, toklnlist) --luacheck: ignore
|
||||||
|
print("Example: the number of lexed elements is "..#toklist)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Post-parsing processing, gives globalinfo, localinfo.
|
||||||
|
function M.post_parse(globalinfo, localinfo)
|
||||||
|
print("Example: size of globalinfo is "..#globalinfo)
|
||||||
|
print("Example: size of localinfo is "..#localinfo)
|
||||||
|
old_quiet = option.QUIET
|
||||||
|
option.QUIET = true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Post-parser optimization processing, can get tables from elsewhere.
|
||||||
|
function M.post_optparse()
|
||||||
|
option.QUIET = old_quiet
|
||||||
|
print("Example: pretend to do post-optparse")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Post-lexer optimization processing, can get tables from elsewhere.
|
||||||
|
function M.post_optlex(toklist, seminfolist, toklnlist) --luacheck: ignore
|
||||||
|
print("Example: pretend to do post-optlex")
|
||||||
|
-- restore old settings, other file might need original settings
|
||||||
|
option.QUIET = old_quiet
|
||||||
|
-- option.EXIT can be set at the end of any post_* function to stop
|
||||||
|
-- further processing and exit for the current file being worked on
|
||||||
|
-- in this case, final stats printout is disabled and the output will
|
||||||
|
-- not be written to the destination file
|
||||||
|
option.EXIT = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
177
tools/luasrcdiet/plugin/html.lua
Normal file
177
tools/luasrcdiet/plugin/html.lua
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
---------
|
||||||
|
-- Turns Lua 5.1 source code into HTML files.
|
||||||
|
--
|
||||||
|
-- WARNING: highly experimental! interface liable to change
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * This HTML highlighter marks globals brightly so that their usage
|
||||||
|
-- can be manually optimized.
|
||||||
|
-- * Either uses a .html extension for output files or it follows the
|
||||||
|
-- -o <filespec> option.
|
||||||
|
-- * The HTML style tries to follow that of the Lua wiki.
|
||||||
|
----
|
||||||
|
local fs = require "luasrcdiet.fs"
|
||||||
|
|
||||||
|
local concat = table.concat
|
||||||
|
local find = string.find
|
||||||
|
local fmt = string.format
|
||||||
|
local sub = string.sub
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local HTML_EXT = ".html"
|
||||||
|
local ENTITIES = {
|
||||||
|
["&"] = "&", ["<"] = "<", [">"] = ">",
|
||||||
|
["'"] = "'", ["\""] = """,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- simple headers and footers
|
||||||
|
local HEADER = [[
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>%s</title>
|
||||||
|
<meta name="Generator" content="LuaSrcDiet">
|
||||||
|
<style type="text/css">
|
||||||
|
%s</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre class="code">
|
||||||
|
]]
|
||||||
|
local FOOTER = [[
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
]]
|
||||||
|
-- for more, please see wikimain.css from the Lua wiki site
|
||||||
|
local STYLESHEET = [[
|
||||||
|
BODY {
|
||||||
|
background: white;
|
||||||
|
color: navy;
|
||||||
|
}
|
||||||
|
pre.code { color: black; }
|
||||||
|
span.comment { color: #00a000; }
|
||||||
|
span.string { color: #009090; }
|
||||||
|
span.keyword { color: black; font-weight: bold; }
|
||||||
|
span.number { color: #993399; }
|
||||||
|
span.operator { }
|
||||||
|
span.name { }
|
||||||
|
span.global { color: #ff0000; font-weight: bold; }
|
||||||
|
span.local { color: #0000ff; font-weight: bold; }
|
||||||
|
]]
|
||||||
|
|
||||||
|
local option -- local reference to list of options
|
||||||
|
local srcfl, destfl -- filenames
|
||||||
|
local toklist, seminfolist -- token data
|
||||||
|
|
||||||
|
local function print(...) -- handle quiet option
|
||||||
|
if option.QUIET then return end
|
||||||
|
_G.print(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Initialization.
|
||||||
|
function M.init(_option, _srcfl)
|
||||||
|
option = _option
|
||||||
|
srcfl = _srcfl
|
||||||
|
local extb, _ = find(srcfl, "%.[^%.%\\%/]*$")
|
||||||
|
local basename = srcfl
|
||||||
|
if extb and extb > 1 then
|
||||||
|
basename = sub(srcfl, 1, extb - 1)
|
||||||
|
end
|
||||||
|
destfl = basename..HTML_EXT
|
||||||
|
if option.OUTPUT_FILE then
|
||||||
|
destfl = option.OUTPUT_FILE
|
||||||
|
end
|
||||||
|
if srcfl == destfl then
|
||||||
|
error("output filename identical to input filename")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Message display, post-load processing.
|
||||||
|
function M.post_load()
|
||||||
|
print([[
|
||||||
|
HTML plugin module for LuaSrcDiet
|
||||||
|
]])
|
||||||
|
print("Exporting: "..srcfl.." -> "..destfl.."\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Post-lexing processing, can work on lexer table output.
|
||||||
|
function M.post_lex(_toklist, _seminfolist)
|
||||||
|
toklist, seminfolist = _toklist, _seminfolist
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Escapes the usual suspects for HTML/XML.
|
||||||
|
local function do_entities(z)
|
||||||
|
local i = 1
|
||||||
|
while i <= #z do
|
||||||
|
local c = sub(z, i, i)
|
||||||
|
local d = ENTITIES[c]
|
||||||
|
if d then
|
||||||
|
c = d
|
||||||
|
z = sub(z, 1, i - 1)..c..sub(z, i + 1)
|
||||||
|
end
|
||||||
|
i = i + #c
|
||||||
|
end--while
|
||||||
|
return z
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Post-parsing processing, gives globalinfo, localinfo.
|
||||||
|
function M.post_parse(globalinfo, localinfo)
|
||||||
|
local html = {}
|
||||||
|
local function add(s) -- html helpers
|
||||||
|
html[#html + 1] = s
|
||||||
|
end
|
||||||
|
local function span(class, s)
|
||||||
|
add('<span class="'..class..'">'..s..'</span>')
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #globalinfo do -- mark global identifiers as TK_GLOBAL
|
||||||
|
local obj = globalinfo[i]
|
||||||
|
local xref = obj.xref
|
||||||
|
for j = 1, #xref do
|
||||||
|
local p = xref[j]
|
||||||
|
toklist[p] = "TK_GLOBAL"
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
|
||||||
|
for i = 1, #localinfo do -- mark local identifiers as TK_LOCAL
|
||||||
|
local obj = localinfo[i]
|
||||||
|
local xref = obj.xref
|
||||||
|
for j = 1, #xref do
|
||||||
|
local p = xref[j]
|
||||||
|
toklist[p] = "TK_LOCAL"
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
|
||||||
|
add(fmt(HEADER, -- header and leading stuff
|
||||||
|
do_entities(srcfl),
|
||||||
|
STYLESHEET))
|
||||||
|
for i = 1, #toklist do -- enumerate token list
|
||||||
|
local tok, info = toklist[i], seminfolist[i]
|
||||||
|
if tok == "TK_KEYWORD" then
|
||||||
|
span("keyword", info)
|
||||||
|
elseif tok == "TK_STRING" or tok == "TK_LSTRING" then
|
||||||
|
span("string", do_entities(info))
|
||||||
|
elseif tok == "TK_COMMENT" or tok == "TK_LCOMMENT" then
|
||||||
|
span("comment", do_entities(info))
|
||||||
|
elseif tok == "TK_GLOBAL" then
|
||||||
|
span("global", info)
|
||||||
|
elseif tok == "TK_LOCAL" then
|
||||||
|
span("local", info)
|
||||||
|
elseif tok == "TK_NAME" then
|
||||||
|
span("name", info)
|
||||||
|
elseif tok == "TK_NUMBER" then
|
||||||
|
span("number", info)
|
||||||
|
elseif tok == "TK_OP" then
|
||||||
|
span("operator", do_entities(info))
|
||||||
|
elseif tok ~= "TK_EOS" then -- TK_EOL, TK_SPACE
|
||||||
|
add(info)
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
add(FOOTER)
|
||||||
|
assert(fs.write_file(destfl, concat(html), "wb"))
|
||||||
|
option.EXIT = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
89
tools/luasrcdiet/plugin/sloc.lua
Normal file
89
tools/luasrcdiet/plugin/sloc.lua
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
---------
|
||||||
|
-- Calculates SLOC for Lua 5.1 scripts
|
||||||
|
--
|
||||||
|
-- WARNING: highly experimental! interface liable to change
|
||||||
|
--
|
||||||
|
-- **Notes:**
|
||||||
|
--
|
||||||
|
-- * SLOC's behaviour is based on David Wheeler's SLOCCount.
|
||||||
|
-- * Empty lines and comment don't count as significant.
|
||||||
|
-- * Empty lines in long strings are also insignificant. This is
|
||||||
|
-- debatable. In SLOCCount, this allows counting of invalid multi-
|
||||||
|
-- line strings for C. But an empty line is still an empty line.
|
||||||
|
-- * Ignores the --quiet option, print own result line.
|
||||||
|
----
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local option -- local reference to list of options
|
||||||
|
local srcfl -- source file name
|
||||||
|
|
||||||
|
function M.init(_option, _srcfl)
|
||||||
|
option = _option
|
||||||
|
option.QUIET = true
|
||||||
|
srcfl = _srcfl
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Splits a block into a table of lines (minus EOLs).
|
||||||
|
--
|
||||||
|
-- @tparam string blk
|
||||||
|
-- @treturn {string,...} lines
|
||||||
|
local function split(blk)
|
||||||
|
local lines = {}
|
||||||
|
local i, nblk = 1, #blk
|
||||||
|
while i <= nblk do
|
||||||
|
local p, q, r, s = blk:find("([\r\n])([\r\n]?)", i)
|
||||||
|
if not p then
|
||||||
|
p = nblk + 1
|
||||||
|
end
|
||||||
|
lines[#lines + 1] = blk:sub(i, p - 1)
|
||||||
|
i = p + 1
|
||||||
|
if p < nblk and q > p and r ~= s then -- handle Lua-style CRLF, LFCR
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Post-lexing processing, can work on lexer table output.
|
||||||
|
function M.post_lex(toklist, seminfolist, toklnlist)
|
||||||
|
local lnow, sloc = 0, 0
|
||||||
|
local function chk(ln) -- if a new line, count it as an SLOC
|
||||||
|
if ln > lnow then -- new line # must be > old line #
|
||||||
|
sloc = sloc + 1; lnow = ln
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = 1, #toklist do -- enumerate over all tokens
|
||||||
|
local tok, info, ln
|
||||||
|
= toklist[i], seminfolist[i], toklnlist[i]
|
||||||
|
|
||||||
|
if tok == "TK_KEYWORD" or tok == "TK_NAME" or -- significant
|
||||||
|
tok == "TK_NUMBER" or tok == "TK_OP" then
|
||||||
|
chk(ln)
|
||||||
|
|
||||||
|
-- Both TK_STRING and TK_LSTRING may be multi-line, hence, a loop
|
||||||
|
-- is needed in order to mark off lines one-by-one. Since llex.lua
|
||||||
|
-- currently returns the line number of the last part of the string,
|
||||||
|
-- we must subtract in order to get the starting line number.
|
||||||
|
elseif tok == "TK_STRING" then -- possible multi-line
|
||||||
|
local t = split(info)
|
||||||
|
ln = ln - #t + 1
|
||||||
|
for _ = 1, #t do
|
||||||
|
chk(ln); ln = ln + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif tok == "TK_LSTRING" then -- possible multi-line
|
||||||
|
local t = split(info)
|
||||||
|
ln = ln - #t + 1
|
||||||
|
for j = 1, #t do
|
||||||
|
if t[j] ~= "" then chk(ln) end
|
||||||
|
ln = ln + 1
|
||||||
|
end
|
||||||
|
-- Other tokens are comments or whitespace and are ignored.
|
||||||
|
end
|
||||||
|
end--for
|
||||||
|
print(srcfl..": "..sloc) -- display result
|
||||||
|
option.EXIT = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
30
tools/luasrcdiet/utils.lua
Normal file
30
tools/luasrcdiet/utils.lua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
---------
|
||||||
|
-- General utility functions.
|
||||||
|
--
|
||||||
|
-- **Note: This module is not part of public API!**
|
||||||
|
----
|
||||||
|
local ipairs = ipairs
|
||||||
|
local pairs = pairs
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Returns a new table containing the contents of all the given tables.
|
||||||
|
-- Tables are iterated using @{pairs}, so this function is intended for tables
|
||||||
|
-- that represent *associative arrays*. Entries with duplicate keys are
|
||||||
|
-- overwritten with the values from a later table.
|
||||||
|
--
|
||||||
|
-- @tparam {table,...} ... The tables to merge.
|
||||||
|
-- @treturn table A new table.
|
||||||
|
function M.merge (...)
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
for _, tab in ipairs{...} do
|
||||||
|
for key, val in pairs(tab) do
|
||||||
|
result[key] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
343
tools/luatool.py
Executable file
343
tools/luatool.py
Executable file
@ -0,0 +1,343 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# ESP8266 luatool
|
||||||
|
# Author e-mail: 4ref0nt@gmail.com
|
||||||
|
# Site: http://esp8266.ru
|
||||||
|
# Contributions from: https://github.com/sej7278
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
|
||||||
|
# Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
# version 0.6.4 based version
|
||||||
|
# Version 0.8.0 upgraded to python3
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import serial
|
||||||
|
from time import sleep
|
||||||
|
import socket
|
||||||
|
import argparse
|
||||||
|
from os.path import basename
|
||||||
|
|
||||||
|
|
||||||
|
version = "0.8.0"
|
||||||
|
|
||||||
|
|
||||||
|
class TransportError(Exception):
|
||||||
|
"""Custom exception to represent errors with a transport
|
||||||
|
"""
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractTransport:
|
||||||
|
def __init__(self):
|
||||||
|
raise NotImplementedError('abstract transports cannot be instantiated.')
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
raise NotImplementedError('Function not implemented')
|
||||||
|
|
||||||
|
def read(self, length):
|
||||||
|
raise NotImplementedError('Function not implemented')
|
||||||
|
|
||||||
|
def writeln(self, data, check=1):
|
||||||
|
raise NotImplementedError('Function not implemented')
|
||||||
|
|
||||||
|
def writer(self, data):
|
||||||
|
self.writeln("file.writeline([==[" + data + "]==])\r")
|
||||||
|
|
||||||
|
def execute(self, data):
|
||||||
|
self.writeln(data + "\r")
|
||||||
|
|
||||||
|
def performcheck(self, expected):
|
||||||
|
line = ''
|
||||||
|
char = ''
|
||||||
|
i = -1
|
||||||
|
while (len(char) == 0) or (ord(char) != 62): # '>'
|
||||||
|
char = self.read(1)
|
||||||
|
#print(ord(char))
|
||||||
|
if char == '':
|
||||||
|
raise Exception('No proper answer from MCU')
|
||||||
|
if ord(char) == 13 or ord(char) == 10: # LF or CR
|
||||||
|
if line != '':
|
||||||
|
line = line.strip()
|
||||||
|
if line+'\r' == expected:
|
||||||
|
sys.stdout.write(" -> ok")
|
||||||
|
else:
|
||||||
|
if line[:4] == "lua:":
|
||||||
|
sys.stdout.write("\r\n\r\nLua ERROR: %s" % line)
|
||||||
|
raise Exception('ERROR from Lua interpreter\r\n\r\n')
|
||||||
|
else:
|
||||||
|
expected = expected.split("\r")[0]
|
||||||
|
sys.stdout.write("\r\n\r\nERROR")
|
||||||
|
sys.stdout.write("\r\n send string : '%s'" % expected)
|
||||||
|
sys.stdout.write("\r\n expected echo : '%s'" % expected)
|
||||||
|
sys.stdout.write("\r\n but got answer : '%s'" % line)
|
||||||
|
sys.stdout.write("\r\n\r\n")
|
||||||
|
raise Exception('Error sending data to MCU\r\n\r\n')
|
||||||
|
line = ''
|
||||||
|
else:
|
||||||
|
line += char.decode("utf-8")
|
||||||
|
if ord(char) == 62 and expected[i] == char:
|
||||||
|
char = ''
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
class SerialTransport(AbstractTransport):
|
||||||
|
def __init__(self, port, baud, delay):
|
||||||
|
self.port = port
|
||||||
|
self.baud = baud
|
||||||
|
self.serial = None
|
||||||
|
self.delay = delay
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.serial = serial.Serial(port, baud)
|
||||||
|
except serial.SerialException as e:
|
||||||
|
raise TransportError(e.strerror)
|
||||||
|
|
||||||
|
self.serial.timeout = 3
|
||||||
|
self.serial.interCharTimeout = 3
|
||||||
|
|
||||||
|
def writeln(self, data, check=1):
|
||||||
|
if self.serial.inWaiting() > 0:
|
||||||
|
self.serial.flushInput()
|
||||||
|
if len(data) > 0:
|
||||||
|
sys.stdout.write("\r\n->")
|
||||||
|
sys.stdout.write(data.split("\r")[0])
|
||||||
|
self.serial.write(str.encode(data))
|
||||||
|
sleep(self.delay)
|
||||||
|
if check > 0:
|
||||||
|
self.performcheck(data)
|
||||||
|
else:
|
||||||
|
sys.stdout.write(" -> send without check")
|
||||||
|
|
||||||
|
def read(self, length):
|
||||||
|
return self.serial.read(length)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.serial.flush()
|
||||||
|
self.serial.close()
|
||||||
|
|
||||||
|
|
||||||
|
class TcpSocketTransport(AbstractTransport):
|
||||||
|
def __init__(self, host, port):
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
except socket.error as e:
|
||||||
|
raise TransportError(e.strerror)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.socket.connect((host, port))
|
||||||
|
except socket.error as e:
|
||||||
|
raise TransportError(e.strerror)
|
||||||
|
# read intro from telnet server (see telnet_srv.lua)
|
||||||
|
self.socket.recv(50)
|
||||||
|
|
||||||
|
def writeln(self, data, check=1):
|
||||||
|
if len(data) > 0:
|
||||||
|
sys.stdout.write("\r\n->")
|
||||||
|
sys.stdout.write(data.split("\r")[0])
|
||||||
|
self.socket.sendall(str.encode(data))
|
||||||
|
if check > 0:
|
||||||
|
self.performcheck(data)
|
||||||
|
else:
|
||||||
|
sys.stdout.write(" -> send without check")
|
||||||
|
|
||||||
|
def read(self, length):
|
||||||
|
return self.socket.recv(length)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.socket.close()
|
||||||
|
|
||||||
|
|
||||||
|
def decidetransport(cliargs):
|
||||||
|
if cliargs.ip:
|
||||||
|
data = cliargs.ip.split(':')
|
||||||
|
host = data[0]
|
||||||
|
if len(data) == 2:
|
||||||
|
port = int(data[1])
|
||||||
|
else:
|
||||||
|
port = 23
|
||||||
|
return TcpSocketTransport(host, port)
|
||||||
|
else:
|
||||||
|
return SerialTransport(cliargs.port, cliargs.baud, cliargs.delay)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# parse arguments or use defaults
|
||||||
|
parser = argparse.ArgumentParser(description='ESP8266 Lua script uploader.')
|
||||||
|
parser.add_argument('-p', '--port', default='/dev/ttyUSB0', help='Device name, default /dev/ttyUSB0')
|
||||||
|
parser.add_argument('-b', '--baud', default=9600, help='Baudrate, default 9600')
|
||||||
|
parser.add_argument('-f', '--src', default='main.lua', help='Source file on computer, default main.lua')
|
||||||
|
parser.add_argument('-t', '--dest', default=None, help='Destination file on MCU, default to source file name')
|
||||||
|
parser.add_argument('-c', '--compile', action='store_true', help='Compile lua to lc after upload')
|
||||||
|
parser.add_argument('-r', '--restart', action='store_true', help='Restart MCU after upload')
|
||||||
|
parser.add_argument('-d', '--dofile', action='store_true', help='Run the Lua script after upload')
|
||||||
|
parser.add_argument('-v', '--verbose', action='store_true', help="Show progress messages.")
|
||||||
|
parser.add_argument('-a', '--append', action='store_true', help='Append source file to destination file.')
|
||||||
|
parser.add_argument('-l', '--list', action='store_true', help='List files on device')
|
||||||
|
parser.add_argument('-w', '--wipe', action='store_true', help='Delete all lua/lc files on device.')
|
||||||
|
parser.add_argument('-i', '--id', action='store_true', help='Query the modules chip id.')
|
||||||
|
parser.add_argument('-e', '--echo', action='store_true', help='Echo output of MCU until script is terminated.')
|
||||||
|
parser.add_argument('--delay', default=0.3, help='Delay in seconds between each write.', type=float)
|
||||||
|
parser.add_argument('--delete', default=None, help='Delete a lua/lc file from device.')
|
||||||
|
parser.add_argument('--volatile', action='store_true', help='Volatile (nothing stored on ESP)')
|
||||||
|
parser.add_argument('--ip', default=None, help='Connect to a telnet server on the device (--ip IP[:port])')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
transport = decidetransport(args)
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
transport.writeln("local l = file.list();for k,v in pairs(l) do print('name:'..k..', size:'..v)end\r", 0)
|
||||||
|
while True:
|
||||||
|
char = transport.read(1)
|
||||||
|
if char == '' or char == chr(62):
|
||||||
|
break
|
||||||
|
sys.stdout.write(str.encode(char))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.id:
|
||||||
|
transport.writeln("=node.chipid()\r", 0)
|
||||||
|
id=""
|
||||||
|
while True:
|
||||||
|
char = transport.read(1)
|
||||||
|
if char == '' or char == chr(62):
|
||||||
|
break
|
||||||
|
if char.isdigit():
|
||||||
|
id += char.decode("utf-8")
|
||||||
|
print("\n"+id)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.wipe:
|
||||||
|
transport.writeln("local l = file.list();for k,v in pairs(l) do print(k)end\r", 0)
|
||||||
|
file_list = []
|
||||||
|
fn = ""
|
||||||
|
while True:
|
||||||
|
char = transport.read(1)
|
||||||
|
if char == '' or len(char) == 0 or ord(char) == 62:
|
||||||
|
break
|
||||||
|
if char not in ['\r', '\n']:
|
||||||
|
fn += str(char)
|
||||||
|
else:
|
||||||
|
if fn:
|
||||||
|
file_list.append(fn.strip())
|
||||||
|
fn = ''
|
||||||
|
for fn in file_list[1:]: # first line is the list command sent to device
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("Delete file {} from device.\r\n".format(fn))
|
||||||
|
transport.writeln("file.remove(\"" + fn + "\")\r")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.delete:
|
||||||
|
transport.writeln("file.remove(\"" + args.delete + "\")\r")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if args.dest is None:
|
||||||
|
args.dest = basename(args.src)
|
||||||
|
|
||||||
|
# open source file for reading
|
||||||
|
try:
|
||||||
|
f = open(args.src, "rt")
|
||||||
|
except:
|
||||||
|
sys.stderr.write("Could not open input file \"%s\"\n" % args.src)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Verify the selected file will not exceed the size of the serial buffer.
|
||||||
|
# The size of the buffer is 256. This script does not accept files with
|
||||||
|
# lines longer than 230 characters to have some room for command overhead.
|
||||||
|
for ln in f:
|
||||||
|
if len(ln) > 230:
|
||||||
|
sys.stderr.write("File \"%s\" contains a line with more than 240 "
|
||||||
|
"characters. This exceeds the size of the serial buffer.\n"
|
||||||
|
% args.src)
|
||||||
|
f.close()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Go back to the beginning of the file after verifying it has the correct
|
||||||
|
# line length
|
||||||
|
f.seek(0)
|
||||||
|
|
||||||
|
# set serial timeout
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("Upload starting\r\n")
|
||||||
|
|
||||||
|
# remove existing file on device
|
||||||
|
if args.append==False and not args.volatile:
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("Stage 1. Deleting old file from flash memory")
|
||||||
|
transport.writeln("file.open(\"" + args.dest + "\", \"w\")\r")
|
||||||
|
transport.writeln("file.close()\r")
|
||||||
|
transport.writeln("file.remove(\"" + args.dest + "\")\r")
|
||||||
|
else:
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("[SKIPPED] Stage 1. Deleting old file from flash memory [SKIPPED]")
|
||||||
|
|
||||||
|
|
||||||
|
# read source file line by line and write to device
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("\r\nStage 2. Creating file in flash memory and write first line")
|
||||||
|
if not args.volatile:
|
||||||
|
if args.append:
|
||||||
|
transport.writeln("file.open(\"" + args.dest + "\", \"a+\")\r")
|
||||||
|
else:
|
||||||
|
transport.writeln("file.open(\"" + args.dest + "\", \"w+\")\r")
|
||||||
|
else:
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("\r\nStage 2. Directly execute the script...")
|
||||||
|
line = f.readline()
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("\r\nStage 3. Start writing data to flash memory...")
|
||||||
|
while line != '':
|
||||||
|
if args.volatile:
|
||||||
|
transport.execute(line.strip())
|
||||||
|
else:
|
||||||
|
transport.writer(line.strip())
|
||||||
|
line = f.readline()
|
||||||
|
|
||||||
|
# close both files
|
||||||
|
f.close()
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("\r\nStage 4. Flush data and closing file")
|
||||||
|
if not args.volatile:
|
||||||
|
transport.writeln("file.flush()\r")
|
||||||
|
transport.writeln("file.close()\r")
|
||||||
|
|
||||||
|
# compile?
|
||||||
|
if args.compile:
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("\r\nStage 5. Compiling")
|
||||||
|
transport.writeln("node.compile(\"" + args.dest + "\")\r")
|
||||||
|
transport.writeln("file.remove(\"" + args.dest + "\")\r")
|
||||||
|
|
||||||
|
# restart or dofile
|
||||||
|
if args.restart:
|
||||||
|
transport.writeln("node.restart()\r")
|
||||||
|
if args.dofile: # never exec if restart=1
|
||||||
|
transport.writeln("dofile(\"" + args.dest + "\")\r", 0)
|
||||||
|
|
||||||
|
if args.echo:
|
||||||
|
if args.verbose:
|
||||||
|
sys.stderr.write("\r\nEchoing MCU output, press Ctrl-C to exit")
|
||||||
|
while True:
|
||||||
|
data = transport.read(1)
|
||||||
|
sys.stdout.write( data.decode("ascii") )
|
||||||
|
|
||||||
|
# close serial port
|
||||||
|
transport.close()
|
||||||
|
|
||||||
|
# flush screen
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
sys.stderr.write("\r\n--->>> All done <<<---\r\n")
|
0
programESP.sh → tools/programESP.sh
Normal file → Executable file
0
programESP.sh → tools/programESP.sh
Normal file → Executable file
103
tools/remoteFlash.sh
Executable file
103
tools/remoteFlash.sh
Executable file
@ -0,0 +1,103 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
MQTTSERVER=$1
|
||||||
|
MQTTPREFIX=$2
|
||||||
|
CUSTOMFILE=$3
|
||||||
|
|
||||||
|
FLASHTOOL=./tools/tcpFlash.py
|
||||||
|
TOOLDIR=tools/
|
||||||
|
DIET=bin/luasrcdiet
|
||||||
|
|
||||||
|
UPGRADEPREP=/tmp/upgradeCMD4clock.txt
|
||||||
|
|
||||||
|
if [ ! -f $FLASHTOOL ]; then
|
||||||
|
echo "Execute the script in root folder of the project"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$MQTTPREFIX" == "" ]] || [[ "$MQTTSERVER" == "" ]]; then
|
||||||
|
echo "MQTTSERVER: ip address to mqtt server"
|
||||||
|
echo "MQTTPREFIX: configured prefex in MQTT of ESP required"
|
||||||
|
echo "usage:"
|
||||||
|
echo "$0 <MQTTSERVER> <MQTTPREFIX>"
|
||||||
|
echo "$0 192.168.0.2 basetopic"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare all files on host
|
||||||
|
if [[ "$CUSTOMFILE" == "" ]]; then
|
||||||
|
FILES="displayword.lua main.lua timecore.lua webpage.html webserver.lua wordclock.lua init.lua"
|
||||||
|
echo "Start Flasing ..."
|
||||||
|
else
|
||||||
|
FILES=$CUSTOMFILE
|
||||||
|
echo "Start Flasing $FILES ..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Convert files, if necessary
|
||||||
|
if [ "$FILES" != "config.lua" ]; then
|
||||||
|
echo "Generate DIET version of the files"
|
||||||
|
OUTFILES=""
|
||||||
|
ROOTDIR=$PWD
|
||||||
|
cd $TOOLDIR
|
||||||
|
for f in $FILES; do
|
||||||
|
if [[ "$f" == *.lua ]] && [[ "$f" != init.lua ]]; then
|
||||||
|
echo "Compress $f ..."
|
||||||
|
out=$(echo "$f" | sed 's/.lua/_diet.lua/g')
|
||||||
|
$DIET ../$f -o ../diet/$out >> /dev/null
|
||||||
|
OUTFILES="$OUTFILES diet/$out"
|
||||||
|
else
|
||||||
|
OUTFILES="$OUTFILES $f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
FILES=$OUTFILES
|
||||||
|
cd $ROOTDIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# check the connection
|
||||||
|
echo "Searching $MQTTPREFIX ..."
|
||||||
|
mosquitto_sub -h $MQTTSERVER -t "$MQTTPREFIX/#" -C 1 -v
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Entered Wordclock address: $MQTTPREFIX on $MQTTSERVER is NOT online"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "Activate Telnet server ..."
|
||||||
|
TELNETIP="empty"
|
||||||
|
while [ "$TELNETIP" = "empty" ]; do
|
||||||
|
date
|
||||||
|
mosquitto_pub -h $MQTTSERVER -t "$MQTTPREFIX/cmd/telnet" -m "a"
|
||||||
|
TELNETIP=$(mosquitto_sub -h $MQTTSERVER -t "$MQTTPREFIX/telnet" -C 1 -W 30)
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Upgrading $MQTTPREFIX via telenet on $TELNETIP ..."
|
||||||
|
sleep 1
|
||||||
|
echo "if (mlt ~= nil) then mlt:unregister() end" > $UPGRADEPREP
|
||||||
|
echo "uart.write(0, tostring(node.heap())" >> $UPGRADEPREP
|
||||||
|
echo "collectgarbage()" >> $UPGRADEPREP
|
||||||
|
echo "" >> $UPGRADEPREP
|
||||||
|
echo "download = string.char(0,0,64)" >> $UPGRADEPREP
|
||||||
|
echo "w = string.char(0,0,0)" >> $UPGRADEPREP
|
||||||
|
echo "ws2812.write(w:rep(4) .. download .. w:rep(15) .. download .. w:rep(9) .. download .. w:rep(30) .. download .. w:rep(41) .. download )" >> $UPGRADEPREP
|
||||||
|
echo "collectgarbage()" >> $UPGRADEPREP
|
||||||
|
$FLASHTOOL -f $UPGRADEPREP -t $TELNETIP -v
|
||||||
|
|
||||||
|
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
|
||||||
|
espFile=$(echo "$f" | sed 's;diet/;;g')
|
||||||
|
echo "------------- $espFile ------------"
|
||||||
|
$FLASHTOOL -t $TELNETIP -f $f -o $espFile
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "STOOOOP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "TODO: Reboot the ESP"
|
||||||
|
#echo "node.restart()" | nc $TELNETIP 23
|
||||||
|
|
||||||
|
exit 0
|
2
tools/stopController.lua
Normal file
2
tools/stopController.lua
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node.restart()
|
||||||
|
if (initTimer ~= nil) then initTimer:unregister() end
|
155
tools/tcpFlash.py
Executable file
155
tools/tcpFlash.py
Executable file
@ -0,0 +1,155 @@
|
|||||||
|
#!/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, outfile=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)
|
||||||
|
|
||||||
|
if (volatile is None):
|
||||||
|
if (outfile is None):
|
||||||
|
print "Flashing " + luafile
|
||||||
|
outfile=luafile
|
||||||
|
else:
|
||||||
|
print "Flashing " + luafile + " as " + outfile
|
||||||
|
sendCmd(s, "w= file.writeline", True)
|
||||||
|
sendCmd(s, "file.open(\"temp.lua\",\"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)
|
||||||
|
|
||||||
|
sendCmd(s, "file.remove(\"" + outfile +"\");", True)
|
||||||
|
if (not sendCmd(s, "file.rename(\"temp.lua\",\""+ outfile + "\")")):
|
||||||
|
print "Cannot move temporary file to " + outfile
|
||||||
|
|
||||||
|
|
||||||
|
# Check if the file exists:
|
||||||
|
if (not sendRecv(s, "=file.exists(\"" + outfile + "\")", "true")):
|
||||||
|
print("Cannot send " + outfile + " to the ESP")
|
||||||
|
sys.exit(4)
|
||||||
|
else:
|
||||||
|
print("Updated " + outfile + " 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('-o', '--outfile', help='LUA file name on the microcontroller (default: same name as on host)')
|
||||||
|
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 and args.outfile):
|
||||||
|
main(args.target, args.file, args.volatile, args.outfile)
|
||||||
|
elif (args.target and args.file and args.outfile):
|
||||||
|
main(args.target, args.file, None, args.outfile)
|
||||||
|
elif (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()
|
@ -1,6 +1,7 @@
|
|||||||
-- Example usage of the word clock
|
-- Example usage of the word clock
|
||||||
dofile("../wordclock.lua")
|
dofile("../wordclock.lua")
|
||||||
|
|
||||||
|
print "------- Manual test ----"
|
||||||
-- Manually set something
|
-- Manually set something
|
||||||
leds=display_timestat(15,30)
|
leds=display_timestat(15,30)
|
||||||
for k,v in pairs(leds) do
|
for k,v in pairs(leds) do
|
||||||
@ -8,6 +9,7 @@ for k,v in pairs(leds) do
|
|||||||
print(k)
|
print(k)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
print "---------------------"
|
||||||
|
|
||||||
function checkWords(leds, expected, hour, min)
|
function checkWords(leds, expected, hour, min)
|
||||||
for k, v in pairs(leds) do
|
for k, v in pairs(leds) do
|
||||||
@ -34,6 +36,19 @@ function checkWords(leds, expected, hour, min)
|
|||||||
print(hour .. ":" .. min)
|
print(hour .. ":" .. min)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function checkCharacter(words, expected)
|
||||||
|
if (words == nil or expected == nil) then
|
||||||
|
print("Not all parameter set to checkCharacter")
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
if (words ~= expected) then
|
||||||
|
print("Not all character found. Expected " .. expected .. ", but found " .. words)
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print ""
|
||||||
|
print "----------- Unit tests -------------"
|
||||||
-- Unit tests
|
-- Unit tests
|
||||||
leds=display_timestat(1,0)
|
leds=display_timestat(1,0)
|
||||||
expected={}
|
expected={}
|
||||||
@ -65,10 +80,9 @@ checkWords(leds, expected, 4 , 15)
|
|||||||
|
|
||||||
leds=display_timestat(5,20)
|
leds=display_timestat(5,20)
|
||||||
expected={}
|
expected={}
|
||||||
expected.six=1
|
expected.five=1
|
||||||
expected.tenMin=1
|
expected.twenty=1
|
||||||
expected.before=1
|
expected.after=1
|
||||||
expected.half=1
|
|
||||||
checkWords(leds, expected, 5 , 20)
|
checkWords(leds, expected, 5 , 20)
|
||||||
|
|
||||||
leds=display_timestat(6,25)
|
leds=display_timestat(6,25)
|
||||||
@ -97,9 +111,8 @@ checkWords(leds, expected, 8 , 35)
|
|||||||
leds=display_timestat(9,40)
|
leds=display_timestat(9,40)
|
||||||
expected={}
|
expected={}
|
||||||
expected.ten=1
|
expected.ten=1
|
||||||
expected.half=1
|
expected.twenty=1
|
||||||
expected.tenMin=1
|
expected.before=1
|
||||||
expected.after=1
|
|
||||||
checkWords(leds, expected, 9 , 40)
|
checkWords(leds, expected, 9 , 40)
|
||||||
|
|
||||||
leds=display_timestat(10,45)
|
leds=display_timestat(10,45)
|
||||||
@ -118,7 +131,7 @@ checkWords(leds, expected, 11 , 50)
|
|||||||
|
|
||||||
leds=display_timestat(12,55)
|
leds=display_timestat(12,55)
|
||||||
expected={}
|
expected={}
|
||||||
expected.one=1
|
expected.oneLong=1
|
||||||
expected.fiveMin=1
|
expected.fiveMin=1
|
||||||
expected.before=1
|
expected.before=1
|
||||||
checkWords(leds, expected, 12 , 55)
|
checkWords(leds, expected, 12 , 55)
|
||||||
|
64
webpage.html
Normal file
64
webpage.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html><head><title>WordClock Setup Page</title>
|
||||||
|
<style type="text/css">
|
||||||
|
#table-6 {
|
||||||
|
width: 100%
|
||||||
|
border: 1px solid #B0B0B0;
|
||||||
|
}
|
||||||
|
#table-6 tbody {
|
||||||
|
/* Kind of irrelevant unless your .css is alreadt doing something else */
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
vertical-align: baseline;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
#table-6 thead {
|
||||||
|
text-align: left;<tr><th>Three quater</th><td><input type="checkbox" name="threequater" ></td><td>Dreiviertel Joa/nei</td></tr>
|
||||||
|
|
||||||
|
}
|
||||||
|
#table-6 thead th {
|
||||||
|
background: -moz-linear-gradient(top, #F0F0F0 0, #DBDBDB 100%);
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F0F0F0), color-stop(100%, #DBDBDB));
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F0F0F0', endColorstr='#DBDBDB', GradientType=0);
|
||||||
|
border: 1px solid #B0B0B0;
|
||||||
|
color: #444;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 3px 10px;
|
||||||
|
}
|
||||||
|
#table-6 td {
|
||||||
|
padding: 3px 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
#table-6 tr:nth-child(even) {
|
||||||
|
background: #F2F2F2;
|
||||||
|
}
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to the WordClock</h1>
|
||||||
|
<h2>Initial Setup</h2>
|
||||||
|
Please note that all settings are mandatory<br /><br />
|
||||||
|
<form action="/" method="POST">
|
||||||
|
<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.</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>General 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>Foreground Color Minute1</th><td><input type="color" name="mcolor1" value="$HEXCOLOR1"></td><td>LED Color for first single minute</td></tr>
|
||||||
|
<tr><th>Foreground Color Minute2</th><td><input type="color" name="mcolor2" value="$HEXCOLOR2"></td><td>LED Color for second single minute</td></tr>
|
||||||
|
<tr><th>Foreground Color Minute3</th><td><input type="color" name="mcolor3" value="$HEXCOLOR3"></td><td>LED Color for third single minute</td></tr>
|
||||||
|
<tr><th>Foreground Color Minute4</th><td><input type="color" name="mcolor4" value="$HEXCOLOR4"></td><td>LED Color for fourth single minute</td></tr>
|
||||||
|
<tr><th>Threequarter</th><td><input type="checkbox" name="threequarter" $THREEQUATER></td><td>3/4 instead of 1/4 before</td></tr>
|
||||||
|
<tr><th>Adjust brightness</th><td><input type="checkbox" name="dim" $AUTODIM></td><td>Dim brightness automatically</td></tr>
|
||||||
|
<input type="hidden" value="true" name="web" /><!-- Activate Webserver only -->
|
||||||
|
<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>
|
308
webserver.lua
Normal file
308
webserver.lua
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
-- Webserver
|
||||||
|
local configFile="config.lua"
|
||||||
|
local httpSending=false
|
||||||
|
local sentBytes=0
|
||||||
|
|
||||||
|
-- Source https://stackoverflow.com/questions/28916182/parse-parameters-out-of-url-in-lua#28921280
|
||||||
|
function urldecode(s)
|
||||||
|
s = s:gsub('+', ' ')
|
||||||
|
:gsub('%%(%x%x)', function(h)
|
||||||
|
return string.char(tonumber(h, 16))
|
||||||
|
end)
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
function sendPage(conn, nameOfFile, replaceMap)
|
||||||
|
collectgarbage()
|
||||||
|
print("Sending " .. nameOfFile .. " " .. sentBytes .. "B already; " .. node.heap() .. "B in heap")
|
||||||
|
if (sentBytes == 0) then
|
||||||
|
-- print status status
|
||||||
|
local statusColor=string.char(0,128,0)
|
||||||
|
if ((inv46 ~= nil) and (inv46 == "on")) then
|
||||||
|
ws2812.write(string.char(0,0,0):rep(55) .. statusColor:rep(2) .. string.char(0,0,0):rep(5) .. statusColor .. string.char(0,0,0):rep(49))
|
||||||
|
else
|
||||||
|
ws2812.write(string.char(0,0,0):rep(57) .. statusColor .. string.char(0,0,0):rep(5) .. statusColor:rep(2) .. string.char(0,0,0):rep(49))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
conn:on("sent", function(conn)
|
||||||
|
if (sentBytes == 0) then
|
||||||
|
conn:close()
|
||||||
|
print("Page sent")
|
||||||
|
collectgarbage()
|
||||||
|
httpSending=false
|
||||||
|
else
|
||||||
|
collectgarbage()
|
||||||
|
sendPage(conn, nameOfFile, replaceMap)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if file.open(nameOfFile, "r") then
|
||||||
|
local buf=""
|
||||||
|
if (sentBytes <= 0) then
|
||||||
|
buf=buf .. "HTTP/1.1 200 OK\r\n"
|
||||||
|
buf=buf .. "Content-Type: text/html\r\n"
|
||||||
|
buf=buf .. "Connection: close\r\n"
|
||||||
|
buf=buf .. "Date: Thu, 29 Dec 2016 20:18:20 GMT\r\n"
|
||||||
|
buf=buf .. "\r\n\r\n"
|
||||||
|
end
|
||||||
|
-- amount of sent bytes is always zero at the beginning (so no problem)
|
||||||
|
file.seek("set", sentBytes)
|
||||||
|
|
||||||
|
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
|
||||||
|
if (replaceMap ~= nil) then
|
||||||
|
for key,value in pairs(replaceMap)
|
||||||
|
do
|
||||||
|
line = string.gsub(line, key, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
buf = buf .. line
|
||||||
|
|
||||||
|
-- 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")
|
||||||
|
-- end the function, this part is sent
|
||||||
|
return
|
||||||
|
else
|
||||||
|
-- fetch the next line
|
||||||
|
line = file.readline()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--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")
|
||||||
|
-- print status status
|
||||||
|
local statusColor=string.char(128,0,0)
|
||||||
|
if ((inv46 ~= nil) and (inv46 == "on")) then
|
||||||
|
ws2812.write(string.char(0,0,0):rep(55) .. statusColor:rep(2) .. string.char(0,0,0):rep(5) .. statusColor .. string.char(0,0,0):rep(49))
|
||||||
|
else
|
||||||
|
ws2812.write(string.char(0,0,0):rep(57) .. statusColor .. string.char(0,0,0):rep(5) .. statusColor:rep(2) .. string.char(0,0,0):rep(49))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function fillDynamicMap()
|
||||||
|
replaceMap = {}
|
||||||
|
ssid, _ = wifi.sta.getconfig()
|
||||||
|
if (ssid == nil) then return replaceMap end
|
||||||
|
if (sntpserverhostname == nil) then sntpserverhostname="ptbtime1.ptb.de" end
|
||||||
|
if (timezoneoffset == nil) then timezoneoffset=1 end
|
||||||
|
-- Set the default color, if nothing is set
|
||||||
|
if (color == nil) then color=string.char(0,0,250) end
|
||||||
|
if (color1 == nil) then color1=color end
|
||||||
|
if (color2 == nil) then color2=color end
|
||||||
|
if (color3 == nil) then color3=color end
|
||||||
|
if (color4 == nil) then color4=color end
|
||||||
|
if (colorBg == nil) then colorBg=string.char(0,0,0) end
|
||||||
|
local hexColor = "#" .. string.format("%02x",string.byte(color,2)) .. string.format("%02x",string.byte(color,1)) .. string.format("%02x",string.byte(color,3))
|
||||||
|
local hexColor1 = "#" .. string.format("%02x",string.byte(color1,2)) .. string.format("%02x",string.byte(color1,1)) .. string.format("%02x",string.byte(color1,3))
|
||||||
|
local hexColor2 = "#" .. string.format("%02x",string.byte(color2,2)) .. string.format("%02x",string.byte(color2,1)) .. string.format("%02x",string.byte(color2,3))
|
||||||
|
local hexColor3 = "#" .. string.format("%02x",string.byte(color3,2)) .. string.format("%02x",string.byte(color3,1)) .. string.format("%02x",string.byte(color3,3))
|
||||||
|
local hexColor4 = "#" .. string.format("%02x",string.byte(color4,2)) .. string.format("%02x",string.byte(color4,1)) .. string.format("%02x",string.byte(color4,3))
|
||||||
|
local hexColorBg = "#" .. string.format("%02x",string.byte(colorBg,2)) .. string.format("%02x",string.byte(colorBg,1)) .. string.format("%02x",string.byte(colorBg,3))
|
||||||
|
|
||||||
|
replaceMap["$SSID"]=ssid
|
||||||
|
replaceMap["$SNTPSERVER"]=sntpserverhostname
|
||||||
|
replaceMap["$TIMEOFFSET"]=timezoneoffset
|
||||||
|
replaceMap["$THREEQUATER"]=(threequater and "checked" or "")
|
||||||
|
replaceMap["$ADDITIONAL_LINE"]=""
|
||||||
|
replaceMap["$HEXCOLORFG"]=hexColor
|
||||||
|
replaceMap["$HEXCOLOR1"]=hexColor1
|
||||||
|
replaceMap["$HEXCOLOR2"]=hexColor2
|
||||||
|
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 readHex(source, variable)
|
||||||
|
local hexColor=string.sub(source, 4)
|
||||||
|
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(variable.."=string.char(" .. green .. "," .. red .. "," .. blue .. ")\n")
|
||||||
|
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
|
||||||
|
httpSending=true
|
||||||
|
if (color == nil) then
|
||||||
|
color=string.char(0,128,0)
|
||||||
|
end
|
||||||
|
if (sendPage ~= nil) then
|
||||||
|
print("Sending webpage.html (" .. tostring(node.heap()) .. "B free) ...")
|
||||||
|
mydofile("config")
|
||||||
|
-- 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")
|
||||||
|
--Next lines catches POST-requests without POST-data....
|
||||||
|
if postdatastart==nil then postdatastart = 1 end
|
||||||
|
local postRequestData=string.sub(payload,postdatastart+1)
|
||||||
|
local _POST = {}
|
||||||
|
for i, j in string.gmatch(postRequestData, "(%w+)=([^&]+)&*") do
|
||||||
|
_POST[i] = j
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Do the magic!
|
||||||
|
if (_POST.action ~= nil and _POST.action == "Reboot") then
|
||||||
|
node.restart()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if ((_POST.ssid~=nil) and (_POST.sntpserver~=nil) and (_POST.timezoneoffset~=nil)) then
|
||||||
|
print("New config!")
|
||||||
|
if (_POST.password==nil) then
|
||||||
|
_, password, _, _ = wifi.sta.getconfig()
|
||||||
|
print("Restoring password : " .. password)
|
||||||
|
_POST.password = password
|
||||||
|
password = nil
|
||||||
|
end
|
||||||
|
-- Safe configuration:
|
||||||
|
file.remove(configFile .. ".new")
|
||||||
|
sec, _ = rtctime.get()
|
||||||
|
file.open(configFile.. ".new", "w+")
|
||||||
|
file.write("-- Config\n" .. "station_cfg={}\nstation_cfg.ssid='" .. urldecode(_POST.ssid) .. "'\nstation_cfg.pwd='" .. urldecode(_POST.password) .. "'\nstation_cfg.save=false\nwifi.sta.config(station_cfg)\n")
|
||||||
|
file.write("sntpserverhostname=\"" .. _POST.sntpserver .. "\"\n" .. "timezoneoffset=\"" .. _POST.timezoneoffset .. "\"\n".. "inv46=nil\n")
|
||||||
|
|
||||||
|
if ( _POST.fcolor ~= nil) then
|
||||||
|
-- color=string.char(_POST.green, _POST.red, _POST.blue)
|
||||||
|
print ("Got fcolor: " .. _POST.fcolor)
|
||||||
|
readHex(_POST.fcolor, "color")
|
||||||
|
end
|
||||||
|
if ( _POST.mcolor1 ~= nil) then
|
||||||
|
readHex(_POST.mcolor1, "color1")
|
||||||
|
end
|
||||||
|
if ( _POST.mcolor2 ~= nil) then
|
||||||
|
readHex(_POST.mcolor2, "color2")
|
||||||
|
end
|
||||||
|
if ( _POST.mcolor3 ~= nil) then
|
||||||
|
readHex(_POST.mcolor3, "color3")
|
||||||
|
end
|
||||||
|
if ( _POST.mcolor4 ~= nil) then
|
||||||
|
readHex(_POST.mcolor4, "color4")
|
||||||
|
end
|
||||||
|
if ( _POST.bcolor ~= nil) then
|
||||||
|
local hexColor=string.sub(_POST.bcolor, 4)
|
||||||
|
readHex(_POST.bcolor, "colorBg")
|
||||||
|
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 (_POST.web ~= nil) then
|
||||||
|
file.write("web=true\n")
|
||||||
|
-- fill the current values
|
||||||
|
web=true
|
||||||
|
else
|
||||||
|
file.write("web=nil\n") -- use webserver instead of mqtt or telnet
|
||||||
|
-- fill the current values
|
||||||
|
web=nil
|
||||||
|
end
|
||||||
|
if (_POST.dim ~= nil) then
|
||||||
|
file.write("dim=\"" .. tostring(_POST.dim) .. "\"\n")
|
||||||
|
else
|
||||||
|
file.write("dim=nil\n") -- unset dimming functionality
|
||||||
|
end
|
||||||
|
if (_POST.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)
|
||||||
|
print("Rename config")
|
||||||
|
if (file.rename(configFile .. ".new", configFile)) then
|
||||||
|
print("Successfully")
|
||||||
|
local mytimer = tmr.create()
|
||||||
|
mytimer:register(50, tmr.ALARM_SINGLE, function (t)
|
||||||
|
mydofile("config")
|
||||||
|
replaceMap=fillDynamicMap()
|
||||||
|
replaceMap["$ADDITIONAL_LINE"]="<h2><font color=\"green\">New configuration saved</font></h2>"
|
||||||
|
print("Send success to client")
|
||||||
|
sendPage(conn, "webpage.html", replaceMap)
|
||||||
|
t:unregister()
|
||||||
|
end)
|
||||||
|
mytimer:start()
|
||||||
|
else
|
||||||
|
local mytimer = tmr.create()
|
||||||
|
mytimer:register(50, tmr.ALARM_SINGLE, function (t)
|
||||||
|
replaceMap=fillDynamicMap()
|
||||||
|
replaceMap["$ADDITIONAL_LINE"]="<h2><font color=\"red\">ERROR</font></h2>"
|
||||||
|
sendPage(conn, "webpage.html", replaceMap)
|
||||||
|
t:unregister()
|
||||||
|
end)
|
||||||
|
mytimer:start()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
replaceMap=fillDynamicMap()
|
||||||
|
replaceMap["$ADDITIONAL_LINE"]="<h2><font color=\"orange\">Not all parameters set</font></h2>"
|
||||||
|
sendPage(conn, "webpage.html", replaceMap)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print("Hello via telnet")
|
||||||
|
--here is code, if the connection is not from a webbrowser, i.e. telnet or nc
|
||||||
|
global_c=conn
|
||||||
|
function s_output(str)
|
||||||
|
if(global_c~=nil)
|
||||||
|
then global_c:send(str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
node.output(s_output, 0)
|
||||||
|
global_c:on("receive",function(c,l)
|
||||||
|
node.input(l)
|
||||||
|
end)
|
||||||
|
global_c:on("disconnection",function(c)
|
||||||
|
node.output(nil)
|
||||||
|
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
|
||||||
|
|
||||||
|
startWebServer()
|
||||||
|
collectgarbage()
|
@ -1,3 +0,0 @@
|
|||||||
-- Tell the chip to connect to thi access point
|
|
||||||
wifi.setmode(wifi.STATION)
|
|
||||||
wifi.sta.config("SSID","password")
|
|
193
wordclock.lua
193
wordclock.lua
@ -1,17 +1,25 @@
|
|||||||
-- Revese engeeniered code of display_wc_ger.c by Vlad Tepesch
|
-- Revese engeeniered code of display_wc_ger.c by Vlad Tepesch
|
||||||
-- See https://www.mikrocontroller.net/articles/Word_Clock_Variante_1#Download
|
-- See https://www.mikrocontroller.net/articles/Word_cl_Variante_1#Download
|
||||||
|
local M
|
||||||
|
do
|
||||||
|
|
||||||
-- Return the leds to use
|
-- @fn wc_timestat
|
||||||
-- the granuality is 5 minutes
|
-- Return the leds to use the granuality is 5 minutes
|
||||||
function display_timestat(hours, minutes, longmode)
|
-- @param hours the current hours (0-23)
|
||||||
|
-- @param minutes the current minute (0-59)
|
||||||
|
-- @param longmode (optional parameter) 0: no long mode, 1: long mode (itis will be set)
|
||||||
|
local timestat=function (hours, minutes, longmode)
|
||||||
if (longmode == nil) then
|
if (longmode == nil) then
|
||||||
longmode=0
|
longmode=0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- generate an empty return type
|
-- generate an empty return type
|
||||||
local ret = { itis=0, fiveMin=0, tenMin=0, after=0, before=0, threeHour=0, quater=0, half=0, s=0,
|
-- Values: it, is, 5 minutes, 10 minutes, afer, before, three hour, quarter, dreiviertel, half, s
|
||||||
one=0, two=0, three=0, four=0, five=0, six=0, seven=0, eight=0, nine=0, ten=0, eleven=0, twelve=0,
|
-- hours: one, one Long, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve
|
||||||
clock=0, sr_nc=0, min1=0, min2=0, min3=0, min4=0 }
|
-- Special ones: twenty, clock, minute 1 flag, minute 2 flag, minute 3 flag, minute 4 flag
|
||||||
|
local ret = { it=0, is=0, m5=0, m10=0, ha=0, hb=0, h3=0, hq=0, h3q=0, half=0, s=0,
|
||||||
|
h1=0, h1l=0, h2=0, h3=0, h4=0, h5=0, h6=0, h7=0, h8=0, h9=0, h10=0, h11=0, h12=0,
|
||||||
|
m20=0, cl=0, m1=0, m2=0, m3=0, m4=0 }
|
||||||
|
|
||||||
-- return black screen if there is no real time given
|
-- return black screen if there is no real time given
|
||||||
if (hours == nil or minutes == nil) then
|
if (hours == nil or minutes == nil) then
|
||||||
@ -20,71 +28,75 @@ function display_timestat(hours, minutes, longmode)
|
|||||||
|
|
||||||
-- transcode minutes
|
-- transcode minutes
|
||||||
local minutesLeds = minutes%5
|
local minutesLeds = minutes%5
|
||||||
minutes=math.floor(minutes/5)
|
local minutes=math.floor(minutes/5)
|
||||||
|
|
||||||
-- "It is" only display each half hour and each hour
|
-- "It is" only display each half hour and each hour
|
||||||
-- or if longmode is set
|
-- or if longmode is set
|
||||||
if ((longmode==1)
|
if ((longmode==1)
|
||||||
or (minutes==0)
|
or (minutes==0)
|
||||||
or (minutes==6)) then
|
or (minutes==6)) then
|
||||||
ret.itis=1
|
ret.it=1
|
||||||
|
ret.is=1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle minutes
|
-- Handle minutes
|
||||||
if (minutes > 0) then
|
if (minutes > 0) then
|
||||||
if (minutes==1) then
|
if (minutes==1) then
|
||||||
ret.fiveMin=1
|
ret.m5=1
|
||||||
ret.after=1
|
ret.ha=1
|
||||||
elseif (minutes==2) then
|
elseif (minutes==2) then
|
||||||
ret.tenMin=1
|
ret.m10=1
|
||||||
ret.after=1
|
ret.ha=1
|
||||||
elseif (minutes==3) then
|
elseif (minutes==3) then
|
||||||
ret.quater=1
|
ret.hq=1
|
||||||
ret.after=1
|
ret.ha=1
|
||||||
elseif (minutes==4) then
|
elseif (minutes==4) then
|
||||||
ret.tenMin=1
|
ret.m20=1
|
||||||
ret.half=1
|
ret.ha=1
|
||||||
ret.before=1
|
|
||||||
elseif (minutes==5) then
|
elseif (minutes==5) then
|
||||||
ret.fiveMin=1
|
ret.m5=1
|
||||||
ret.half=1
|
ret.half=1
|
||||||
ret.before=1
|
ret.hb=1
|
||||||
elseif (minutes==6) then
|
elseif (minutes==6) then
|
||||||
ret.half=1
|
ret.half=1
|
||||||
elseif (minutes==7) then
|
elseif (minutes==7) then
|
||||||
ret.fiveMin=1
|
ret.m5=1
|
||||||
ret.half=1
|
ret.half=1
|
||||||
ret.after=1
|
ret.ha=1
|
||||||
elseif (minutes==8) then
|
elseif (minutes==8) then
|
||||||
ret.tenMin=1
|
ret.m20=1
|
||||||
ret.half=1
|
ret.hb=1
|
||||||
ret.after=1
|
|
||||||
elseif (minutes==9) then
|
elseif (minutes==9) then
|
||||||
ret.quater=1
|
-- Hande if three quater or quater before is displayed
|
||||||
ret.before=1
|
if ((threequater ~= nil) and (threequater==true or threequater=="on")) then
|
||||||
|
ret.h3q=1
|
||||||
|
else
|
||||||
|
ret.hq = 1
|
||||||
|
ret.hb = 1
|
||||||
|
end
|
||||||
elseif (minutes==10) then
|
elseif (minutes==10) then
|
||||||
ret.tenMin=1
|
ret.m10=1
|
||||||
ret.before=1
|
ret.hb=1
|
||||||
elseif (minutes==11) then
|
elseif (minutes==11) then
|
||||||
ret.fiveMin=1
|
ret.m5=1
|
||||||
ret.before=1
|
ret.hb=1
|
||||||
end
|
end
|
||||||
|
|
||||||
if (minutes >= 4) then
|
if (minutes > 4) then
|
||||||
hours=hours+1
|
hours=hours+1
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
ret.clock=1
|
ret.cl=1
|
||||||
end
|
end
|
||||||
-- Display the minutes as as extra gimmic on min1 to min 4 to display the cut number
|
-- Display the minutes as as extra gimmic on m1 to min 4 to display the cut number
|
||||||
if (minutesLeds==1) then
|
if (minutesLeds==1) then
|
||||||
ret.min1=1
|
ret.m1=1
|
||||||
elseif (minutesLeds==2) then
|
elseif (minutesLeds==2) then
|
||||||
ret.min2=1
|
ret.m2=1
|
||||||
elseif (minutesLeds==3) then
|
elseif (minutesLeds==3) then
|
||||||
ret.min3=1
|
ret.m3=1
|
||||||
elseif (minutesLeds==4) then
|
elseif (minutesLeds==4) then
|
||||||
ret.min4=1
|
ret.m4=1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle hours
|
-- handle hours
|
||||||
@ -97,30 +109,87 @@ function display_timestat(hours, minutes, longmode)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if (hours == 1) then
|
if (hours == 1) then
|
||||||
ret.one=1
|
if ((ret.it == 1) and (ret.half == 0) ) then
|
||||||
elseif (hours == 2) then
|
ret.h1=1
|
||||||
ret.two=1
|
else
|
||||||
elseif (hours == 3) then
|
ret.h1l=1
|
||||||
ret.three=1
|
|
||||||
elseif (hours == 4) then
|
|
||||||
ret.four=1
|
|
||||||
elseif (hours == 5) then
|
|
||||||
ret.five=1
|
|
||||||
elseif (hours == 6) then
|
|
||||||
ret.six=1
|
|
||||||
elseif (hours == 7) then
|
|
||||||
ret.seven=1
|
|
||||||
elseif (hours == 8) then
|
|
||||||
ret.eight=1
|
|
||||||
elseif (hours == 9) then
|
|
||||||
ret.nine=1
|
|
||||||
elseif (hours == 10) then
|
|
||||||
ret.ten=1
|
|
||||||
elseif (hours == 11) then
|
|
||||||
ret.eleven=1
|
|
||||||
elseif (hours == 12) then
|
|
||||||
ret.twelve=1
|
|
||||||
end
|
end
|
||||||
|
elseif (hours == 2) then
|
||||||
|
ret.h2=1
|
||||||
|
elseif (hours == 3) then
|
||||||
|
ret.h3=1
|
||||||
|
elseif (hours == 4) then
|
||||||
|
ret.h4=1
|
||||||
|
elseif (hours == 5) then
|
||||||
|
ret.h5=1
|
||||||
|
elseif (hours == 6) then
|
||||||
|
ret.h6=1
|
||||||
|
elseif (hours == 7) then
|
||||||
|
ret.h7=1
|
||||||
|
elseif (hours == 8) then
|
||||||
|
ret.h8=1
|
||||||
|
elseif (hours == 9) then
|
||||||
|
ret.h9=1
|
||||||
|
elseif (hours == 10) then
|
||||||
|
ret.h10=1
|
||||||
|
elseif (hours == 11) then
|
||||||
|
ret.h11=1
|
||||||
|
elseif (hours == 12) then
|
||||||
|
ret.h12=1
|
||||||
|
end
|
||||||
|
collectgarbage()
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Logic to display number from Mqtt as text
|
||||||
|
function showText(dw, rgbBuffer, invertRows, dispNumber)
|
||||||
|
if (dispNumber ~= nil) then
|
||||||
|
-- Values: it, is, 5 minutes, 10 minutes, afer, before, three hour, quarter, dreiviertel, half, s
|
||||||
|
-- hours: one, one Long, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve
|
||||||
|
-- Special ones: twenty, clock, minute 1 flag, minute 2 flag, minute 3 flag, minute 4 flag
|
||||||
|
local ret = { it=0, is=0, m5=0, m10=0, ha=0, hb=0, h3=0, hq=0, h3q=0, half=0, s=0,
|
||||||
|
h1=0, h1l=0, h2=0, h3=0, h4=0, h5=0, h6=0, h7=0, h8=0, h9=0, h10=0, h11=0, h12=0,
|
||||||
|
m20=0, cl=0, m1=0, m2=0, m3=0, m4=0 }
|
||||||
|
|
||||||
|
print("Mqtt Display of temperature: " .. tostring(dispNumber) )
|
||||||
|
if (dispNumber == 1) or (dispNumber == -1) then
|
||||||
|
ret.h1=1
|
||||||
|
elseif (dispNumber == 2) or (dispNumber == -2) then
|
||||||
|
ret.h2=1
|
||||||
|
elseif (dispNumber == 3) or (dispNumber == -3) then
|
||||||
|
ret.h3=1
|
||||||
|
elseif (dispNumber == 4) or (dispNumber == -4) then
|
||||||
|
ret.h4=1
|
||||||
|
elseif (dispNumber == 5) or (dispNumber == -5) then
|
||||||
|
ret.h5=1
|
||||||
|
elseif (dispNumber == 6) or (dispNumber == -6) then
|
||||||
|
ret.h6=1
|
||||||
|
elseif (dispNumber == 7) or (dispNumber == -7) then
|
||||||
|
ret.h7=1
|
||||||
|
elseif (dispNumber == 8) or (dispNumber == -8) then
|
||||||
|
ret.h8=1
|
||||||
|
elseif (dispNumber == 9) or (dispNumber == -9) then
|
||||||
|
ret.h9=1
|
||||||
|
elseif (dispNumber == 10) or (dispNumber == -10) then
|
||||||
|
ret.h10=1
|
||||||
|
elseif (dispNumber == 11) or (dispNumber == -11) then
|
||||||
|
ret.h11=1
|
||||||
|
elseif (dispNumber == 12) or (dispNumber == -12) then
|
||||||
|
ret.h12=1
|
||||||
|
else
|
||||||
|
-- over or under temperature
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
-- Pack everything into a module
|
||||||
|
M = {
|
||||||
|
timestat = timestat,
|
||||||
|
showText = showText
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return M
|
||||||
|
Loading…
Reference in New Issue
Block a user