Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Rapid Prototyping Lab
Info Beamer
Freifunk Karlsruhe
Commits
6e47190d
Commit
6e47190d
authored
Oct 24, 2019
by
loelkes
Browse files
WIP
parent
ea340750
Changes
10
Hide whitespace changes
Inline
Side-by-side
__pycache__/sseclient.cpython-37.pyc
0 → 100644
View file @
6e47190d
File added
config.json
View file @
6e47190d
...
...
@@ -40,5 +40,5 @@
]
},
"api_url"
:
"https://api.frickelfunk.net/yanic/meshviewer/nodes.json"
,
"node_id"
:
"
c46e1ffe524
8"
"node_id"
:
"
6c98eb30000
8"
}
example_nod.json
0 → 100644
View file @
6e47190d
{
"firstseen"
:
"2019-09-20T02:03:31+0200"
,
"lastseen"
:
"2019-10-05T15:03:29+0200"
,
"flags"
:
{
"online"
:
true
,
"gateway"
:
false
},
"statistics"
:
{
"node_id"
:
"6c98eb300008"
,
"clients"
:
0
,
"rootfs_usage"
:
0.0974
,
"loadavg"
:
0.09
,
"memory_usage"
:
0.37309840169458885
,
"uptime"
:
351981.01
,
"idletime"
:
342112.25
,
"gateway"
:
"fc:e5:17:0a:03:80"
,
"gateway6"
:
"fc:cb:ff:00:0a:03"
,
"processes"
:
{
"total"
:
57
,
"running"
:
2
},
"mesh_vpn"
:
{
"groups"
:
{
"backbone"
:
{
"peers"
:
{
"alb0"
:
null
,
"alb1"
:
null
,
"alb2"
:
{
"established"
:
263134.264
},
"alb3"
:
null
},
"groups"
:
null
}
}
},
"traffic"
:
{
"tx"
:
{
"bytes"
:
42077985
,
"packets"
:
331155
,
"dropped"
:
112
},
"rx"
:
{
"bytes"
:
366651931
,
"packets"
:
576347
},
"forward"
:
{
"bytes"
:
370
,
"packets"
:
5
},
"mgmt_tx"
:
{
"bytes"
:
599254740
,
"packets"
:
5368354
},
"mgmt_rx"
:
{
"bytes"
:
177532820
,
"packets"
:
2106432
}
}
},
"nodeinfo"
:
{
"node_id"
:
"6c98eb300008"
,
"network"
:
{
"mac"
:
"6c:98:eb:30:00:08"
,
"addresses"
:
[
"2001:678:6e3:10b0:6e98:ebff:fe30:8"
,
"fe80::6e98:ebff:fe30:8"
],
"mesh"
:
{
"bat0"
:
{
"interfaces"
:
{
"wireless"
:
[
"4a:11:4f:2e:cc:75"
,
"4a:11:4f:2e:cc:71"
],
"other"
:
[
"4a:11:4f:2e:cc:73"
],
"tunnel"
:
[
"4a:11:4f:2e:cc:77"
]
}
}
},
"mesh_interfaces"
:
null
},
"owner"
:
null
,
"system"
:
{
"site_code"
:
"ffka"
,
"domain_code"
:
"kakr_76327"
},
"hostname"
:
"ffka-Seltenbach-Sued"
,
"location"
:
{
"longitude"
:
8.524798
,
"latitude"
:
49.004719
},
"software"
:
{
"autoupdater"
:
{
"enabled"
:
true
,
"branch"
:
"beta"
},
"batman-adv"
:
{
"version"
:
"openwrt-2018.1-8"
,
"compat"
:
15
},
"babeld"
:
{},
"fastd"
:
{
"enabled"
:
true
,
"version"
:
"v18"
},
"firmware"
:
{
"base"
:
"gluon-v2018.2.3"
,
"release"
:
"0.7.5-beta.0-20190915"
},
"status-page"
:
{
"api"
:
0
}
},
"hardware"
:
{
"nproc"
:
1
,
"model"
:
"OCEDO Koala"
},
"vpn"
:
false
}
}
freifunk.py
View file @
6e47190d
#!/usr/bin/python
# -*- coding: utf-8 -*-
class
FreifunkNode
(
object
):
def
__init__
(
self
,
data
):
self
.
data
=
data
# Easier handline og the YANIC API of Freifunk.
from
http_API
import
API
import
logging
logger
=
logging
.
getLogger
(
__name__
)
class
Freifunk
(
object
):
def
__init__
(
self
):
self
.
node_id
=
0
self
.
node_data
=
False
self
.
human_readable
=
True
self
.
supported_versions
=
[
2
]
def
update
(
self
,
data
,
id
):
self
.
data
=
data
self
.
select
(
id
)
def
check_version
(
self
):
if
self
.
data
[
'data'
][
'version'
]
not
in
self
.
supported_versions
:
return
False
else
:
return
True
def
select
(
self
,
id
):
self
.
node_id
=
id
self
.
node_data
=
next
(
node
for
node
in
self
.
data
[
'nodes'
]
\
if
node
[
'nodeinfo'
][
'node_id'
]
==
self
.
node_id
)
def
select
(
self
,
id
=
False
):
if
id
:
self
.
node_id
=
id
self
.
node_data
=
next
(
node
for
node
in
self
.
data
[
'nodes'
]
if
node
[
'nodeinfo'
][
'node_id'
]
==
self
.
node_id
)
# From https://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size
# See also https://en.wikipedia.org/wiki/Binary_prefix#yobi
...
...
@@ -122,9 +131,9 @@ class FreifunkNode(object):
def
traffic
(
self
):
return
'Tx: {} | Rx: {}'
.
format
(
self
.
tx_bytes
,
self
.
rx_bytes
)
# --------------------------------------------------------------------------
# Node information
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# Node information
# --------------------------------------------------------------------------
@
property
def
hostname
(
self
):
...
...
http_API.py
View file @
6e47190d
import
time
,
json
,
pytz
,
requests
from
datetime
import
datetime
,
timedelta
# Version 05.Oktober 2019
import
time
import
json
import
requests
from
sseclient
import
SSEClient
import
xml.etree.ElementTree
as
ET
import
logging
from
toolbox
import
Util
class
Timer
(
object
):
"""
Timer to enforce rate limits and colltect statisticsself.
Parameters
----------
timeout: integer
Number of seconds until the timeout resturns false after reset or init.
"""
def
__init__
(
self
,
timeout
=
10
):
self
.
tOut
=
timeout
def
__enter__
(
self
):
self
.
tStart
=
time
.
time
()
def
__exit__
(
self
,
type
,
value
,
traceback
):
pass
@
property
def
timeout
(
self
):
if
self
.
tOut
-
self
.
elapsed
>
0
:
return
True
else
:
return
False
@
property
def
reset
(
self
):
self
.
tStart
=
time
.
time
()
@
property
def
elapsed
(
self
):
return
time
.
time
()
-
self
.
tStart
logger
=
logging
.
getLogger
(
__name__
)
class
API
(
object
):
def
__init__
(
self
,
url
=
None
,
user
=
None
,
key
=
None
):
self
.
setupRequests
(
url
=
url
,
user
=
user
,
key
=
key
)
self
.
reset
()
self
.
statistics
=
dict
(
speed
=
0
,
time
=
0
,
size
=
0
)
self
.
format
=
'json'
def
reset
(
self
):
self
.
tElapsed
,
self
.
lines
,
self
.
util
=
0
,
[],
Util
()
self
.
format
=
''
def
setupRequests
(
self
,
url
,
user
=
None
,
key
=
None
,
rateLimit
=
3600
,
api_suffix
=
''
):
"""
Setup the request parameters.
This should be called once within __init__
.
This should be called once within __init__
Parametes
---------
...
...
@@ -70,9 +34,11 @@ class API(object):
String to append to the URL for the request. Example: '&page=1'
"""
self
.
api_url
,
self
.
rateLimit
=
url
,
rateLimit
self
.
lastUpdate
,
self
.
response
=
0
,
dict
()
self
.
api_suffix
,
self
.
request_url
=
''
,
''
self
.
api_url
=
url
self
.
rateLimit
=
rateLimit
self
.
lastUpdate
=
0
self
.
api_suffix
=
''
self
.
request_url
=
''
self
.
status
=
False
self
.
auth
=
requests
.
auth
.
HTTPBasicAuth
(
user
,
key
)
if
user
and
key
else
None
...
...
@@ -99,7 +65,17 @@ class API(object):
"""
pass
def
query
(
self
,
stream
=
False
):
def
stream
(
self
):
try
:
response
=
requests
.
get
(
self
.
request_url
,
auth
=
self
.
auth
,
stream
=
True
)
except
Excpetion
as
e
:
logging
.
warning
(
e
)
pass
if
response
.
status_code
==
200
:
self
.
content
=
None
self
.
sseclient
=
SSEClient
(
response
)
def
query
(
self
,
url
=
False
):
"""
Perfom a GET-Request on the URL in self.request_url.
...
...
@@ -112,25 +88,18 @@ class API(object):
request has failed or it hits the rate limit.
"""
if
time
.
time
()
-
self
.
lastUpdate
>
self
.
rateLimit
:
if
time
.
time
()
-
self
.
lastUpdate
>
self
.
rateLimit
or
url
:
# Ignore rate limit if manual url
self
.
status
=
False
self
.
preQuery
()
tStart
=
time
.
time
()
result
=
requests
.
get
(
self
.
request_url
,
auth
=
self
.
auth
,
stream
=
stream
)
self
.
tElapsed
=
time
.
time
()
-
tStart
self
.
lastUpdate
+=
5
if
result
.
status_code
==
202
else
0
if
result
.
status_code
==
200
:
if
not
stream
:
self
.
stats
(
self
.
tElapsed
,
len
(
result
.
content
))
if
self
.
format
==
'json'
:
self
.
response
=
result
.
json
()
elif
self
.
format
==
'xml'
:
self
.
response
=
ET
.
fromstring
(
result
.
content
)
elif
stream
:
self
.
response
=
None
self
.
sseclient
=
SSEClient
(
result
)
self
.
lastUpdate
=
time
.
time
()
self
.
status
=
True
try
:
response
=
requests
.
get
(
url
or
self
.
request_url
,
auth
=
self
.
auth
)
except
Exception
as
e
:
logger
.
warning
(
e
)
return
self
.
status
self
.
lastUpdate
+=
5
if
response
.
status_code
==
202
else
0
if
response
.
status_code
==
200
:
self
.
handleResponse
(
response
)
self
.
postQuery
()
else
:
pass
...
...
@@ -138,9 +107,46 @@ class API(object):
self
.
postQuery
()
return
self
.
status
def
stats
(
self
,
time
,
size
):
self
.
statistics
.
update
(
time
=
time
,
size
=
size
)
self
.
statistics
.
update
(
speed
=
round
(
self
.
statistics
[
'speed'
]
/
self
.
statistics
[
'time'
]))
def
handleResponse
(
self
,
response
):
if
self
.
format
==
'json'
:
self
.
content
=
response
.
json
()
elif
self
.
format
==
'xml'
:
self
.
content
=
ET
.
fromstring
(
response
.
content
)
else
:
self
.
content
=
response
.
content
self
.
lastUpdate
=
time
.
time
()
self
.
status
=
True
def
update
(
self
):
pass
def
checkConnection
(
self
,
url
=
'https://1.1.1.1'
,
timeout
=
5
):
logger
.
info
(
'Performing selftest with {}'
.
format
(
url
))
if
self
.
query
(
url
):
logger
.
info
(
'connected to the internet.'
)
else
:
logger
.
warning
(
'Not connected to the internet'
)
def
speedtest
(
self
,
url
=
'http://speedtest.belwue.net/100M'
):
logger
.
info
(
'Performing speedtest with {}'
.
format
(
url
))
now
=
time
.
time
()
if
self
.
query
(
url
):
self
.
connectionSpeed
=
int
(
len
(
self
.
content
)
/
(
time
.
time
()
-
now
))
logger
.
info
(
'Speed is {}/s'
.
format
(
human_readable
(
self
.
connectionSpeed
)))
# Source https://stackoverflow.com/a/43750422
def
human_readable
(
bytes
,
units
=
[
' bytes'
,
'KB'
,
'MB'
,
'GB'
,
'TB'
,
'PB'
,
'EB'
]):
""" Returns a human readable string reprentation of bytes"""
return
str
(
bytes
)
+
units
[
0
]
if
bytes
<
1024
else
human_readable
(
bytes
>>
10
,
units
[
1
:])
if
__name__
==
'__main__'
:
logging
.
basicConfig
(
level
=
logging
.
INFO
)
logger
.
info
(
'Script is executed as standalone file.'
)
test
=
API
()
# See if we are connected to the internet
test
.
checkConnection
()
# Test our connection speed.
test
.
speedtest
()
http_API.pyc
0 → 100644
View file @
6e47190d
File added
node.lua
View file @
6e47190d
---
--- node.lua for BMJV - Gesetze im Internet
---
gl
.
setup
(
NATIVE_WIDTH
,
NATIVE_HEIGHT
)
local
json
=
require
"json"
local
st
=
util
.
screen_transform
(
0
)
-- local font = resource.load_font "Inconsolata-Regular.ttf"
local
metadata
=
""
local
on
=
true
local
pushedLine
s
=
{}
local
element
s
=
{}
--
-- watch the json files
...
...
@@ -20,55 +16,43 @@ util.json_watch("config.json", function(cfg)
st
=
util
.
screen_transform
(
cfg
.
rotation
)
font
=
resource
.
load_font
(
cfg
.
font
)
background_color
=
cfg
.
bg_color
text_color
=
cfg
.
text_color
end
)
util
.
data_mapper
{
push
=
function
(
data
)
local
payload
=
json
.
decode
(
data
)
text
=
payload
.
text
buttons
=
payload
.
buttons
end
;
stream
=
function
(
data
)
clear
=
function
(
data
)
for
i
in
pairs
(
elements
)
do
elements
[
i
]
=
nil
end
;
end
;
text
=
function
(
data
)
local
payload
=
json
.
decode
(
data
)
table.remove
(
pushedLines
,
52
)
table.insert
(
pushedLines
,
1
,
payload
.
line
)
table.insert
(
elements
,
1
,
payload
.
data
)
end
;
toggle
=
function
(
status
)
on
=
status
==
"on"
end
;
}
-- create streamed content
function
streamContent
()
for
i
,
line
in
ipairs
(
pushedLines
)
do
-- font:write(line.x, (i)*line.y, line.string, line.h, line.r, line.g, line.b, line.a)
font
:
write
(
line
.
x
,
(
i
)
*
line
.
y
,
line
.
string
,
line
.
h
,
text_color
.
r
,
text_color
.
g
,
text_color
.
b
,
text_color
.
a
)
function
writeText
()
for
i
,
line
in
ipairs
(
elements
)
do
font
:
write
(
line
.
x
,
line
.
y
,
line
.
string
,
line
.
h
,
line
.
r
,
line
.
g
,
line
.
b
,
line
.
a
)
end
end
-- create the content
function
content
()
-- -- get the single lines to be written
-- for i, button in ipairs(buttons) do
-- width = 0 -- font:width(button.text.string, button.text.h)
-- for j, shape in ipairs(button.shapes) do
-- plain_color = resource.create_colored_texture(shape.r,shape.g,shape.b,shape.a)
-- plain_color:draw(shape.x1, shape.y1, shape.x2+width, shape.y2)
-- end
-- font:write(button.text.x, button.text.y, button.text.string, button.text.h, button.text.r, button.text.g, button.text.b, button.text.a)
-- end
for
i
,
line
in
ipairs
(
text
)
do
font
:
write
(
line
.
x
,
line
.
y
,
line
.
string
,
line
.
h
,
text_color
.
r
,
text_color
.
g
,
text_color
.
b
,
text_color
.
a
)
end
end
function
node
.
render
()
gl
.
clear
(
background_color
.
r
,
background_color
.
g
,
background_color
.
b
,
background_color
.
a
)
-- clear the backgrount
st
()
if
on
then
content
()
streamContent
()
end
gl
.
clear
(
background_color
.
r
,
background_color
.
g
,
background_color
.
b
,
background_color
.
a
)
st
()
if
on
then
writeText
()
end
end
service
View file @
6e47190d
...
...
@@ -6,7 +6,7 @@ import traceback
from
hosted
import
CONFIG
,
NODE
CONFIG
.
restart_on_update
()
from
freifunk
import
Freifunk
Node
from
freifunk
import
Freifunk
from
http_API
import
API
from
toolbox
import
Util
from
hosted
import
node
,
device
...
...
@@ -16,25 +16,27 @@ import time
class
Service
(
object
):
def
__init__
(
self
,
url
,
node_id
):
self
.
util
=
Util
()
self
.
api_url
=
API
(
url
=
url
)
self
.
api_url
.
format
=
'json'
self
.
yanic
=
API
(
url
=
url
)
self
.
yanic
.
format
=
'json'
self
.
freifunk
=
Freifunk
()
self
.
lastRefresh
=
0
self
.
node_id
=
node_id
def
refresh
(
self
):
if
time
.
time
()
-
self
.
lastRefresh
>
60
:
node
[
'/clear'
](
True
)
self
.
util
.
reset
()
self
.
api_url
.
query
()
self
.
node
=
F
reifunk
Nod
e
(
self
.
api_url
.
response
)
self
.
node
.
select
(
self
.
node_id
)
self
.
util
.
text
(
'Freifunk Node {}'
.
format
(
self
.
node
.
model
),
20
,
20
,
100
)
self
.
yanic
.
query
()
self
.
f
reifunk
.
updat
e
(
self
.
yanic
.
content
,
self
.
node_id
)
line
=
self
.
freifunk
.
arrange
(
)
node
[
'/text'
](
dict
(
data
=
line
)
)
self
.
lastRefresh
=
time
.
time
()
else
:
pass
def
update
(
self
):
self
.
refresh
()
self
.
util
.
update
()
#
self.util.update()
def
run
(
self
):
while
1
:
...
...
sseclient.pyc
0 → 100644
View file @
6e47190d
File added
toolbox.py
View file @
6e47190d
# Version:
17 Juni
2019
# Version:
05 Oktober
2019
import
time
,
json
,
pytz
from
datetime
import
datetime
...
...
@@ -17,54 +17,52 @@ class Util(object):
def
reset
(
self
):
self
.
timezone
=
pytz
.
timezone
(
'Europe/Berlin'
)
self
.
lines
,
self
.
rectangles
,
self
.
buttons
=
[],
[],
[]
self
.
touch_areas
,
self
.
lineHeight
=
[],
32
self
.
offset
,
self
.
margin
=
dict
(
x
=
0
,
y
=
0
),
dict
(
x
=
20
,
y
=
20
)
self
.
text
=
[]
# self.rectangles
# self.buttons = [], [], []
# self.touch_areas = []
self
.
lineHeight
=
32
self
.
colour
=
dict
(
button
=
Colour
(
1
,
1
,
1
,
1
),
border
=
Colour
(
1
,
0
,
0
,
1
),
text
=
Colour
(
1
,
1
,
1
,
1
))
self
.
timeFormat
=
'%Y-%m-%d %H:%M:%S'
#
self.timeFormat = '%Y-%m-%d %H:%M:%S'
# Touch utils.
def
button
(
self
,
x
,
y
,
width
,
height
,
label
=
''
,
id
=
0
,
border
=
0
,
colour
=
None
):
colour
=
colour
or
self
.
colour
touch
=
TouchArea
(
x
,
y
,
x
+
width
,
y
+
height
,
id
,
border
=
border
,
colour
=
colour
)
if
id
:
# No actions if no ID provided
self
.
touch_areas
.
append
(
touch
)
button
=
dict
(
shapes
=
[
s
.
_asdict
()
for
s
in
touch
.
shapes
],
text
=
{})
button
.
update
(
dict
(
text
=
self
.
line
(
string
=
label
,
height
=
height
/
2
,
offset
=
dict
(
x
=
x
,
y
=
y
),
colour
=
colour
[
'text'
].
_asdict
())))
self
.
buttons
.
append
(
button
)
def
touched
(
self
,
c
):
# c holds the cursos position, f the field element
active
=
[]
for
f
in
self
.
touch_areas
:
if
c
.
x
in
range
(
f
.
box
.
x1
,
f
.
box
.
x2
)
and
c
.
y
in
range
(
f
.
box
.
y1
,
f
.
box
.
y2
):
active
.
append
(
f
.
id
)
return
active
#
def button(self, x, y, width, height, label='', id=0, border=0, colour=None):
#
colour = colour or self.colour
#
touch = TouchArea(x, y, x+width, y+height, id, border=border, colour=colour)
#
if id: # No actions if no ID provided
#
self.touch_areas.append(touch)
#
button = dict(shapes=[s._asdict() for s in touch.shapes], text={})
#
button.update(dict(text=self.line(string=label, height=height/2,
#
offset=dict(x=x, y=y), colour=colour['text']._asdict())))
#
self.buttons.append(button)
#
#
def touched(self, c): # c holds the cursos position, f the field element
#
active = []
#
for f in self.touch_areas:
#
if c.x in range(f.box.x1, f.box.x2) and c.y in range(f.box.y1, f.box.y2):
#
active.append(f.id)
#
return active
# Design utils
def
line
(
self
,
string
=
None
,
offset
=
None
,
height
=
0
,
colour
=
None
):
offset
=
offset
or
self
.
offset
def
line
(
self
,
position
=
None
,
string
=
None
,
height
=
0
,
colour
=
None
):
line
=
dict
(
string
=
''
or
string
,
h
=
height
or
self
.
lineHeight
)
line
.
update
({
k
:
offset
[
k
]
+
self
.
margi
n
[
k
]
for
k
in
[
'x'
,
'y'
]})
line
.
update
({
k
:
positio
n
[
k
]
for
k
in
[
'x'
,
'y'
]})
line
.
update
(
colour
or
self
.
colour
[
'text'
].
_asdict
())
return
line
def
text
(
self
,
string
,
x
,
y
,
height
):
self
.
lines
.
append
(
dict
(
string
=
string
,
x
=
x
,
y
=
y
,
h
=
height
))
# Datetime tools
def
current_time
(
self
,
timestamp
=
None
):
now
=
datetime
.
utcnow
()
if
not
timestamp
else
datetime
.
utcfromtimestamp
(
timestamp
)
now
=
now
.
replace
(
tzinfo
=
pytz
.
utc
)
now
=
now
.
astimezone
(
self
.
timezone
)
now
=
now
.
replace
(
tzinfo
=
None
)
return
now
# # Datetime tools
# def current_time(self, timestamp=None):
# now = datetime.utcnow() if not timestamp else datetime.utcfromtimestamp(timestamp)
# now = now.replace(tzinfo=pytz.utc)
# now = now.astimezone(self.timezone)
# now = now.replace(tzinfo=None)
# return now
def
week
(
self
,
timestamp
=
None
):
return
self
.
current_time
(
timestamp
).
strftime
(
'%U'
)
#
def week(self, timestamp=None):
#
return self.current_time(timestamp).strftime('%U')
def
update
(
self
):
node
[
'/push'
](
dict
(
buttons
=
self
.
buttons
,
text
=
self
.
lines
))
#
def update(self):
#
node['/push'](dict(buttons=self.buttons, text=self.lines))
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please