Compare commits
8 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63ef9727eb | ||
|
|
6c1bf0bbf6 | ||
|
|
fc5a5cf688 | ||
|
|
039197437e | ||
|
|
f36babac0a | ||
|
|
92932b3ed5 | ||
|
|
05836bb7f7 | ||
|
|
c9d3d3d656 |
1
.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
public
|
||||
0
.gitmodules
vendored
77
404.html
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>404 Page not found | jaseg.de</title>
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="stylesheet" href="/css/fonts.css" />
|
||||
|
||||
<header>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
<nav>
|
||||
<ul>
|
||||
|
||||
|
||||
<li class="pull-left ">
|
||||
<a href="https://blog.jaseg.de/">/home/jaseg.de</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<br/>
|
||||
|
||||
|
||||
404 NOT FOUND
|
||||
|
||||
<footer>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function center_el(tagName) {
|
||||
var tags = document.getElementsByTagName(tagName), i, tag;
|
||||
for (i = 0; i < tags.length; i++) {
|
||||
tag = tags[i];
|
||||
var parent = tag.parentElement;
|
||||
|
||||
if (parent.childNodes.length === 1) {
|
||||
|
||||
if (parent.nodeName === 'A') {
|
||||
parent = parent.parentElement;
|
||||
if (parent.childNodes.length != 1) continue;
|
||||
}
|
||||
if (parent.nodeName === 'P') parent.style.textAlign = 'center';
|
||||
}
|
||||
}
|
||||
}
|
||||
var tagNames = ['img', 'embed', 'object'];
|
||||
for (var i = 0; i < tagNames.length; i++) {
|
||||
center_el(tagNames[i]);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<div id="license-info">
|
||||
©2020 by Jan Götte. This work is licensed under
|
||||
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC-BY-NC-SA 4.0</a>.
|
||||
</div>
|
||||
<div id="imprint-info">
|
||||
<a href="/imprint">Impressum und Haftungsausschluss und Datenschutzerklärung</a>.
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
97
about/index.html
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>About jaseg | jaseg.de</title>
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="stylesheet" href="/css/fonts.css" />
|
||||
|
||||
<header>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
<nav>
|
||||
<ul>
|
||||
|
||||
|
||||
<li class="pull-left ">
|
||||
<a href="https://blog.jaseg.de/">/home/jaseg.de</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<br/>
|
||||
|
||||
<div class="article-meta">
|
||||
<h1><span class="title">About jaseg</span></h1>
|
||||
|
||||
|
||||
<p class="terms">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<main>
|
||||
<div class="document" id="about">
|
||||
<h1 class="title">About</h1>
|
||||
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function center_el(tagName) {
|
||||
var tags = document.getElementsByTagName(tagName), i, tag;
|
||||
for (i = 0; i < tags.length; i++) {
|
||||
tag = tags[i];
|
||||
var parent = tag.parentElement;
|
||||
|
||||
if (parent.childNodes.length === 1) {
|
||||
|
||||
if (parent.nodeName === 'A') {
|
||||
parent = parent.parentElement;
|
||||
if (parent.childNodes.length != 1) continue;
|
||||
}
|
||||
if (parent.nodeName === 'P') parent.style.textAlign = 'center';
|
||||
}
|
||||
}
|
||||
}
|
||||
var tagNames = ['img', 'embed', 'object'];
|
||||
for (var i = 0; i < tagNames.length; i++) {
|
||||
center_el(tagNames[i]);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<div id="license-info">
|
||||
©2020 by Jan Götte. This work is licensed under
|
||||
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC-BY-NC-SA 4.0</a>.
|
||||
</div>
|
||||
<div id="imprint-info">
|
||||
<a href="/imprint">Impressum und Haftungsausschluss und Datenschutzerklärung</a>.
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
||||
81
categories/index.html
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Categories | jaseg.de</title>
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="stylesheet" href="/css/fonts.css" />
|
||||
|
||||
<header>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
<nav>
|
||||
<ul>
|
||||
|
||||
|
||||
<li class="pull-left ">
|
||||
<a href="https://blog.jaseg.de/">/home/jaseg.de</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<br/>
|
||||
|
||||
|
||||
<h1>Categories</h1>
|
||||
|
||||
<ul class="terms">
|
||||
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function center_el(tagName) {
|
||||
var tags = document.getElementsByTagName(tagName), i, tag;
|
||||
for (i = 0; i < tags.length; i++) {
|
||||
tag = tags[i];
|
||||
var parent = tag.parentElement;
|
||||
|
||||
if (parent.childNodes.length === 1) {
|
||||
|
||||
if (parent.nodeName === 'A') {
|
||||
parent = parent.parentElement;
|
||||
if (parent.childNodes.length != 1) continue;
|
||||
}
|
||||
if (parent.nodeName === 'P') parent.style.textAlign = 'center';
|
||||
}
|
||||
}
|
||||
}
|
||||
var tagNames = ['img', 'embed', 'object'];
|
||||
for (var i = 0; i < tagNames.length; i++) {
|
||||
center_el(tagNames[i]);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<div id="license-info">
|
||||
©2020 by Jan Götte. This work is licensed under
|
||||
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC-BY-NC-SA 4.0</a>.
|
||||
</div>
|
||||
<div id="imprint-info">
|
||||
<a href="/imprint">Impressum und Haftungsausschluss und Datenschutzerklärung</a>.
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
10
categories/index.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Categories on jaseg.de</title>
|
||||
<link>https://blog.jaseg.de/categories/</link>
|
||||
<description>Recent content in Categories on jaseg.de</description>
|
||||
<generator>Hugo -- gohugo.io</generator>
|
||||
<language>en-us</language><atom:link href="https://blog.jaseg.de/categories/index.xml" rel="self" type="application/rss+xml" />
|
||||
</channel>
|
||||
</rss>
|
||||
76
config.toml
|
|
@ -1,76 +0,0 @@
|
|||
baseURL = "http://jaseg.de/"
|
||||
languageCode = "en-us"
|
||||
title = "Home"
|
||||
copyright = "Jan Sebastian Götte"
|
||||
theme = "conspiracy"
|
||||
enableRobotsTXT = true
|
||||
|
||||
[outputs]
|
||||
home = ['html', 'rss']
|
||||
taxonomy = ['html', 'rss']
|
||||
|
||||
[params]
|
||||
fediverse_account = "@jaseg@chaos.social"
|
||||
|
||||
[taxonomies]
|
||||
category = "Categories"
|
||||
blog = "Posts"
|
||||
|
||||
[[menu.main]]
|
||||
name = "Home"
|
||||
url = "/"
|
||||
weight = 1
|
||||
|
||||
[[menu.main]]
|
||||
name = "Blog"
|
||||
url = "/blog/"
|
||||
weight = 2
|
||||
|
||||
[[menu.main]]
|
||||
name = "Projects"
|
||||
url = "/projects/"
|
||||
weight = 3
|
||||
|
||||
[[menu.main]]
|
||||
name = "About"
|
||||
url = "/about/"
|
||||
weight = 4
|
||||
|
||||
[[params.profile_links]]
|
||||
name = "cgit"
|
||||
url = "https://git.jaseg.de/"
|
||||
weight = 1
|
||||
|
||||
[[params.profile_links]]
|
||||
name = "Github"
|
||||
url = "https://github.com/jaseg"
|
||||
weight = 2
|
||||
|
||||
[[params.profile_links]]
|
||||
name = "Gitlab"
|
||||
url = "https://gitlab.com/neinseg"
|
||||
weight = 3
|
||||
|
||||
[[params.profile_links]]
|
||||
name = "Mastodon"
|
||||
url = "https://chaos.social/@jaseg"
|
||||
weight = 4
|
||||
|
||||
[[params.footer_links]]
|
||||
name = "About"
|
||||
url = "/about/"
|
||||
weight = 1
|
||||
|
||||
[[params.footer_links]]
|
||||
name = "Imprint"
|
||||
url = "/imprint/"
|
||||
weight = 2
|
||||
|
||||
[[params.homepage_categories]]
|
||||
title = "Blog"
|
||||
key = "blog"
|
||||
weight = 2
|
||||
count = 10
|
||||
|
||||
[security.exec]
|
||||
allow = ["^dart-sass-embedded$", "^go$", "^npx$", "^postcss$", "^rst2html$"]
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
title: jaseg.de
|
||||
---
|
||||
|
||||
Hi there, and welcome to my personal website.
|
||||
|
||||
I'm jaseg, and I write about my projects here. You can find long-form articles in the blog, and links to my open-source
|
||||
projects on the projects page. On the top right of this page, there are links to my git repositories and social media
|
||||
pages. If you want to learn more about me, head over to the about page.
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
---
|
||||
title: "About jaseg"
|
||||
---
|
||||
|
||||
About
|
||||
-----
|
||||
|
||||
Hej, I'm Jan, or jaseg. At the moment I'm doing a PhD (Dr.-Ing.) at TU Darmstadt in Computer Science, specializing on
|
||||
Hardware Security. This is my personal website where I publish things that I find interesting.
|
||||
|
||||
I self-host my code at `git.jaseg.de <https://git.jaseg.de/>`__, but I am also on `github <https://github.com/jaseg>`__
|
||||
and on `gitlab <https://gitlab.com/neinseg>`__. I use github for issue tracking for some of my projects such as
|
||||
`gerbolyze <https://github.com/jaseg/gerbolyze>`__ and `python-mpv <https://github.com/jaseg/python-mpv>`__. I maintain
|
||||
the `python-mpv <https://pypi.org/project/python-mpv/>`__ and `gerbolyze <https://pypi.org/project/gerbolyze/>`__ python
|
||||
packages on PyPI. Release tags on these two repositories are signed with the release signing key found `on github
|
||||
<https://github.com/jaseg.gpg>`__ and below.
|
||||
|
||||
I am not on any social network, but feel free to write me an email at `hello@jaseg.de
|
||||
<mailto:hello@jaseg.de?subject=About\ page\ on\ blog.jaseg.de>`__.
|
||||
|
||||
I do not use application-level email encryption such as S/MIME or PGP. If you need a higher level of secrecy than
|
||||
regular old email provides, please ask around for my signal contact or email me a file encrypted using `age
|
||||
<https://github.com/FiloSottile/age>`__ with one of the SSH keys listed `on my github
|
||||
<https://github.com/jaseg.keys>`__. You can find both PGP and other SSH keys that I have used in the past on the
|
||||
internet, but please consider these keys revoked, and do not use them to encrypt anything you send me.
|
||||
|
||||
Python package release signing key
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
I use this GPG key (key ID ``ED7A208EEEC76F2D``) to sign git release tags of both `gerbolyze <https://github.com/jaseg/gerbolyze>`__ and `python-mpv
|
||||
<https://github.com/jaseg/python-mpv>`__:
|
||||
|
||||
.. code::
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
mDMEXom49xYJKwYBBAHaRw8BAQdA/KrWMt2MKGIZUvlQZnWjNd6i8/ZYjRsBQqEf
|
||||
PJ8pJ+20NHB5dGhvbi1tcHYgUmVsZWFzZSBTaWduaW5nIEtleSA8cHl0aG9uLW1w
|
||||
dkBqYXNlZy5kZT6IlgQTFggAPhYhBONvdTB/Cg7C0UX/XO16II7ux28tBQJeibj3
|
||||
AhsDBQkSzAMABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEO16II7ux28thRYA
|
||||
/3Yl1RdeUGor6K0RTxce9TIBB+DpLNupJgB9f6onuocpAQC614zQ/RQ6rkGTHCwA
|
||||
ElFClWRQ5eppj0jpAuH15udqAbg4BF6JuPcSCisGAQQBl1UBBQEBB0A0mrXSv6rj
|
||||
ajCmZR4H4OtowAx477YS+yWARqo1NtdgJwMBCAeIfgQYFggAJhYhBONvdTB/Cg7C
|
||||
0UX/XO16II7ux28tBQJeibj3AhsMBQkSzAMAAAoJEO16II7ux28tMZwBAIUpHHvP
|
||||
gRW2jQuzdw1r06kItfFk/0t+mgNUQ2+vtbhzAP98BoWx7lv+bvlIbBaVgLldusj0
|
||||
pHnZI/0y3ksMBkdbBw==
|
||||
=Mr6G
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
About this site
|
||||
---------------
|
||||
|
||||
This site is made with the hugo static site generator. I made the theme myself, feel free to grab a copy at
|
||||
`git.jaseg.de <https://git.jaseg.de/blog.git/tree/themes/conspiracy?h=main>`__. The nifty auto-reflowing code embeds are
|
||||
made with some CSS magic I made that you can find in `style.css
|
||||
<https://git.jaseg.de/blog.git/tree/themes/conspiracy/assets/css/style.css?h=main&id=2fd22e30ce176d8d8a641fd371ad1623b082eaaf#n367>`__.
|
||||
The body text is typeset in Roboto Slab, created by `Christian Robertson <https://christianrobertson.com/>`__ while
|
||||
working at Google. The headlines are set in Nyght Serif, a font by `Maksym Kobuzan <https://linktr.ee/mkobuzan>`__.
|
||||
Check out their other fonts, their work is beautiful! Source code is typeset in Fira Code, a derivate by ... from
|
||||
Mozilla's `Fira Mono <https://github.com/mozilla/Fira>`__ font, designed by `Erik Spiekermann
|
||||
<https://spiekermann.com/>`__, `Ralph du Carrois <https://carrois.com/>`__, `Anja Meiners
|
||||
<https://anjameiners.com/de/hallo/>`__ and Botio Nikoltchev of Carrois Type Design, now succeeded by `bBoxType
|
||||
<https://bboxtype.com/typefaces/FiraMono/#!layout=specimen>`__ , and Patryk Adamczyk of Mozilla. The photo of mountains
|
||||
that's used in the background of this site is by `Fabrizio Conti <https://www.conti.photos/>`__ and can be found on
|
||||
`Unsplash <https://unsplash.com/photos/TUmjK7ZJgbI>`__.
|
||||
|
||||
|
Before Width: | Height: | Size: 92 KiB |
|
|
@ -1,203 +0,0 @@
|
|||
---
|
||||
title: "8seg Technical Overview"
|
||||
date: 2023-12-26T15:26:00+01:00
|
||||
summary: >
|
||||
8seg is a large-scale LED light art installation that displays text on a 1.5 meter high, 30 meter wide
|
||||
8-segment display made from cheap LED tape.
|
||||
---
|
||||
|
||||
Prologue
|
||||
--------
|
||||
|
||||
German hacker culture has this intense love for things that light up in colorful ways. Like for many others in this
|
||||
community, I have always been fascinated by LEDs. One of the first things on my pile of unfinished projects was to build
|
||||
my own LED matrix and use it to display text. When I started that project, I was still new to electronics. Back then,
|
||||
commercial LED matrices were limited to red or green color only, and were very expensive, so there was an incentive to
|
||||
build your own. At the same time, while individual LEDs were'nt expensive anymore, they hadn't started to be cheap yet,
|
||||
either. On top of the material cost, back then there were no PCB fabs, and especially no PCB assembly houses that a
|
||||
hobbyist could afford. Ultimately, I ended up never finishing this project because I felt it was more of a feat of
|
||||
material wealth than of technical prowess.
|
||||
|
||||
Over time, LEDs came down in price, and peoople started using them in all sorts of fun things. Around the mid-2010s,
|
||||
cheap-ish, ready-made tapes and chains of RGB LEDs that included WS2811 or similar digitally controllable driver chips
|
||||
led to a cambrian explosion in projects involving large amounds of colorful LEDs since suddenly, all you needed was an
|
||||
arduino and a beefy power supply to individually control an almost unlimited number of these LEDs.
|
||||
|
||||
Today, LED technology has advanced even furhter, to a point where now you can buy staggering quantities of the second
|
||||
generation of these controllable LEDs that provides better color rendering embedded in all sorts of shapes, from tapes
|
||||
through rings to grids. When I built the first matelight_ in 2013, the matelight's 640 individually-controllable LEDs
|
||||
were *a lot*. Today, you can buy a roll with several thousand channels for about the price of a nice pizza.
|
||||
|
||||
The idea behind 8seg
|
||||
--------------------
|
||||
|
||||
Living through this amazing escalation of LED technology, in 2018, I looked at a then-obsolete piece of single-color,
|
||||
dumb, non-controllable LED tape with a simple question in mind: Taking this unsophisticated artifact of yesterday's
|
||||
technology, what would be the coolest thing I could build from it? Can I buld something that not only rivals, but
|
||||
outmatches the modern controllable LED stuff? From that question, I set myself two goals. First, I wanted to keep the
|
||||
project's use of financial and labor resources reasonable. A lot of art consists of taking a simple idea, and simply
|
||||
extrapolating its implementation to a ridiculous scale at the expense of the artist's time and wallet. That wasn't the
|
||||
point I wanted to make. I wanted to make something cool from an obsolete technology, not prove how much patience I had
|
||||
soldering. My second goal was to create something that is meaningfully controllable. Controllability is much harder with
|
||||
these dumb LED tapes, but it is possible nontheless, and I wanted to test out how far you could go with it.
|
||||
|
||||
After thinking through a number of possibilities, I settled on the basics of the 8seg design I ended up realizing. The
|
||||
installation would be a banner-style display consisting of a series of characters made from non-controllable LED tape.
|
||||
The banner can be rigged up in any convenient air space, bending and folding to conform to the space's shape and size.
|
||||
The key idea behind 8seg is that it makes up for it's lack of control fidelity with sheer size. If nothing else, this
|
||||
non-controllable LED tape is *cheap*.
|
||||
|
||||
The design of a single 8seg character
|
||||
-------------------------------------
|
||||
|
||||
Each 8seg character consists of 8 *segments* of LED tape that are inter-connected through small circuit boards, four in
|
||||
the corners, and one in the center. As it turns out, 8 segments arranged in this shape are enough to display all of the
|
||||
English language's alphabet as well as numbers in a weird, but readable form.
|
||||
|
||||
The electrical design of an 8seg character has one weird trick at its core. To avoid having to run a bunch of wires from
|
||||
some kind of driver circuit board to each of the eight segments, I thought, why not use the LED tape itself instead for
|
||||
power and data transmission? Wires are heavy, expensive, and annoying to solder, so if I could find a way to
|
||||
interconnect the LED tape so that it can all be driven from a driver circuit located at one of the character's
|
||||
junctions while simultaneously powering that driver circuit, an 8seg character wouldn't need any wires at all anymore.
|
||||
|
||||
8seg achieves this feat using a circuit as shown in the diagram below. Interconnections between the LED tape segments
|
||||
are done with a small circuit board in each of the four corners. The design is rotationally symmetric, and all four of
|
||||
these boards are identicaly. The top right and bottom left corners simply use the back side of the same circuit board
|
||||
used in the top left and bottom right corners.
|
||||
|
||||
.. image:: 8seg-digit-circuit.png
|
||||
|
||||
The driver circuit sits at the center of the character and directly connects to the four diagonal segments. The key
|
||||
thought behind 8seg's driving scheme is that there are two common phases wound through the display in a zig-zag pattern
|
||||
as shown in red and blue in the schema below. These phases alternate their polarity at a high frequency. Each segment
|
||||
has its negative pole connected to one of these two phases, and can be turned on by the driver while that phase is low
|
||||
and the other phase is high. While a phase is high, the LEDs on all segments connected to that phase are reverse-biased,
|
||||
and thus these segments remain dark.
|
||||
|
||||
The positive poles of all segments are connected to the driver circuit in the center through a spiral pattern. Each arm
|
||||
of the spiral is made up of two segments, one diagonal on the inside, and one horizontal or vertical on the outside.
|
||||
The two segments on each spiral arm are on different phases, one on each of the two phases. Thus, during a single cycle
|
||||
of the two phases alternating polarity, first one of the two segments has its polarity the right way around, then the
|
||||
other. The driver can turn on the active segment by connecting the spiral control line to the positive LED supply
|
||||
voltage.
|
||||
|
||||
Both phases cross at the center where the driver circuit is located, so the driver can power itself from the two phases
|
||||
using a simple full bridge rectifier.
|
||||
|
||||
Saving copper with point of load regulation
|
||||
-------------------------------------------
|
||||
|
||||
In the beginning, I experimented with the design above, putting 12V AC on the two phases, and letting the driver switch
|
||||
its derived LED supply using some cheap MOSFETs. This simple design totally works, but it has an important shortcoming.
|
||||
|
||||
8seg is designed to be physically *very* large. This means that not only does it have a large number of LEDs that
|
||||
together need a lot of current, it also has to transmit all of that current across significant physical distances. The
|
||||
consequence of this was that in the initial design, I was looking at either needing hundreds of Euros worth of copper
|
||||
cables, or burning hundreds of Watts of electricity into heat if I were to use thinner cables. In this case, cables act
|
||||
like resistors. In a resistor, power dissipation rises with the square of the current inside the cable. This is bad for
|
||||
8seg since it means halving the amount of copper in those wires increases power dissipation in these wires fourfold.
|
||||
|
||||
Despite that downside, this square law does come with an upside, too. If we assume we have wires of a particular fixed
|
||||
diameter, if we can halve the current through those wires, we can quarter the wires' power dissipation. If we want to
|
||||
deliver the same amount of power to the LEDs as before, to halve wire current, we have to double the voltage, and add
|
||||
some circuitry on the drivers to convert that increased voltage back down to close to our LED tape's nominal 12V.
|
||||
|
||||
Alas, simply doubling the voltage leads to one question: How is it that we can pass double the voltage through our LED
|
||||
tape to the center control circuit? Isn't the LED tape made for 12V operation only, not 24V? The answer to this
|
||||
apparent problem is that the center is connected to the AC bus voltage only through the negative side of the LED tapes,
|
||||
and controls their positive sides to turn them on or off. The AC bus voltage never appears directly across any single of
|
||||
the eight segments. At the same time, a simple buck converter stepping down our new 24V bus voltage to 12V, and feeding
|
||||
the segment control transistors with that instead of feeding them straight from the rectified AC bus allows us to feed
|
||||
the segments with 12V. The only difference between this circuit and the straight 12V variant is that now, during OFF
|
||||
times, the LED tapes see a negative 24 v across them. To make sure that's not a problem, I tested a number of them with
|
||||
different LED colors and from different manufacturers, and all of them held up past the 50 V I could easily generate
|
||||
with my lab power supply.
|
||||
|
||||
Synchronous rectification
|
||||
-------------------------
|
||||
|
||||
I implemented the point-of-load regulation in a new revision of the center circuit, and built a prototype digit. When I
|
||||
tested this prototype, to my dismay, I noticed some really strange behavior. In my tests, the LED tape did not properly
|
||||
light up, and when I checked the voltages with my oscilloscope, I noticed that the center circuit's ground was floating
|
||||
several volts *below* the AC bus voltage's negative phase. How come?
|
||||
|
||||
After some head-scratching, I found that this problem was due to a simple instance of Kirchhoff's current law. Consider
|
||||
the point where the AC bus voltage's currently negative phase enters the center circuit board. Let's say that we
|
||||
dissipate 24 Watts in the segments' LEDs. In this case, at 24 Volts, 1 Ampère will flow into the center circuit's
|
||||
terminal connected to the currently positive phase, and out from the center circuit's terminal connected to the
|
||||
currently negative phase.
|
||||
|
||||
Now consider the current through the LED tape. During one half-cycle of the AC bus, the center circuit can only address
|
||||
the four segments that have their negative rail connected to the currently negative phase of the AC bus. If one of these
|
||||
four segments is currently on and dissipating our 24 Watts, that segment will be fed 2 Ampère of current from the center
|
||||
circuit through its positive rail. My mistake was that I did not consider what happened to the return current here.
|
||||
The corresponding 2 Ampère return current of course flows back through the segment's negative rail into the center
|
||||
circuit, and herein lies the issue: That negative rail is where our center circuit's supply current comes from! This
|
||||
means that according to Kirchhoff's current law, the 1 A flowing out from the center circuit at its input are adding up
|
||||
with the 2 A flowing into it. The result of this is that in the currently positive phase's connection, we get 1 A
|
||||
flowing into the center circuit, while in the negative phase connection, we get (-1) + (+2) resulting in another 1 A
|
||||
flowing into it! The only terminal where current flows *out* of the center circuit is the positive terminal connected to
|
||||
the active segment, out of which 2 A of current are flowing.
|
||||
|
||||
The big problem with this confusing scenario is that this means the bridge recitifier in our center circuit cannot work,
|
||||
since its negative-side diodes are reverse biased while any of the segments are on. We can't just add more diodes here,
|
||||
since that would just short both AC bus rails together. Instead, the solution is to add one rather chonky MOSFET in
|
||||
parallel with each of the two negative-side diodes of the bridge rectifier that are controlled by the center circuit to
|
||||
act as a sort of synchronous rectifier. When we turn on one of the segments, we have to turn on the MOSFET on the
|
||||
currently negative rail to allow the segment's return current to bypass the bridge rectifier's negative-side diode. Fun
|
||||
fact: If we turn on the wrong MOSFET out of the pair, we short the AC bus, resulting in a very quick end of life for that
|
||||
poor MOSFET.
|
||||
|
||||
Power line data communication
|
||||
-----------------------------
|
||||
|
||||
As we saw above, the driver providing power to a string of digits has to continuously alternate the polarity of its
|
||||
output voltage to provide one part of the digit circuits' multiplexing. Since we want to provide the control information
|
||||
to the center circuits through those same two wires, we can choose between a number of viable power line communication
|
||||
schemes. These schemes usually require a beefy transmitter adding a modulation at a frequency much larger than the
|
||||
underlying bus frequency, and a filter circuit at each receiver to filter that signal from the much stronger fundamental
|
||||
AC waveform. In our application, I saw two issues with these classical approaches. First, they require fairly complex
|
||||
circuitry, especially the beefy transmitter at the driver. Second, they are susceptible to attenuation with either
|
||||
changing load or over long distances, which could potentially be a problem with the high currents and long(ish) wiring
|
||||
runs 8seg needs.
|
||||
|
||||
Because of these disadvantages, I decided on another approach entirely. Instead of modulating our control signal on top
|
||||
of the AC power waveform, we modulate our control data *into* the AC power waveform. To not interfere with the display
|
||||
and cause outages or flicker, and to avoid having to blank the display during transmissions, we choose a modulating
|
||||
technique that leaves the proportions of negative and positive half-waves undisturbed. The practical realization of this
|
||||
is that instead of alternating positive and negative half-waves, we send a positive half wave for each "one" bit, and a
|
||||
negative half wave for each "zero" bit, effectively creating a phase shift keyed signal with two states with an
|
||||
180-degree phase shift, with the transmitted bit rate synchronized to twice the underlying carrier frequency.
|
||||
|
||||
The remaining question is how one can encode arbitrary binary data into a continuous stream of ones and zeros that is
|
||||
precisely 50 % ones and 50 % zeros across any time span longer than a few dozen bits. There exists a near-optimal
|
||||
solution to this question from ethernet over copper twisted pairs. In ethernet, the encoded and modulated signal passes
|
||||
through an isolation transformer to protect the ethernet transceiver from interference or dangerous voltages coming in
|
||||
through the ethernet port. For this isolation transformer to work, the modulated ethernet signal must be exactly
|
||||
balanced to avoid saturating the transformer's core with a DC offset. Ethernet solves this issue by using an encoding
|
||||
known as `8b/10b encoding`_. 8b/10b encoding is named like that because it specifies a way to produce a 10 bit codeword
|
||||
from any 8 bit input data word while guaranteeing that the resulting codewords are always precisely balanced when
|
||||
looking at two or more consecutively.
|
||||
|
||||
Framing
|
||||
-------
|
||||
|
||||
Since 8b/10b encoding maps a space of 256 data words to 1024 code words, there necessarily are a number of unused code
|
||||
words. While for some of them, leaving them unallocated is beneficial because it improves error tolerance by decreasing
|
||||
the probability of one code word turning into another undetectably when a single one of its bits is flipped, even
|
||||
accounting for that it leaves some room for other uses. In 8b/10b, these leftover code words are used for synchronizing
|
||||
the receiver to the transmitter, and for framing transmissions. Synchronization is necessary for the receiver to know
|
||||
where a code word stards, and 8b/10b has a handful of special "comma" code words that can be uniquely identified in a
|
||||
continuous stream of received ones and zeros, because no other combination of 8b/10b code words could produce the same
|
||||
sequence of ones and zeros of the comma code word anywhere.
|
||||
|
||||
The leftover code words that are not commas are useful, too. They can be used, for instance, as filler code words
|
||||
betwene actual data transmissions, or to act as framing markers denoting things like the end of a protocol message.
|
||||
|
||||
The 8seg driver produces its modulation waveform by translating all data to be transmitted into 8b/10b codes, padding
|
||||
the result with framing markers and filler codes, and copy-pasting together the corresponding AC waveform from a small
|
||||
set of pre-programmed waveform transitions.
|
||||
|
||||
.. _matelight: https://github.com/jaseg/matelight
|
||||
.. _`8b/10b encoding`: https://en.wikipedia.org/wiki/8b/10b_encoding
|
||||
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
title: Blog
|
||||
---
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="60mm"
|
||||
height="18mm"
|
||||
viewBox="0 0 60 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<rect
|
||||
x="73.275604"
|
||||
y="7.5154462"
|
||||
width="82.669907"
|
||||
height="60.687229"
|
||||
id="rect1" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-60.324998,-47.365882)">
|
||||
<path
|
||||
id="path4-6"
|
||||
style="fill:#ececec;stroke-width:0.105392;stroke-linecap:round;stroke-linejoin:round;fill-opacity:1"
|
||||
d="m 69.324799,48.865886 a 7.5,7.5 0 0 0 -7.499801,7.499801 7.5,7.5 0 0 0 3.273185,6.195487 c -0.913481,-0.983452 -1.40067,-2.340488 -1.315165,-3.825089 0.198575,-3.447783 1.631942,-4.848283 1.631942,-4.848283 l 1.590084,3.588411 c -0.48406,-2.540144 -0.201121,-4.995971 2.766756,-8.596891 a 7.5,7.5 0 0 0 -0.447001,-0.01344 z m 1.328084,0.118339 c 0.310055,1.43067 1.013609,4.202913 2.107882,5.883879 1.029143,1.580919 1.930339,2.092129 1.865519,4.55166 -0.03918,1.486422 -0.657454,2.712199 -1.732194,3.542419 a 7.5,7.5 0 0 0 3.931026,-6.596496 7.5,7.5 0 0 0 -6.172233,-7.381462 z" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
transform="matrix(0.67004375,0,0,0.67004375,30.304048,47.575216)"
|
||||
id="text1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;line-height:13.9843px;font-family:Equateur;-inkscape-font-specification:Equateur;text-decoration-color:#000000;writing-mode:lr-tb;direction:ltr;white-space:pre;shape-inside:url(#rect1);display:inline;fill:#f9f9f9;fill-opacity:1;stroke-width:0.755906;stroke-linecap:round;stroke-linejoin:round"><tspan
|
||||
x="73.275391"
|
||||
y="19.8411"
|
||||
id="tspan2">ashen</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="60mm"
|
||||
height="18mm"
|
||||
viewBox="0 0 60 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<rect
|
||||
x="73.275604"
|
||||
y="7.5154462"
|
||||
width="82.669907"
|
||||
height="60.687229"
|
||||
id="rect1" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-60.324998,-47.365882)">
|
||||
<path
|
||||
id="path4-6"
|
||||
style="fill:#000000;stroke-width:0.105392;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 69.324799,48.865886 a 7.5,7.5 0 0 0 -7.499801,7.499801 7.5,7.5 0 0 0 3.273185,6.195487 c -0.913481,-0.983452 -1.40067,-2.340488 -1.315165,-3.825089 0.198575,-3.447783 1.631942,-4.848283 1.631942,-4.848283 l 1.590084,3.588411 c -0.48406,-2.540144 -0.201121,-4.995971 2.766756,-8.596891 a 7.5,7.5 0 0 0 -0.447001,-0.01344 z m 1.328084,0.118339 c 0.310055,1.43067 1.013609,4.202913 2.107882,5.883879 1.029143,1.580919 1.930339,2.092129 1.865519,4.55166 -0.03918,1.486422 -0.657454,2.712199 -1.732194,3.542419 a 7.5,7.5 0 0 0 3.931026,-6.596496 7.5,7.5 0 0 0 -6.172233,-7.381462 z" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
transform="matrix(0.67004375,0,0,0.67004375,30.304048,47.575216)"
|
||||
id="text1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;line-height:13.9843px;font-family:Equateur;-inkscape-font-specification:Equateur;text-decoration-color:#000000;writing-mode:lr-tb;direction:ltr;white-space:pre;shape-inside:url(#rect1);display:inline;fill:#000000;fill-opacity:1;stroke-width:0.755906;stroke-linecap:round;stroke-linejoin:round"><tspan
|
||||
x="73.275391"
|
||||
y="19.8411"
|
||||
id="tspan2">ashen</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,59 +0,0 @@
|
|||
---
|
||||
title: "Project Announcement: Ashen and Yanartas"
|
||||
date: 2026-05-31T08:00:00+02:00
|
||||
summary: >
|
||||
There are exciting things ahead for the next year: I have been granted funding by both nlnet and by prototype fund
|
||||
for open-source work on an open source hardware Hardware Security Module. As a vessel for this project, I created a
|
||||
consulting company, yasec.
|
||||
---
|
||||
|
||||
I'm currently in the last days of finishing my PhD (Dr.-Ing.) in Electrical Engineering. To make sure things don't get
|
||||
boring afterwards, I've been busy looking for new opportunities. As a result, there are exciting things ahead for the
|
||||
next year: I have been granted funding by both nlnet *and* by Prototype Fund for open-source work on an Open Source
|
||||
Hardware Hardware Security Module. As infrastructure for these projects, I created a consulting company, `yasec
|
||||
<https://yasec.de>`__.
|
||||
|
||||
Prototype Fund supports Ashen, the OS for open-source HSMs
|
||||
----------------------------------------------------------
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<picture style="max-width: 10em; margin: 1em auto 1em auto">
|
||||
<source media="(prefers-color-scheme: light)" srcset="ashen-logo-text-light-plain.svg">
|
||||
<img src="ashen-logo-text-dark-plain.svg alt="Ashen project logo showing a stylized flame in a circle">
|
||||
</picture>
|
||||
|
||||
Starting June 2026, I will be working on Ashen_, an open-source software stack that provides the operating system layer
|
||||
for open-source HSMs. The project is funded as part of `Prototype Fund`_'s Class 02.
|
||||
|
||||
Compared to existing open-source HSM software that work at the application level and that don't
|
||||
consider physical attacks, this stack will provide the underlying operating system services to protect such systems from
|
||||
physical attacks. A key component of this stack will be a portable mechanism to connect hardware tamper sensors to a
|
||||
system. The stack will enable deterministic guarantees of the maximum latency until secrets are destroyed after a tamper
|
||||
alarm was raised.
|
||||
|
||||
.. _Ashen: https://yasec.de/projects/ashen/
|
||||
.. _`Prototype Fund`: https://www.prototypefund.de/
|
||||
|
||||
nlnet supports Yanartas, the OSHW HSM platform
|
||||
----------------------------------------------
|
||||
|
||||
After work on the Ashen software stack is completed, I will continue by creating Yanartas_, an Open Source Hardware
|
||||
design for a complete open-source Hardware Security Module that provides protection against advanced physical attacks
|
||||
using a security mesh based on the `Inertial HSM`_ technology I developed during my PhD. The design will be customizable
|
||||
to different use cases and payload sizes from microcontrollers to whole servers.
|
||||
|
||||
.. _Yanartas: https://yasec.de/projects/yanartas/
|
||||
.. _`Inertial HSM`: https://tches.iacr.org/index.php/TCHES/article/view/9290
|
||||
|
||||
Let's talk!
|
||||
-----------
|
||||
|
||||
In case you're interested to talk about hardware security engineering or open-source hardware, feel free to reach out
|
||||
through email or on mastodon. The projects are in an early stage, and I'm looking both for collaborators for these
|
||||
projects, and for opportunities once these projects have been completed. At this time, I only have a small amount of
|
||||
spare capacity outside of these projects, but that will change with time. I'd love to hear about *your* projects and
|
||||
your needs for specialist work in case you're interested.
|
||||
|
||||
Follow this blog's `RSS <https://jaseg.de/index.xml>`__ and follow me `on mastodon <https://chaos.social/@jaseg>`__ for
|
||||
updates!
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="35mm"
|
||||
height="17.549mm"
|
||||
viewBox="0 0 35 17.549"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<rect
|
||||
x="33.844814"
|
||||
y="193.19748"
|
||||
width="593.69446"
|
||||
height="361.01135"
|
||||
id="rect1" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-30.000001,-82.703)">
|
||||
<path
|
||||
style="font-size:21.3333px;line-height:13.9843px;font-family:Equateur;-inkscape-font-specification:Equateur;white-space:pre;fill:#e0e0e0;stroke-width:0.755906;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 33.611037,211.68229 v 0.10667 h 3.583994 l 7.082656,-15.33865 1.87733,-0.55466 v -0.10667 h -5.503991 v 0.10667 l 2.111996,0.53333 -2.218663,6.82666 -3.135995,-6.84799 2.239997,-0.512 v -0.10667 h -6.549323 v 0.10667 l 1.68533,0.512 4.543993,9.66398 c -1.727997,2.816 -3.135995,4.416 -5.717324,5.61066 z m 14.762647,-5.11999 c 1.151998,0 1.834664,-0.74667 2.794662,-1.45067 0.277333,0.78934 0.853332,1.42934 1.642664,1.42934 1.087999,0 1.941331,-1.216 2.474663,-2.112 l -0.08533,-0.0853 c -0.256,0.256 -0.874665,0.76799 -1.237331,0.76799 -0.341333,0 -0.490666,-0.46933 -0.490666,-1.13066 v -5.63199 c 0,-1.408 -1.130665,-2.816 -2.51733,-2.816 -1.62133,0 -5.205325,1.94133 -5.205325,2.83733 0,0.66133 1.919997,1.81333 3.093329,2.368 l 0.08533,-0.10667 c -0.511999,-0.704 -1.045332,-1.83466 -1.045332,-2.53866 0,-0.896 0.895999,-1.536 1.727997,-1.536 1.066665,0 1.471998,1.024 1.471998,2.24 v 2.21866 l -4.906659,1.25867 c 0,0 -0.447999,0.85333 -0.447999,1.79199 0,1.68533 1.450664,2.496 2.645329,2.496 z m -0.362666,-3.05066 c 0,-0.384 0.08533,-0.85334 0.08533,-0.85334 l 2.986662,-1.13066 v 3.072 c -0.554666,0.31999 -0.981332,0.61866 -1.578664,0.61866 -0.895999,0 -1.493331,-0.704 -1.493331,-1.70666 z m 11.818665,3.09333 c 1.727997,0 4.053327,-1.088 4.053327,-3.37067 0,-0.95999 -0.405333,-1.91999 -1.535998,-2.47466 l -2.794662,-1.38666 c -0.725332,-0.36267 -1.194665,-0.832 -1.194665,-1.536 0,-0.896 0.746666,-1.62133 1.813331,-1.62133 1.578664,0 2.19733,1.62133 2.090663,3.11466 l 0.128,0.0427 1.301331,-2.88 c -0.639999,-0.49066 -2.090663,-0.87466 -3.199995,-0.87466 -1.770663,0 -3.839994,0.98133 -3.839994,3.22132 0,0.896 0.341333,1.92 1.450665,2.45333 l 2.794662,1.344 c 0.746666,0.36267 1.151998,0.85333 1.151998,1.55733 0,1.00267 -0.810665,1.83467 -1.919997,1.83467 -1.855997,0 -3.007995,-2.38933 -2.794662,-4.11733 l -0.106666,-0.0427 -1.514665,3.28533 c 0.810666,0.74667 2.709329,1.45067 4.117327,1.45067 z m 9.96266,0 c 2.111996,0 3.221328,-1.152 4.245326,-2.688 L 73.90967,203.8103 c -0.938666,0.96 -1.813331,1.42933 -2.922662,1.42933 -2.51733,0 -3.669328,-2.43199 -3.669328,-4.69332 0,-0.17067 0,-0.32 0.02133,-0.46933 l 6.186657,-0.68267 c 0,-2.09066 -0.725332,-3.83999 -3.157328,-3.83999 -2.346663,0 -4.415993,1.94133 -5.077325,4.37332 l -1.407998,0.384 0.02133,0.128 1.279998,-0.14933 c -0.08533,0.42666 -0.128,0.85333 -0.128,1.28 0,2.92266 1.855997,5.03466 4.735993,5.03466 z m -2.38933,-7.25333 c 0.256,-1.94133 1.194665,-3.02933 2.431996,-3.02933 0.895999,0 1.493331,0.93867 1.557331,1.92 z m 12.181323,7.25333 c 1.919997,0 3.498661,-1.00267 4.586659,-2.688 l -0.128,-0.10667 c -0.895998,0.896 -2.00533,1.42933 -3.221328,1.42933 -2.751996,0 -3.797327,-2.73066 -3.797327,-4.86399 0,-2.23999 1.173331,-3.98933 2.837329,-3.98933 1.130665,0 1.87733,0.98134 1.87733,2.176 0,0.832 -0.234666,1.77067 -0.511999,2.41066 l 0.106666,0.0853 c 0.810666,-0.85333 2.495996,-2.41067 2.495996,-3.456 0,-1.06666 -1.151998,-2.06933 -2.986662,-2.06933 -3.370661,0 -6.165323,2.41067 -6.165323,5.84533 0,2.87999 1.962663,5.22666 4.906659,5.22666 z"
|
||||
id="text1"
|
||||
transform="matrix(0.58740671,0,0,0.58740671,12.90413,-25.337712)"
|
||||
aria-label="yasec" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="35mm"
|
||||
height="17.549mm"
|
||||
viewBox="0 0 35 17.549"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<rect
|
||||
x="33.844814"
|
||||
y="193.19748"
|
||||
width="593.69446"
|
||||
height="361.01135"
|
||||
id="rect1" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-30.000001,-82.703)">
|
||||
<path
|
||||
style="font-size:21.3333px;line-height:13.9843px;font-family:Equateur;-inkscape-font-specification:Equateur;white-space:pre;stroke-width:0.755906;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 33.611037,211.68229 v 0.10667 h 3.583994 l 7.082656,-15.33865 1.87733,-0.55466 v -0.10667 h -5.503991 v 0.10667 l 2.111996,0.53333 -2.218663,6.82666 -3.135995,-6.84799 2.239997,-0.512 v -0.10667 h -6.549323 v 0.10667 l 1.68533,0.512 4.543993,9.66398 c -1.727997,2.816 -3.135995,4.416 -5.717324,5.61066 z m 14.762647,-5.11999 c 1.151998,0 1.834664,-0.74667 2.794662,-1.45067 0.277333,0.78934 0.853332,1.42934 1.642664,1.42934 1.087999,0 1.941331,-1.216 2.474663,-2.112 l -0.08533,-0.0853 c -0.256,0.256 -0.874665,0.76799 -1.237331,0.76799 -0.341333,0 -0.490666,-0.46933 -0.490666,-1.13066 v -5.63199 c 0,-1.408 -1.130665,-2.816 -2.51733,-2.816 -1.62133,0 -5.205325,1.94133 -5.205325,2.83733 0,0.66133 1.919997,1.81333 3.093329,2.368 l 0.08533,-0.10667 c -0.511999,-0.704 -1.045332,-1.83466 -1.045332,-2.53866 0,-0.896 0.895999,-1.536 1.727997,-1.536 1.066665,0 1.471998,1.024 1.471998,2.24 v 2.21866 l -4.906659,1.25867 c 0,0 -0.447999,0.85333 -0.447999,1.79199 0,1.68533 1.450664,2.496 2.645329,2.496 z m -0.362666,-3.05066 c 0,-0.384 0.08533,-0.85334 0.08533,-0.85334 l 2.986662,-1.13066 v 3.072 c -0.554666,0.31999 -0.981332,0.61866 -1.578664,0.61866 -0.895999,0 -1.493331,-0.704 -1.493331,-1.70666 z m 11.818665,3.09333 c 1.727997,0 4.053327,-1.088 4.053327,-3.37067 0,-0.95999 -0.405333,-1.91999 -1.535998,-2.47466 l -2.794662,-1.38666 c -0.725332,-0.36267 -1.194665,-0.832 -1.194665,-1.536 0,-0.896 0.746666,-1.62133 1.813331,-1.62133 1.578664,0 2.19733,1.62133 2.090663,3.11466 l 0.128,0.0427 1.301331,-2.88 c -0.639999,-0.49066 -2.090663,-0.87466 -3.199995,-0.87466 -1.770663,0 -3.839994,0.98133 -3.839994,3.22132 0,0.896 0.341333,1.92 1.450665,2.45333 l 2.794662,1.344 c 0.746666,0.36267 1.151998,0.85333 1.151998,1.55733 0,1.00267 -0.810665,1.83467 -1.919997,1.83467 -1.855997,0 -3.007995,-2.38933 -2.794662,-4.11733 l -0.106666,-0.0427 -1.514665,3.28533 c 0.810666,0.74667 2.709329,1.45067 4.117327,1.45067 z m 9.96266,0 c 2.111996,0 3.221328,-1.152 4.245326,-2.688 L 73.90967,203.8103 c -0.938666,0.96 -1.813331,1.42933 -2.922662,1.42933 -2.51733,0 -3.669328,-2.43199 -3.669328,-4.69332 0,-0.17067 0,-0.32 0.02133,-0.46933 l 6.186657,-0.68267 c 0,-2.09066 -0.725332,-3.83999 -3.157328,-3.83999 -2.346663,0 -4.415993,1.94133 -5.077325,4.37332 l -1.407998,0.384 0.02133,0.128 1.279998,-0.14933 c -0.08533,0.42666 -0.128,0.85333 -0.128,1.28 0,2.92266 1.855997,5.03466 4.735993,5.03466 z m -2.38933,-7.25333 c 0.256,-1.94133 1.194665,-3.02933 2.431996,-3.02933 0.895999,0 1.493331,0.93867 1.557331,1.92 z m 12.181323,7.25333 c 1.919997,0 3.498661,-1.00267 4.586659,-2.688 l -0.128,-0.10667 c -0.895998,0.896 -2.00533,1.42933 -3.221328,1.42933 -2.751996,0 -3.797327,-2.73066 -3.797327,-4.86399 0,-2.23999 1.173331,-3.98933 2.837329,-3.98933 1.130665,0 1.87733,0.98134 1.87733,2.176 0,0.832 -0.234666,1.77067 -0.511999,2.41066 l 0.106666,0.0853 c 0.810666,-0.85333 2.495996,-2.41067 2.495996,-3.456 0,-1.06666 -1.151998,-2.06933 -2.986662,-2.06933 -3.370661,0 -6.165323,2.41067 -6.165323,5.84533 0,2.87999 1.962663,5.22666 4.906659,5.22666 z"
|
||||
id="text1"
|
||||
transform="matrix(0.58740671,0,0,0.58740671,12.90413,-25.337712)"
|
||||
aria-label="yasec" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
|
|
@ -1,209 +0,0 @@
|
|||
---
|
||||
title: "Code listings with nice line wrapping and line numbers from plain CSS"
|
||||
date: 2025-07-23T23:42:00+01:00
|
||||
summary: >
|
||||
Code listings in web pages are often a bit of a pain to use. Usually, they don't wrap on small screens. Also,
|
||||
copy-pasting code from a code listing often copies the line numbers along with the code. Finally, many
|
||||
implementations use heavyweight HTML and/or javascript, making them slow to render. For this blog, I wrote a little
|
||||
CSS hack that renders nice, wrapping code blocks with line continuation markers in plain CSS without any JS.
|
||||
---
|
||||
|
||||
Code listings in web pages are often a bit of a pain to use. Often, they don't wrap on small screens. Also, copy-pasting
|
||||
code from a code listing often copies the line numbers along with the code. Finally, many implementations use
|
||||
heavyweight HTML and/or javascript, making them slow to render (looking at you, gitlab).
|
||||
|
||||
For this blog, I wrote an implementation that renders HTML code listings entirely without JavaScript, renders line
|
||||
numbers using plain CSS such that they don't get selected with the code, and that works with the browser to wrap in a
|
||||
natural way while still supporting the little line continuation arrows that are used to show that a line was soft
|
||||
wrapped in text editors.
|
||||
|
||||
This blog is rendered as a static site using Hugo_ from a pile of RestructuredText_ documents. RestructuredText renders
|
||||
code listings using Pygments_ by default. Pygments hard-bakes the line numbers into the generated HTML, so I am using a
|
||||
`monkey-patched`_ hook that changes the line number rendering to just a bunch of empty ``<span>`` elements. The resulting
|
||||
HTML for a code block then looks like this:
|
||||
|
||||
.. code:: html
|
||||
|
||||
<pre class="code [language] literal-block">
|
||||
<span class="lineno"></span>
|
||||
<span class="line">
|
||||
<span class="[syntax highlight token]">The </span><span class="[other syntax highlight token]">code!<span>
|
||||
</span>
|
||||
<!-- ... repeat once for each source line. -->
|
||||
</pre>
|
||||
|
||||
You can find the (rather short) source of the ``rst2html`` wrapper `below <#rst2html-wrapper>`_.
|
||||
|
||||
The CSS
|
||||
-------
|
||||
|
||||
This modified HTML structure of the code listing gets accompanied by some CSS to make it flow nicely. Here is a listing
|
||||
of the complete CSS controlling the listing. The only bit that isn't included here is the actual syntax styling rules
|
||||
for the pygments tokens.
|
||||
|
||||
.. code:: css
|
||||
|
||||
/*****************************************************/
|
||||
/* Code block formatting / syntax highlighting rules */
|
||||
/*****************************************************/
|
||||
|
||||
.code {
|
||||
font-family: "Fira Code";
|
||||
font-size: 13px;
|
||||
text-align: left; /* Override default content "justify" alignment */
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-x: auto;
|
||||
display: grid;
|
||||
align-items: start;
|
||||
grid-template-columns: min-content 1fr;
|
||||
}
|
||||
|
||||
.code > .line {
|
||||
padding-left: calc(2em + 5px);
|
||||
text-indent: -2em;
|
||||
padding-top: 2px;
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
/* Make individual syntax tokens wrap anywhere */
|
||||
.code > .line > span {
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* We render line numbers in CSS! */
|
||||
.code > .lineno {
|
||||
counter-increment: lineno;
|
||||
word-break: keep-all;
|
||||
margin: 0;
|
||||
padding-left: 15px;
|
||||
padding-right: 5px;
|
||||
overflow: clip;
|
||||
position: relative;
|
||||
text-align: right;
|
||||
color: var(--c-text-muted);
|
||||
border-right: 1px solid var(--c-fg-highlight);
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
/* We also handle line continuation markers in CSS. */
|
||||
.code > .lineno::after {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
content: "\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳";
|
||||
white-space: pre;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
/* Insert the actual line number */
|
||||
.code > .lineno::before {
|
||||
content: counter(lineno);
|
||||
}
|
||||
|
||||
.code::before {
|
||||
counter-reset: lineno;
|
||||
}
|
||||
|
||||
.code .hll {}
|
||||
/* Following are about 50 lines that define the styling of each kind of pygments syntax highlight token. These lines
|
||||
all look like the following: */
|
||||
.code .c { color: var(--c-text); font-weight: 400 } /* Comment */
|
||||
|
||||
This CSS does a few things:
|
||||
|
||||
1. It renders the ``<pre>`` code listing element using a two-column CSS ``display: grid`` layout. The left column is
|
||||
used for the line numbers, and the right column is used for the code lines.
|
||||
2. It numbers the lines using a `CSS Counter`_. CSS counters are meant for things like numbering headings and such, but
|
||||
they are a perfect fit for our purpose.
|
||||
3. It inserts the counter value as the line number into the ``<span class="lineno">`` element's ``::before``
|
||||
pseudo-element. A side effect of using the ``::before`` pseudo-element is that without doing anything extra, the
|
||||
line numbers will remain outside of the normal text selection so they will neither be highlighted when selecting
|
||||
listing content, nor will they be copied when copy/pasting the listing content.
|
||||
4. It inserts a string of ``"\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳"`` into the line number span's
|
||||
``::after`` pseudo-element. This string evaluates to a sequence of unicode arrows separated by line breaks, and
|
||||
starting with an empty line. The ``::after`` pseudo-element is positioned using ``position: absolute``, and the
|
||||
parent ``<span class="lineno">`` has ``position: relative`` set. This way, the arrow pseudo-element gets placed on
|
||||
top of the lineno span without affecting the layout at all. By setting ``overflow: clip`` on the parent ``<span
|
||||
class="lineno">``, the arrow pseudo-element gets cut off vertically wherever the parent lineno element naturally
|
||||
ends.
|
||||
|
||||
The line number span is inserted into the parent ``<pre>`` element's CSS grid using ``align-self: stretch``, which
|
||||
causes it to vertically stretch to fill the available space. Since the line number span only contains the line number,
|
||||
its minimum height is a single line. As a result, it will stretch higher only when the corresponding code line in the
|
||||
right grid column stretches vertically because of line wrapping. When that happens, part of the arrow pseudo-element
|
||||
starts showing through from behind the ``overflow: clip`` of the line number span, and one arrow gets rendered for each
|
||||
wrapped listing line.
|
||||
|
||||
When the page is too narrow, we don't want the code listing's lines to wrapp into a column of single characters. To
|
||||
prevent that, we simply set a ``min-width`` on the ``<span class="line">`` in the right column, and set ``overflow-x:
|
||||
auto`` on the listing ``<pre>``. This results in a horizontal scroll bar appearing whenever the listing gets too narrow.
|
||||
|
||||
You can try out the line wrapping by resizing this page!
|
||||
|
||||
rst2html wrapper
|
||||
----------------
|
||||
|
||||
Here is the python ``rst2html`` wrapper that monkey-patches code rendering. I made hugo invoke this while building the
|
||||
page by simply overriding the ``PATH`` environment variable.
|
||||
|
||||
.. code:: python
|
||||
|
||||
#!/usr/bin/env python3
|
||||
# Based on https://gist.github.com/mastbaum/2655700 for the basic plugin scaffolding
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
import docutils.core
|
||||
from docutils.transforms import Transform
|
||||
from docutils.nodes import TextElement, Inline, Text
|
||||
from docutils.parsers.rst import Directive, directives
|
||||
from docutils.writers.html4css1 import Writer, HTMLTranslator
|
||||
|
||||
|
||||
class UnfuckedHTMLTranslator(HTMLTranslator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.in_literal_block = False
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
# Insert an empty "lineno" span before each line. We insert the line numbers using pure CSS in a ::before
|
||||
# pseudo-element. This has the added advantage that the line numbers don't get included in text selection.
|
||||
# These line number spans are also used to show line continuation markers when a line is wrapped.
|
||||
self.in_literal_block = True
|
||||
self.body.append(self.starttag(node, 'pre', CLASS='literal-block'))
|
||||
self.body.append('<span class="lineno"></span><span class="line">')
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
self.in_literal_block = False
|
||||
self.body.append('\n</span></pre>\n')
|
||||
|
||||
def visit_Text(self, node):
|
||||
if self.in_literal_block:
|
||||
for match in re.finditer('([^\n]*)(\n|$)', node.astext()):
|
||||
text, end = match.groups()
|
||||
|
||||
if text:
|
||||
super().visit_Text(Text(text))
|
||||
|
||||
if end == '\n':
|
||||
if isinstance(node.parent, Inline):
|
||||
self.depart_inline(node.parent)
|
||||
self.body.append(f'</span>\n<span class="lineno"></span><span class="line">')
|
||||
if isinstance(node.parent, Inline):
|
||||
self.visit_inline(node.parent)
|
||||
|
||||
else:
|
||||
super().visit_Text(node)
|
||||
|
||||
|
||||
html_writer = Writer()
|
||||
html_writer.translator_class = UnfuckedHTMLTranslator
|
||||
docutils.core.publish_cmdline(writer=html_writer)
|
||||
|
||||
.. _Hugo: https://gohugo.io/
|
||||
.. _RestructuredText: https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html
|
||||
.. _Pygments: https://pygments.org/
|
||||
.. _`monkey-patched`: https://en.wikipedia.org/wiki/Monkey_patch
|
||||
.. _`CSS Counter`: https://developer.mozilla.org/en-US/docs/Web/CSS/counter
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
---
|
||||
title: "75 Million Lives, Two Keys"
|
||||
date: 2025-01-05T23:42:00+01:00
|
||||
draft: true
|
||||
---
|
||||
|
||||
2025 has begun. In this new year, with its new national healthcare record system, the country of Germany will start one
|
||||
of the largest rollouts of a cryptographic system in history. While the system has received scrutiny as well as
|
||||
resulting harsh criticism from a number of parties ranging from NGOs to everyday civilians, the system has received
|
||||
surprisingly little attention from the academic applied cryptography crowd. Additionally, previous criticism of
|
||||
the system has largely revolved around organizational issues. While valid, we belive that some cryptographic issues at
|
||||
the core of the system have escaped attention unitl now. In particular, at the core of the system is a key escrow system
|
||||
that contains several questionable design choices and that in its overall design seems out of place in 2025.
|
||||
|
||||
The aim of the system is to serve as a shared storage for all healthcare records of a person. In the system, a person's
|
||||
entire patient file with all documentation on the treatment process including test results, images and other raw data
|
||||
will be stored in something vaguely resembling cloud storage such that all healthcare providers that the person visits
|
||||
can access the entire file. This centralized, synchronized storage eliminates the need for transferring this data
|
||||
between hospitals and doctors offices by fax, mail or physical media as it was common practice until now. After a
|
||||
development and testing phase lasting approximately five years, the German government decided to roll out the system to
|
||||
everybody insured under Germany's mandatory national health insurance scheme, totalling approximately 75 million people,
|
||||
on January 15th 2025.
|
||||
|
||||
In this article, we will give an overview of the system's cryptographic design before highlighting a few odd
|
||||
design choices that could amount to a viable attack vector to the powerful adversaies
|
||||
|
||||
## Context and involved parties
|
||||
|
||||
Germany has a national, mandatory health insurance system. The system is open to any permanent resident of the country
|
||||
irrespective of citizenship. The system is mandatory in that while residents can choose between a number of both
|
||||
publically owned as well as private healthcare providers, it is not possible to opt out of the system. The public health
|
||||
insurance providers cover approximately 90% of German residents. These providers are organized in an umbrella
|
||||
organization named "GKV Spitzenverband". The resposibility of this umbrella organization largely revolves around
|
||||
negotiating prices with pharmaceutical companies and with healthcare providers as a publically sanctioned cartel, but
|
||||
also includes the specification and operation of shared IT infrastructure for billing and data exchange between
|
||||
healthcare providers.
|
||||
|
||||
While GKV Spitzenverband is the party that ultimately holds responsibility for the regulatory administration of national
|
||||
healthcare IT infrastructure, it has delegated large parts of both the technical specification of this infrastructure as
|
||||
well as its day-to-day operation to Gematik GmbH, a state-owned limited liability corporation created specifically for
|
||||
the purpose of developing and implementing national healthcare IT standards. The electronic healthcare record system we
|
||||
describe in this article was standardized and implemented by Gematik GmbH under the direction of GKV Spitzenverband.
|
||||
|
||||
Healthcare providers in Germany need to be registered with GKV Spitzenverband to serve members of public health
|
||||
insurance providers. Since these public providers constitute approximately 90% market share, the vast majority of
|
||||
healthcare providers are registered this way.
|
||||
|
||||
Before the new national health record system, a number of healthcare IT processes have already been standardized and
|
||||
implemented by the parties above. In particular, every insured person already owns a cryptographic smartcard that acts
|
||||
as their proof of identity when accessing healthcare services. On the other side of such transactions, healthcare
|
||||
providers are likewise identified by cryptographic smartcards. Until now, these cards were used to facilitate billing of
|
||||
services from healthcare providers to insurers and to transfer prescriptions from prescribing doctors to pharmacies.
|
||||
|
||||
A central role in this existing infrastructure is assumed by VPN gateways that link healthcare providers to
|
||||
the centrally-run backend infrastructure. Gematik GmbH calls these devices "Konnektor". They are specially-built
|
||||
hardware devices that contain multiple smart cards to authenticate the VPN connection towards the backend, and besides
|
||||
acting as a standard VPN gateway for client applications in the healthcare provider's network to tunnnel their backend
|
||||
requests through, the Konnektors also perform cryptographic operations in some of Gematik GmbH's protocols, such as
|
||||
authenticating certain requests using signatures.
|
||||
|
||||
## Design principles
|
||||
|
||||
The new health record system was built on top of the existing infrastructure described above. In particular, access to
|
||||
health records is managed through keys stored in the patient's and the healthcare provider's existing smartcards, and
|
||||
all backend communication is tunneled through the existing VPN. Access to the files is mediated through the healthcare
|
||||
provider's existing patient management software. While in early drafts of the system, access to healthcare records
|
||||
through the patient's smartcard was gated behind a PIN, the impracticality of making the entire patient populace
|
||||
remember PINs led the implementors to scrap this provision, meaning that the patient's smartcard is all a healthcare
|
||||
provider needs to access the patient's record.
|
||||
|
||||
A critical cornerstone in the system's design is that the system's designers decided that a lost smartcard should not
|
||||
lead to any data loss. As a consequence of this decision, while some of the record's access keys are kept on the
|
||||
patient smartcard, in contravention to conventional smartcard designs the same keys are kept accessible in a centralized
|
||||
key escrow system named "Schlüsselgenerierungsdienst" and abbreviated as SGD. Furthermore, these keys are not generated
|
||||
on the smartcard either -- instead, the key escrow system generates these access keys, one copy of which is then
|
||||
transmitted and stored inside the smartcard.
|
||||
|
||||
The system supports re-issuing a smartcard to gain access to a healthcare record. Since the record's privacy pivots on
|
||||
this process, the system incorporates some organziational countermeasures that aim to make it hard to gain access to a
|
||||
re-issued copy of a patient smartcard without the patient's help or otherwise multiple colluding parties.
|
||||
|
||||
## Cryptographic design
|
||||
|
||||
|
||||
|
||||
## The implied adversary model
|
||||
|
||||
While Gematik GmbH publishes detailed specifications of the systems they standardize, these specifications and some
|
||||
associated implementation guidelines are about the extent of public information. Software implementations are being kept
|
||||
secret, and while standardization results are available, a large fraction of design rationale is discussed behind closed
|
||||
doors. From an academic perspective, the most glaring omission in Gematik GmbH's public documents is any definition of a
|
||||
threat model or an adversary model. As a result of this, we will deduce an adversary model below by contextualizing the
|
||||
published standards in the national healthcare setting. We will base our further analysis of the system on this
|
||||
adversary model.
|
||||
|
||||
|
||||
|
||||
## Previous reviews and audits of the system
|
||||
|
||||
[0] https://www.destatis.de/DE/Themen/Arbeit/Arbeitsmarkt/Qualitaet-Arbeit/Dimension-2/krankenversicherungsschutz.html
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
---
|
||||
title: "Hardware Security Module Basics"
|
||||
date: 2019-05-17T15:29:20+08:00
|
||||
summary: >
|
||||
I gave a short introduction into Hardware Security Modules at our university workgroup, including an overview on
|
||||
interesting research directions.
|
||||
---
|
||||
|
||||
On May 17 2019 I gave a short presentation on the fundamentals of hardware security modules at the weekly seminar of
|
||||
Prof. Mori's security research working group at Waseda University. The motivation for this was that outside of low-level
|
||||
hardware security people and people working in the financial industry HSMs are not thought about that often. In
|
||||
particular most network or systems security people would not consider them an option. Also it could turn out to be
|
||||
really interesting to think about what could be done with an HSM in conjunction with modern cryptography (instead of
|
||||
just plain old RSA-OAEP and AES-CBC).
|
||||
|
||||
`Click here to download a PDF with the slides for this talk. <mori_semi_hsm_talk_web.pdf>`__
|
||||
|
||||
Ideas for research in HSMs
|
||||
==========================
|
||||
|
||||
Preparing for this talk brought me back to some research ideas I've been working on for a while now. Since I'm not sure
|
||||
I'll find the time to properly research this topic, I thought it would be great to write down some rought outlines first
|
||||
for future reference.
|
||||
|
||||
The Problem with current HSM tech
|
||||
---------------------------------
|
||||
|
||||
Currently, HSMs are only used in certain specific niche applications such as certificate authority key management and
|
||||
financial transaction data handling. One key reason for this is that HSMs currently don't provide the affordances that
|
||||
would be needed for them to be adopted more widely by the cryptographic and security engineering community. As far as I
|
||||
can tell, the two core missing affordances are:
|
||||
|
||||
1. To be more widely adopted, HSMs must become less expensive. Currently, they go for several tens of thousands of Euro,
|
||||
which puts them outside most budgets.
|
||||
2. To be more widely adopted, HSMs must provide the standardized programming interfaces familiar to cryptographic
|
||||
developers. Currently, every HSM vendor has their own custom cryptographic API and a developer will have to train on
|
||||
one specific vendor's tooling. Furthermore, any documentation of these internals is kept secret behind NDAs. This
|
||||
constitutes a high barrier to entry, decreasing adoption in particular with young developers accustomed to
|
||||
open-source ecosystems.
|
||||
|
||||
Attacking cost of implementation
|
||||
--------------------------------
|
||||
|
||||
The first issue can be addressed by simply creating a viable low-cost alternative. There is no fundamental technical
|
||||
reason for the high cost of HSMs. This cost is instead due to manufacturers trying to recoup their expenses for R&D as
|
||||
well as certification from the small volumes HSMs are sold in.
|
||||
|
||||
Compared to system integration and certification the pure R&D cost of HSM defense mechanisms themselves is not too high
|
||||
in an academic context it should be feasible to develop a sort of HSM blueprint that can then be cheaply produced by
|
||||
anyone in need. Since the application areas outlined here are far from the core business areas of the clients of
|
||||
established HSM vendors this would most likely not be a realistic threat to any established vendor's business and a
|
||||
co-existence of both should not pose any problems in the short term.
|
||||
|
||||
Benefits of an academic HSM standard
|
||||
------------------------------------
|
||||
|
||||
Tackling the high cost of current HSM hardware with an open-source HSM blueprint would yield
|
||||
several academic advantages beyond cost reduction.
|
||||
|
||||
1. An open-source blueprint could serve as an academic reference design to evaluate and compare other HSM designs
|
||||
against. For instance this would not only allow quantifying the effectiveness of academic security measures but also
|
||||
allow an evaluation of commercial HSMs.
|
||||
2. An open-source blueprint could stimulate academic research in this academically very quiet albeit commercially
|
||||
important area. This research would ultimately benefit everyone employing HSMs by raising security standards in the
|
||||
field. Since HSMs are never solely relied upon for overal system security both defensive and offensive security
|
||||
research would yield these benefits.
|
||||
3. An open-source blueprint would encourage new people to get into the field and both apply HSMs to practical problems
|
||||
as well as improve HSMs themselves. Currently, this is highly discouraged due to the strictly proprietary nature of
|
||||
all available systems.
|
||||
4. Finally, developing an open-source HSM blueprint might yield new findings in adjacent academic areas due to the
|
||||
hightly multi-disciplinary nature of security research in general and HSM design in particular.
|
||||
|
||||
Scope of an academic HSM standard
|
||||
---------------------------------
|
||||
|
||||
An academic HSM blueprint would need to be flexible so that researchers can adapt it to their particular problem. A
|
||||
modular architecture would lend itself to this flexibility. Fundamentally, there would be three components to this
|
||||
architecture. First, a **base** containing infrastructure such as the surveillance microcontroller, power supplies,
|
||||
power supply filtering and hardware DPA countermeasures, and possibly a standardized mechanical and electrical
|
||||
interface.
|
||||
|
||||
Next to the base, a system integrator would put their *payload*. The nature of this payload is intentionally kept
|
||||
unspecified, and it might be anything from a cryptographic microcontroller to a small embedded system such as a
|
||||
raspberry pi single board computer. Keeping the *payload* open like this achieves two benefits: It gives the HSM
|
||||
blueprint's user *their* familiar tooling and the hardware *they* need, allowing fast adoption. Someone well-versed in
|
||||
e.g. Javascript could literally implement their cryptography in Javascript, run it on an off-the-shelf raspberry pi and
|
||||
just apply the HSM blueprint around it. In addition, keeping the *payload* open reduces the scope of what needs to be
|
||||
implemented. Building a general SDK on top of something like a bare ARM SoC such as a TI OMAP or a Freescale/NXP IMX
|
||||
would be a considerable additional burden, on top of the actual HSM design. Keeping the *payload* open allows research
|
||||
to concentrate on the actual point, the HSM design.
|
||||
|
||||
The final and most important component would be a set of *security measures* that can be combined with the base to
|
||||
form the final HSM. Each of these *security measures* would entail a detailed specification of its design, manufacture
|
||||
and security properties. These *security measures* could be simple things like tamper switches or potting, but could
|
||||
also be complex things like security meshes.
|
||||
|
||||
Given these three components -- *base*, *payload* and *security measures* as detailed specifications any engineer should
|
||||
be able to design and manufacture a HSM customized to their needs. Unifying these three components within the HSM
|
||||
blueprint would be a set of reference designs. Each reference design would implement a particular parametrization of the
|
||||
three architectural components with a physical hole cut out where the payload would go.. These reference designs would
|
||||
for one serve to guide any implementer on the customization and integration of their own derivation from the blueprint.
|
||||
In addition it would serve as an extremely simple, low-cost point of entry into the ecosystem. A curious researcher
|
||||
could simply replicate the reference design and put their existing payload inside. Practically this might mean e.g. a
|
||||
researcher having PCBs produced according to the design files for a reference design for a mesh-based HSM, producing
|
||||
their own mesh, physically glueing a raspberry pi SBC into the middle of it, and potting the resulting system. Given the
|
||||
ease of prototype PCB fabrication today this would realistically allow evaluation of HSM technologies on a budget that
|
||||
is orders of magnitude less than the cost of current HSMs.
|
||||
|
||||
Research ideas for tamper detection mechanisms
|
||||
==============================================
|
||||
|
||||
The core component of an HSM blueprint would be a suite of tamper detection mechanisms. Following are a few ideas on how
|
||||
to improve on the current state of the art of membrane tamper switches plus temperature sensors plus PCB and printed
|
||||
security meshes plus potting.
|
||||
|
||||
DIY or small lab mesh production
|
||||
--------------------------------
|
||||
**Analog sensing** meshes are a proven technology where instead of just monitoring for continuity and shorts, analog
|
||||
parameters of the mesh traces such as inductance and mutual capacitance are monitored. In 2019, `Immler et al. published
|
||||
a paper <https://tches.iacr.org/index.php/TCHES/article/view/7334>`__ where took this principle and turned it all the
|
||||
way up. They directly derived a cryptographic secret from the analog properties of their HSM's security mesh in an
|
||||
attempt to built a `Physically Unclonable Function, or PUF
|
||||
<https://en.wikipedia.org/wiki/Physical_unclonable_function>`__. The idea with PUFs is that they reproduce some entropy
|
||||
that comes from random tolerances of their production process. The same PUF will always yield (approximately) the same
|
||||
key, but since you cannot control these random production variations, in practice the resulting PUF cannot be cloned.
|
||||
Note however, that its secrets can of course be copied if you find a way to read them out.
|
||||
|
||||
As Immler et al. demonstrated in their paper, you don't need any secret sauce to create an analog mesh sensing circuit.
|
||||
All you need are a bunch of (admittedly, expensive) off-the-shelf analog ICs. The interesting bit here is that by
|
||||
applying more advanced analog sensing, weaknesses of an otherwise coarse mesh desing could maybe be alleviated. That is,
|
||||
instead of monitoring a very fine mesh for continuity, you could instead closely monitor inductance and capacitance of a
|
||||
more coarse mesh. This trade-off between sensing circuit complexity (resp. cost) and mesh production capabilities may
|
||||
allow someone with a poorly equipped lab to still make a decent HSM. The question is, how do you produce a "decent" mesh
|
||||
given only basic tools? Here are some ideas.
|
||||
|
||||
**3D metal patterning techniques** refers to any technique for producing thin, patterned metal structures on a
|
||||
three-dimensional plastic substrate. The basic process would consist of 3D-printing the polymer substrate, depositing a
|
||||
thin metal layer on top and then patterning this metal layer. A good starting point here would be the recent work of
|
||||
`Ben Kraznow`_ on this exact thing.
|
||||
|
||||
**Copper filament methods** would be any method embedding copper wire from a spool in some resin or other matrix. This
|
||||
could mean either of a systematic approach of carefully winding or folding the copper wire into patterns or a
|
||||
non-systematic approach of simply stuffing a large tangle of copper wire into a small space. The main challenge with the
|
||||
former would be to find a non-tedious way of production. The main challenge with the latter would be to find process
|
||||
parameters that guarantee complete coverage of the HSM without holes or other areas of lower sensitivity to intrusions.
|
||||
Both approaches would require careful consideration of the overall design including the polymer resin supporting
|
||||
structure to ensure sensitivity against attacks since copper wire is mechanically much stronger than the micrometre-thin
|
||||
metal coatings used in patterning techniques.
|
||||
|
||||
Envelope measurement
|
||||
--------------------
|
||||
|
||||
Finally, I think there is another set of currently under-utilized tamper-detection methods that would be very
|
||||
interesting to explore. I am not aware of an academic term for these, so I am just going to dub them *envelope
|
||||
measurement* here.
|
||||
|
||||
The fundamental apporach of a mesh is to build a physical security envelope (the mesh) that physically detects when it
|
||||
is disturbed (open or short circuits). This approach works well but has the disadvantage that these meshes are rather
|
||||
complex to manufacture since effectively every part of them is acting as a sensing element. A conceptually more complex
|
||||
but in practice potentially simpler approach might be to split the functions of security envelope and sensing element.
|
||||
This would mean that in place of the mesh, some form of passive element such as metal foil forms the security envelope
|
||||
which is then checked for tampering using a very sensitive sensor inside. This remote-sensing approach might simplify
|
||||
the manufacture of the envelope itself and thus yield a design that is more easily customized. Following are a few ideas
|
||||
on how to approach this envelope measurement problem.
|
||||
|
||||
**Ultrasonic** If the HSM is potted, a few ultrasonic transducers could be added inside the potting. With several
|
||||
transducers, any one could be used to transmit ultrasound while the others measure complex phase and energy of the
|
||||
signal they receive. The circuitry for this could be made fairly simple if using a static transmit frequency or a low
|
||||
chirp rate by using a homodyne receiver built around a comparator fed into some timers. This approach would likely
|
||||
detect any mechanical attack and would also rule out chemical attacks involving liquids (though starting from which
|
||||
amount of liquid depends on receiver sensitivity). The main disadvantages might be high power consumption and cost and
|
||||
size of the ultrasonic transducers. Traditional cheap transducers made for air as a transmission medium are fairly large
|
||||
and might not adequately couple into potting compound. If somehow one could convince a standard small piezo element to
|
||||
do the same job that would be great as far as cost and size are concerned. A concern in some fringe use cases might be
|
||||
suceptibility to ambient noise, though this could easily be reduced at the expense of space and heat dissipation
|
||||
capacity by adding sound dampening on the outside. A likely attack vector against this approach might be using a laser
|
||||
cutter to drill a hole through the potting compound, then inserting probes carefully chosen to not couple too much
|
||||
to the potting compound ultrasonically.
|
||||
|
||||
**Light** In either an unpotted HSM or one potted with a transparent (at some wavelengths) potting compound one could
|
||||
embed LEDs and photodiodes in a similar setup to the ultrasonic setup described above. In contrast to the ultrasound,
|
||||
the LEDs would literally have to light up the HSM's interior and shadows might be an issue since the HSM is likely some
|
||||
flat rectangular shape. A possible solution to this would be to coat both the embedded payload and the lid with some
|
||||
highly reflective paint such as some glossy silver paint or simple white paint. The basic approach might be as simple as
|
||||
simply turning on several LEDs distributed throughout the HSM in turn and measuring amplitude at several photodetectors,
|
||||
or as complex as doing a LIDAR-like phase measurement sweeping through a range of frequencies to determine not only
|
||||
absorption but also phase/distance characteristics between emitter LED and detector photodiode. Using some high-gain TIA
|
||||
along with a homodyne detector (lock-in amplifier) and changing emitter intensity, very precise measurements of both
|
||||
absorption and phase might be possible, as might be measurements through almost opaque, diffuse potting compounds such
|
||||
as a grey epoxide resin. The main disadvantages of this method would likely be the need to thoroughly light-proof the
|
||||
entire HSM (likely by wrapping it in metal foil) and the potentially high cost of transmitter and receiver circuitry
|
||||
(nice TIAs aren't cheap). To be effective against attacks using e.g. very fine drills and probes the system would likely
|
||||
have to be very sensitive.
|
||||
|
||||
**Radar** Finally, one could turn to standard radar techniques to fingerprint the inside of the HSM. The goal here would
|
||||
be fingerprinting instead of mapping since only changes need to be detected. In this approach one could use homodyne
|
||||
detection to improve sensitivity and reduce receiver complexity, and sweep frequencies similar to an FMCW radar (but
|
||||
probably without exploiting the self-demodulation effect). Besides high cost, this approach has two disadvantages.
|
||||
First, such a system would likely not go beyond 24GHz or maybe 40GHz due to component availability issues. Even at 40GHz
|
||||
the wavelength in the potting compound would be in the order of magnitude of several millimeters. Fine intrusions using
|
||||
some tool chosen to not interact too much with the EM field inside the HSM such as a heated ceramic needle or simply a
|
||||
laser cutter might not be detectable using this approach. In any case, this system would certainly not be able to detect
|
||||
small holes piercing the HSM enclosure. The HSM enclosure would have to be made into an RF shield, likely by using some
|
||||
metal foil in it.
|
||||
|
||||
Overall in the author's opinion these three techniques are most promising in order *Light*, *Ultrasonic*, *Radar*. Light
|
||||
would prbably provide the best sensitivity at expense of some cost. Ultrasonic might be used in conjunction with light
|
||||
to cover some additional angles since it is potentially very low-cost. Radar seems hard to engineer into a solution that
|
||||
works reliably and also would likely be at least an order of magnitude more expensive than the other two technologies
|
||||
while not providing better sensitivity.
|
||||
|
||||
.. _`Ben Kraznow`: https://www.youtube.com/watch?v=Z228xymQYho
|
||||
.. _affordances: https://en.wikipedia.org/wiki/Affordance
|
||||
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
---
|
||||
title: "New Paper on Inertial Hardware Security Modules"
|
||||
date: 2021-11-23T23:42:20+01:00
|
||||
summary: >
|
||||
Paper announcement: We have published a paper on how you can DIY a tamper-sensing hardware security module from any
|
||||
single-board computer using a moving tamper-sensing mesh made from cheap PCBs.
|
||||
---
|
||||
|
||||
World's First DIY HSM
|
||||
=====================
|
||||
|
||||
Last week, Prof. Dr. Björn Scheuermann and I have `published our first joint paper on Hardware Security Modules
|
||||
<https://tches.iacr.org/index.php/TCHES/article/view/9290>`__. In our paper, we introduce Inertial Hardware Security
|
||||
Modules (IHSMs), a new way of building high-security HSMs from basic components. I think the technology we demonstrate
|
||||
in our paper might allow some neat applications where some civil organization deploys a service that no one, not even
|
||||
they themselves, can snoop on. Anyone can built an IHSM without needing any fancy equipment, which makes me optimistic
|
||||
that maybe the ideas of the `Cypherpunk movement <https://www.activism.net/cypherpunk/manifesto.html>`__ aren't obsolete
|
||||
after all, despite even the word "crypto" having been co-opted by radical capitalist environmental destructionists.
|
||||
|
||||
An IHSM is basically an ultra-secure enclosure for something like a server or a raspberry pi that even someone with
|
||||
unlimited resources would have a really hard time cracking without destroying all data stored in it. The principle of an
|
||||
IHSM is the same as that of a `normal HSM`_. You have a payload that contains really secret data. There's really no way
|
||||
to prevent an attacker with physical access to the thing from opening it given enough time and abrasive discs for their
|
||||
angle grinder. So what you do instead is that you make it self-destruct its secrets within microseconds of anyone
|
||||
tampering with it. Usually, such HSMs are used for storing credit card pins and other financial data. They're expensive
|
||||
as fuck, all the while being about the same processing speed as a smartphone. Traditional HSMs use printed or
|
||||
lithographically patterned conductive foils for their security mesh. These foils are not an off-the-shelf component and
|
||||
are made in a completely custom manufacturing process. To create your own, you would have to re-engineer that entire
|
||||
process and probably spend some serious money on production machines.
|
||||
|
||||
Inertial HSMs take the concept of traditional HSMs, but replace the usual tamper detection mesh with a few security mesh
|
||||
PCBs. These PCBs are coarser than traditional meshes by orders of magnitude, and would alone not even be close to enough
|
||||
to keep out even a moderately motivated attacker. IHSMs fix this issue by spinning the entire tamper detection mesh at
|
||||
very high speed. To tamper with the mesh, an attacker would have to stop it. This, in turn, can be easily detected by
|
||||
the mesh's alarm circuitry using a simple accelerometer as a rotation sensor.
|
||||
|
||||
In our paper, we have shown a working prototype of the core concepts one needs to build such an IHSM. To build an IHSM
|
||||
you only need a basic electronics lab. I built the prototype in our paper at home during one of Germany's COVID
|
||||
lockdowns. You can have a look at our code and CAD on `my git <https://git.jaseg.de/ihsm.git>`__. What is missing right
|
||||
now is an integration of all of these fragments into something cohesive that an interested person with the right tools
|
||||
could go out and build. We are planning to release this sort of documentation at some point, but right now we are
|
||||
focusing our effort on the next iteration of the design instead. Stay tuned for updates ;)
|
||||
|
||||
.. _`normal HSM`: {{<ref "blog/hsm-basics/index.rst">}}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
---
|
||||
title: "Getting the .ipynb Notebook File Location From a Running Jupyter Lab Notebook"
|
||||
date: 2025-06-29T23:42:00+01:00
|
||||
summary: >
|
||||
If you need to get the path of the ipynb file in a running #Jupyter notebook, this one-liner will do the trick. It
|
||||
seems chatgpt is confused, and a bunch of other approaches on the web look fragile and/or unnecessarily complex to
|
||||
me.
|
||||
---
|
||||
|
||||
If you need to get the path of the ipynb file in a running #Jupyter notebook, this one-liner will do the trick. It seems
|
||||
chatgpt is confused, and a bunch of other approaches on the web look fragile and/or unnecessarily complex to me.
|
||||
|
||||
.. code:: python
|
||||
|
||||
import sys
|
||||
Path(json.loads(Path(sys.argv[-1]).read_bytes())['jupyter_session'])
|
||||
|
||||
The way this works is that for each notebook, jupyter starts a python "kernel" process that actually runs the notebook's
|
||||
code. That kernel gets a json file with info on the notebook's location on the disk passed through its command line.
|
||||
Since we're running code in that exact python process, we can just grab that json file from sys.argv, and read it
|
||||
ourselves.
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
---
|
||||
title: "Kicad Mesh Plugin"
|
||||
date: 2020-08-18T13:15:39+02:00
|
||||
summary: >
|
||||
I wrote a little KiCad plugin that you can use to create security meshes, heaters and other things where you need
|
||||
one or more traces cover the entire surface of a PCB. The plugin supports arbitrary PCB shapes, cutouts, and can
|
||||
route around existing footprints and traces on the PCB.
|
||||
---
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/anim.webp" style="max-width: 20em">
|
||||
</figure>
|
||||
|
||||
Tamper Detection Meshes
|
||||
=======================
|
||||
|
||||
Cryptography is at the foundation of our modern, networked world. From email to card payment infrastructure in brick and
|
||||
mortar stores, cryptographic keys secure almost every part of our digital lives againts cybercriminals or curious
|
||||
surveillance capitalists. Without cryptography, many of the things we routinely do in our lives such as paying for
|
||||
groceries with a credit card, messaging a friend on `Signal <https://signal.org>`_ or unlocking a car with its keyfob
|
||||
would not be possible. The security of all of these systems in its core lies on the secrecy of cryptographic keys.
|
||||
Systems differ in what kind of keys they use, how often these keys are replaced and the intricacies of the cryptographic
|
||||
operations these keys fit into but all have in common that their security relies on keeping the keys secret.
|
||||
|
||||
In practice, this secrecy has been implemented in many different ways. Mass-market software such as browsers or
|
||||
messenger apps usually relies on some operating system facility to tell the computer "*please keep this piece of memory
|
||||
away from all other applications*". While on desktop operating systems usually this does not provide much of a barrier
|
||||
to other programs on the same computer, on modern mobile operating systems this approach is actually quite secure.
|
||||
However, given sufficient resources no security is perfect. All of these systems can be compromised if the host
|
||||
operating system is compromised sufficiently, and for organizations with considerable resources a market has sprung up
|
||||
that offers turn-key solutions for all wiretapping needs.
|
||||
|
||||
In some applications, this level of security has not been considered sufficient. Particularly financial infrastructure
|
||||
is such a high-profile target that a lot of effort has been put into the security of cryptographic implementations. The
|
||||
best cryptographic algorithm is useless if it is run on a compromised system (from that system's point of view anyway).
|
||||
One of the core cryptographic components in financial applications are smartcards like they are used as payment cards in
|
||||
most countries nowadays. These smartcards contain a small, specialized cryptographic microcontroller that is designed to
|
||||
be hard to tamper with. Though one of the design goals of the system is to reduce the amount of sensitive information
|
||||
stored on the card, things such as copying of a card can only be hindered by making the chip hard to read out.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/modern_art.svg" style="max-width: 20em">
|
||||
</figure>
|
||||
|
||||
With smartcards being the means of choice on one side of the counter in electronic payments, on the other side of the
|
||||
counter a different technology prevails. Attacks on payment terminals are bound to have much more dire consequences than
|
||||
attacks on individual cards since one terminal might see hundreds of cards being read every day. For this reason, the
|
||||
level of attack countermeasures employed in these terminals is a considerable step up from bare smartcards. While a
|
||||
smartcard is made physically hard to tamper, it does not have a battery and it can only detect tampering once it is
|
||||
powered by a reader. This allows for well-equipped attackers to use tools such as Focused Ion Beam (FIB) workstations to
|
||||
circumvent the smartcard's defences while it is powered down, and then power up the card to carry out the actual attack.
|
||||
|
||||
The answer to this problem in electronic payment infrastructure is called *Hardware Security Module*, or HSM. An HSM is
|
||||
similar to a smartcard in its function (cryptographic processing using keys that are meant to never leave the protection
|
||||
of the HSM). The one major between the two is that an HSM has its own battery and is continuously powered from its
|
||||
manufacture to the day it is scrapped. If the HSM looses power at any point in time, it uses a small amount of energy
|
||||
stored internally to securely wipe all cryptographic secrets from its memory within a few milliseconds.
|
||||
|
||||
Being powered at all times allows the HSM to actively detect and respond to attacks. The most common way this is done is
|
||||
by wrapping the juicy secret parts in a foil or a printed circuit board that is patterned with a long and convoluted
|
||||
maze of wires, called a *mesh*. The HSM is continuously monitoring these wires for changes (such as shorts, breaks or
|
||||
changes in resistance) and will sound the alarm when any are detected. Practically, this presents a considerable hurdle
|
||||
to any attacker: They have to find a way to disable or circumvent the mesh while it is being monitored by the HSM. In
|
||||
practice, often this is no insurmountable challenge but it again increases attack costs.
|
||||
|
||||
DIY Meshes
|
||||
==========
|
||||
|
||||
Throughout my studies in security research I have always had an interest in HSMs. I have taken apart my fair share of
|
||||
HSMs and at this point, to understand the technology more, I want to experiment with building my own HSM. In last year's
|
||||
`HSM basics <{{<ref "blog/hsm-basics/index.rst">}}>`_ post I have lined out some ideas for a next generation design that
|
||||
deviates from the bread-and-butter apporoach of using a mesh as the primary security feature. Before embarking on
|
||||
practical experiments with these ideas, I want to first take a stab at replicating the current state of the art as best
|
||||
I can. State of the art meshes often use exotic substrates such as 3D plastic parts with traces chemically deposited on
|
||||
their surface or special flexible substrates with conductive ink traces. These technologies will likely be too
|
||||
cumbersome for me to implement myself only for a few prototypes, and industrial manufacturers will most likely be too
|
||||
expensive. Thus, I will concentrate on regular PCB technology for now.
|
||||
|
||||
The idea of a mesh on a PCB is pretty simple: You have one or several traces that you try to cover every corner of the
|
||||
mesh PCB's area with. To be most effective, the traces should be as thin and as close together as possible. To increase
|
||||
the chances of a manipulation being detected, multiple traces can also be used that can then be monitored for shorts
|
||||
between them.
|
||||
|
||||
While one can feasibly lay out these traces by hand, this really is an ideal application of a simple auto-router. While
|
||||
general PCB autorouting is *hard*, auto-routing just a few traces to approximate a space-filling curve is not. Since I
|
||||
am just starting out, I went with the simplest algorithmic solution I could think of. I first approximate the area
|
||||
designated to the mesh with a square grid whose cells are a multiple of my trace/space size. The mesh will only be drawn
|
||||
into grid cells that are fully inside the set boundaries. All cells outside or going across the border are discarded in
|
||||
this step.
|
||||
|
||||
I decided to implement this auto-router in a KiCAD plugin. Though KiCADs plugin API is not the best, it was just about
|
||||
usable for this task.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/kicad-mesh-outline.png" alt="KiCAD showing an irregular board shape with rounded corners and
|
||||
indents. In the middle of the board there is a footprint for a 4-pin surface-mount pin header.">
|
||||
<figcaption>The process starts out with the mesh shape being defined inside KiCAD. The mesh's outline is drawn
|
||||
onto one of the graphical "Eco" layers. A footprint is placed to serve as a placeholder for the mesh's
|
||||
connections to the outside world. This footprint is later used as the starting point for the mesh generation
|
||||
algorithm.</figcaption>
|
||||
</figure>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/grid-vis-plain.svg" alt="A vizualization of the grid fitting process. Over the mesh's irregular
|
||||
outline a grid is drawn. In this picture, all grid cells that are fully inside the grid are shown. Grid cells
|
||||
that overlap the mesh border are highlighted. Grid cells outside of the mesh border are not drawn.">
|
||||
<figcaption>A visualization of the grid fitting process. First, a grid large enough to contain the mesh border
|
||||
is generated. Then, every cell is checked for overlap with the mesh border area. If the cell is fully inside, it
|
||||
(yellow), it is considered in the mesh generation later. Cells outside (gray) or on the border (red) are
|
||||
discarded.</figcaption>
|
||||
</figure>
|
||||
|
||||
After generating the grid, starting from the place I want to connect to the mesh, I walk the grid's cells one by one to
|
||||
generate a tree that covers the entire grid's area. To set the mesh's starting place I place a footprint on the board
|
||||
(dark gray in the picture above) whose designator I then tell my script. The tree generation algorithm looks like a
|
||||
depth-first search, except all checks are random. Depending on the level of randomness used at each step of the
|
||||
algorithm it yields more or less organized-looking results. Below are five example runs of the algorithm at differing
|
||||
levels of randomness with the cells colored according to their distance from the tree root. 0% randomness means that the
|
||||
algorithm is going to try cells in forward direction first on every step, and only then try out left and right. 100%
|
||||
means that on every step, the algorithm is choosing a new direction at random.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<img src="images/cells-0.svg" alt="a completely organized looking grid with spiral patterns all over.">
|
||||
<figcaption>0%</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/cells-25.svg">
|
||||
<figcaption>25%</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/cells-50.svg">
|
||||
<figcaption>50%</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/cells-75.svg">
|
||||
<figcaption>75%</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/cells-100.svg" alt="a completely random looking grid with cells aggregating into ireggular
|
||||
areas that look like paint splotches.">
|
||||
<figcaption>100%</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
After I have built this tree like you would do in a depth-first search, I draw my one or several mesh mesh traces into
|
||||
it. The core observation here is that there is only 16 possible ways a cell can be connected: It has four neighbors,
|
||||
each of which it can either be connected to or not, which results in 2^4 options. If you consider rotations and
|
||||
mirroring, this works out to rotations or mirrored versions of only six base tiles: The empty tile, a tile with all four
|
||||
sides connected, a straight through, a 90 degree bend, and a "T"-junction—see the illustration below.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/maze_tiles_plain.svg" style="max-width: 20em">
|
||||
<figcaption>
|
||||
There are six possible tile types in our connectivity graph inside its square tiling. This graphic illustrates
|
||||
all sixteen rotations of these with how they would look in a two-conductor mesh.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
After tiling the grid according to the key above, we get the result below.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/tiles-25-small.svg">
|
||||
<figcaption>
|
||||
An auto-routed mesh with traces colored according to tile types.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/traces-25-small.svg">
|
||||
<figcaption>
|
||||
The same mesh, but with traces all black.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Putting it all together got me the KiCAD plugin you can see in the screenshot below.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/kicad-mesh-settings2.png">
|
||||
<figcaption>
|
||||
The plugin settings window open.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/kicad-mesh-result-large.png">
|
||||
<figcaption>
|
||||
After runing the plugin, the generated mesh looks like this in pcbnew.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
I am fairly happy with the result, but getting there was a medium pain. Especially KiCAD's plugin API is still very
|
||||
unfinieshed. It is hard to use, most parts are completely undocumented and if you use anything but its most basic parts
|
||||
things tend to break. One particular pain point for me was that after generating the mesh, the traces have been added to
|
||||
the board, but are still invisible for some reason. You have to save the board first, then re-load the file for them to
|
||||
become visible. Also KiCAD crashes whenever the plugin tries to remove a trace, so currently my workflow involves always
|
||||
making a copy of the board file first and treating mesh generation as a non-reversible finishing step.
|
||||
|
||||
`Check out the code on my cgit <https://git.jaseg.de/kimesh.git/tree/plugin/mesh_dialog.py>`_.
|
||||
|
||||
.. ::
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/grid-vis-plain.svg" alt="">
|
||||
<figcaption></figcaption>
|
||||
</figure>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 MiB |
|
|
@ -1,40 +0,0 @@
|
|||
---
|
||||
title: "The KiCoil Planar Coil Generator"
|
||||
date: 2025-12-31T13:15:39+02:00
|
||||
summary: >
|
||||
I wrote a layout tool generating planar coils that can handle spiral coils, toroidal coils, and hybrids in between
|
||||
the two.
|
||||
---
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="header.png" style="max-width: 20em">
|
||||
</figure>
|
||||
|
||||
A planar coil is a coil that is made from flat traces in some printing process like PCB or IC manufacturing, instead of
|
||||
being wound from wire. A few weeks ago, I needed one such planar coil that
|
||||
|
||||
|
||||
Project State
|
||||
-------------
|
||||
|
||||
Currently, circular coils are special cased. Their layouts are directly generated, without the use of polygon
|
||||
offsetting. Windings are efficiently approximated using circular arcs. The circular coil layout code is solid, and
|
||||
contains decent (albeit not infallible) parameter sanity checks. Its main limitation is that sometimes, clearances can
|
||||
be violated a bit.
|
||||
|
||||
The arbitrary shape code path is less stable, and produces faulty output in some cases. The most common error is
|
||||
crossing traces near the first vertex of the polygon when the polygon has highly convex or concave parts. I'm still
|
||||
improving this code path, but as long as you check the output, any errors it produces should be easy to fix by hand.
|
||||
|
||||
If you would like to contribute, I'd welcome any ideas on the arbitrary shape code path. I think there is no single
|
||||
optimal solution here, and a generic algorithm that can be adjusted to favor for instance shape accuracy versus winding
|
||||
smoothness would be nice.
|
||||
|
||||
All project links are listed on `https://jaseg.de/projects/kicoil/ <https://jaseg.de/projects/kicoil/>`__. You can check
|
||||
out the code on my git at `https://git.jaseg.de/kicoil.git <https://git.jaseg.de/kicoil.git>`__. Issues are tracked on
|
||||
codeberg at `https://codeberg.org/jaseg/kicoil <https://codeberg.org/jaseg/kicoil>`__. The kicad addon can be installed
|
||||
from the KiCad plugin manager, and you can install the standalone kicoil python package `from PyPI
|
||||
<https://pypi.org/project/kicoil/>`__.
|
||||
|
||||
|
|
@ -1,510 +0,0 @@
|
|||
---
|
||||
title: "LED Characterization"
|
||||
date: 2018-05-02T11:18:38+02:00
|
||||
summary: >
|
||||
Recently, I have been working on a small driver for ambient lighting using 12V LED strips like you can get
|
||||
inexpensively from China. I wanted to be able to just throw one of these somewhere, stick down some LED tape, hook
|
||||
it up to a small transformer and be able to control it through Wifi. When I was writing the firmware, I noticed that
|
||||
when fading between different colors, the colors look *all wrong*! This observation led me down a rabbit hole of
|
||||
color perception and LED peculiarities.
|
||||
---
|
||||
|
||||
Preface
|
||||
-------
|
||||
|
||||
Recently, I have been working on a `small driver`_ for ambient lighting using 12V LED strips like you can get
|
||||
inexpensively from China. I wanted to be able to just throw one of these somewhere, stick down some LED tape, hook it up
|
||||
to a small transformer and be able to control it through Wifi. When I was writing the firmware, I noticed that when
|
||||
fading between different colors, the colors look *all wrong*! This observation led me down a rabbit hole of color
|
||||
perception and LED peculiarities.
|
||||
|
||||
The idea of the LED driver was that it can be used either with up to eight single-color LED tapes or, much more
|
||||
interesting, with up to two RGB or RGBW (red-green-blue-white) LED tapes. For ambient lighting high color resolution was
|
||||
really important so you could dim it down a lot without flickering. I ended up using the same driver stage I used in the
|
||||
`multichannel LED driver`_ project for its great color resolution and low hardware requirements.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/rgb_cube.svg" alt="An illustration of the RGB color cube.">
|
||||
<figcaption>An illustration of the RGB color cube.
|
||||
<a href="https://commons.wikimedia.org/wiki/File:RGB_color_cube.svg">Picture</a> by
|
||||
<a href="https://commons.wikimedia.org/wiki/User:Maklaan">Maklaan from Wikimedia Commons</a>,
|
||||
<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA 3.0</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
To make setting colors over Wifi more intuitive I implemented support for HSV colors. RGB is fine for communication
|
||||
between computers, but I think HSV is easier to work with when manually inputting colors from the command line. RGB is
|
||||
close to how most monitors, cameras and the human visual apparatus work on a very low level but doesn't match
|
||||
higher-level human color perception very well. When we describe a color we tend to think in terms of "hue" or
|
||||
"brightness", and computing a measure of those from RGB values is not easy.
|
||||
|
||||
Colors and Color Spaces
|
||||
-----------------------
|
||||
|
||||
`Color spaces`_ are a mathematical abstraction of the concept of color. When we say "RGB", most of the time we actually
|
||||
mean `sRGB`_, a standardized notion of how to map three numbers labelled "red", "green" and "blue" onto a perceived
|
||||
color. `HSV`_ is an early attempt to more closely align these numbers with our perception. After HSV, a number of other
|
||||
*perceptual* color spaces such as `XYZ (CIE 1931)`_ and `CIE Lab/LCh`_ were born, further improving this alignment. In
|
||||
this mathematical model, mapping a color from one color space into another color space is just a coordinate
|
||||
transformation.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/hsv_cylinder.png" alt="An illustration of the HSV color space as a cylinder.">
|
||||
<figcaption>An illustration of the HSV color space as a cylinder.
|
||||
<a href="https://commons.wikimedia.org/wiki/File:HSV_color_solid_cylinder.png">Picture</a> by
|
||||
<a href="https://commons.wikimedia.org/wiki/User:SharkD">SharkD from Wikimedia Commons</a>,
|
||||
<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA 3.0</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
CIE 1931 XYZ is much larger than any other color space, which is why it is a good basis to express other color spaces
|
||||
in. In XYZ there are many coordinates that are outside of what the human eye can perceive. Below is an illustration of
|
||||
the sRGB space within XYZ. The wireframe cube is (0,0,0) to (1,1,1) in XYZ. The colorful object in the middle is what
|
||||
of sRGB fits inside XYZ, and the lines extending out from it indicate the space that can be expressed in sRGB but not in
|
||||
XYZ. The fat white curve is a projection of the *monochromatic spectral locus*, that is the curve of points you get in
|
||||
XYZ for pure visible wavelengths.
|
||||
|
||||
As you can see, sRGB is *much* smaller than XYZ or even the part within the monochromatic locus that we can perceive. In
|
||||
particular in the blues and greens we loose *a lot* of colors to sRGB.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<video controls loop>
|
||||
<source src="video/sRGB.mkv" type="video/h264">
|
||||
<source src="video/sRGB.webm" type="video/webm">
|
||||
Your browser does not support the HTML5 video tag.
|
||||
</video>
|
||||
<figcaption>Illustration of the measured sRGB color space within XYZ. The thick, white line is the spectral
|
||||
locus.
|
||||
|
||||
<a href="video/sRGB.mkv">mkv/h264 download</a> /
|
||||
<a href="video/sRGB.webm">webm download</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
The wrong colors I got when fading between colors were caused by this coordinate transformation being askew. Thinking
|
||||
over the problem, there are several sources for imperfections:
|
||||
|
||||
* The LED driver may not be entirely linear. For most modulations such as PWM the brightness will be linear starting
|
||||
from a certain value, but there is probably an offset caused by imperfect edges of the LED current. This offset can be
|
||||
compensated with software calibration. I built a calibration setup for driver linearity in the `multichannel LED
|
||||
driver`_ project. Below are pictures of ringing on the edges of an LED driver's waveform.
|
||||
|
||||
* The red, green and blue channels of the LEDs used on the LED tape are not matched. This skews the RGB color space.
|
||||
In practice, the blue channel of my RGB tape to me *looks* much brighter than the red channel.
|
||||
|
||||
* The precise colors of the red, green and blue channels of the LEDs are unknown. Though the red channel *looks* red, it
|
||||
may be of a slightly different hue compared to the reference red used in `sRGB`_ which would also skew the RGB color
|
||||
space.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<img src="images/driver_ringing_strong.jpg" alt="Strong ringing on the LED voltage waveform edge at about
|
||||
100% overshoot during about 70% of the cycle time.">
|
||||
<figcaption>The LED strip being at the end of a couple meters of wire caused extremely bad ringing at high
|
||||
driving frequencies.</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/driver_ringing_weak.jpg" alt="Weak ringing on the LED voltage waveform edge at about 30%
|
||||
overshoot during about 20% of the cycle time.">
|
||||
<figcaption>Adding a resistor in front of the MOSFET gate to slow the transition dampened the ringing
|
||||
somewhat, but ultimately it cannot be eliminated entirely.</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
These last two errors are tricky to compensate. What I needed for that was basically a model of the *perceived* colors
|
||||
of the LED tape's color channels. A way of doing his is to record the spectra of all color channels and then evaluate
|
||||
their respective XYZ coordinates. If all three channels are measured in one go with the same setup the relative
|
||||
magnitudes of the channels in XYZ will be accurate.
|
||||
|
||||
To map any color to the LEDs, the color's XYZ coordinates simply have to be mapped onto the linear coordinate system
|
||||
produced by these three points within XYZ. LEDs are mostly linear in their luminous flux vs. current characteristic so
|
||||
this model will be adequate. The spectral integrals mapping the channels' measured responses to XYZ need only be
|
||||
calculated once and their results can be used as scaling factors thereafter.
|
||||
|
||||
Measuring the spectrum
|
||||
----------------------
|
||||
|
||||
In order to compensate for the cheap LED tape's non-ideal performance I had to measure the LED's red, green and blue
|
||||
channels' spectra. The obvious thing would be to go out and buy a `spectrograph`_, or ask someone to borrow theirs. The
|
||||
former is kind of expensive, and I did not want to wait two weeks for the thing to arrive. The latter I could probably
|
||||
not do every time I got new LED tape. Thus the only choice was to build my own.
|
||||
|
||||
Luckily, building your own spectrometer is really easy. The first thing you need is something that splits incident light
|
||||
into its constituent wavelengths. In professional devices this is called the *`monochromator`_*, since it allows extraction
|
||||
of small color bands from the spectrum. The second thing is some sort of optics that project the incident light onto a
|
||||
screen behind the monochromator. In professional devices lenses or curved mirrors are used. In a simple homebrew job a
|
||||
pinhole as you would use in a `camera obscura`_ does a remarkably nice job.
|
||||
|
||||
For the monochromator component several things could be used. A prism would work, but I did not have any. The
|
||||
alternative is a `diffraction grating`_. Professional gratings are quite specialized pieces of equipment and thus
|
||||
rather expensive. Luckily, there is a common household item that works almost as well: A regular CD or DVD. The
|
||||
microscopic grooves that are used to record data in a CD or DVD work the same as the grooves in a professional
|
||||
diffraction grating.
|
||||
|
||||
Household spectra
|
||||
-----------------
|
||||
|
||||
From this starting point, a few seconds on my favorite search engine yielded an `article by two researchers from the
|
||||
National Science Museum in Tokyo`_ providing a nice blueprint for a simple cardboard-and-DVD construction for use in
|
||||
classrooms. I replicated their device using a DVD and it worked beautifully. Daylight and several types of small LEDs I
|
||||
had around did show the expected spectra. Small red, yellow, green, and blue LEDs showed narrow spectra, daylight one
|
||||
continuous broad one, and white LEDs a continuous broad one with a distinct bright spot in the blue part. The
|
||||
single-color LED spectra are quite narrow since they are determined by the LED's semiconductor's band gap, which is
|
||||
specific to the semiconductor used and is quite precise. White LEDs are in fact a blue LED chip covered with a so-called
|
||||
*phosphor*. This phosphor is not elementary phosphorus but an anorganic compound that absorbs the LED chip's blue light
|
||||
and re-emits a broader spectrum of more yellow-ish wavelengths instead. The final LED spectrum is a superposition of
|
||||
both spectra, with some of the original blue light leaking through the phosphor mixing with the broadband yellow
|
||||
spectrum of the phosphor.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<img src="images/spectrograph_step1_parts.jpg">
|
||||
<figcaption>The ingredients. The cup of coffee and Madoka Magica DVD set are essential to the eventual
|
||||
function of the appartus.</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/spectrograph_step2.jpg">
|
||||
<figcaption>Step 1: Cut to size and mark down all holes as described in <a
|
||||
href="http://www.candac.ca/candacweb/sites/default/files/BuildaSpectroscope.pdf">the manual</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/spectrograph_step3.jpg">
|
||||
<figcaption>Step 2: Cut out all holes</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/spectrograph_step4_complete.jpg">
|
||||
<figcaption>The finished result with the back side showing. The viewing window is on the bottom of the other
|
||||
side.</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
|
||||
Now that I had a spectrograph, I needed a somewhat predictable way of measuring the spectrum it gave me.
|
||||
|
||||
Measuring a spectrum
|
||||
--------------------
|
||||
|
||||
Pointing a camera at the spectrograph would be the obvious thing to do. This produces pretty images but has one critical
|
||||
flaw: I wanted to acquire quantitative measurements of brightness across the spectrum. Since I don't have a precise
|
||||
technical datasheet specifying the spectral response of any of my cameras I can't compare the absolute brightness of
|
||||
different colors on their pictures. Some other sensor was needed.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/daylight_spectrum_dvd.jpg">
|
||||
<figcaption>The daylight spectrum as seen using a DVD as a grating.
|
||||
<a href="https://commons.wikimedia.org/wiki/File:SpectresSolaires-DVD.jpg">Picture</a> by
|
||||
<a href="https://commons.wikimedia.org/wiki/User:Xofc">Xofc from Wikimedia Commons</a>,
|
||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Measuring light intensity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Looking around my lab, I found a bag of `SFH2701`_ visible-light photodiodes. Their
|
||||
datasheet includes their spectral response so I can compensate for that, allowing precise-ish absolute intensity
|
||||
measurements. Just like LEDs, photodiodes are extremely linear across several orders of magnitude. The datasheet of the
|
||||
classic `BPW34`_ photodiode shows that this photodiode's light current is exactly proportional to illuminance over at
|
||||
least three orders of magnitude. The `SFH2701`_ datasheet does not include a similar graph but its performance will be
|
||||
similar. The `SFH2701`_ photodiodes I had at hand were perfect for the job compared to the vintage `BPW34`_ since their
|
||||
active sensing area is really small (0.6mm by 0.6mm) compared to the BPW34 (a whopping 3mm by 3mm). If I were to use a
|
||||
`BPW34`_ I would have to insert some small apterture in front of it so it does not catch too broad a part of the
|
||||
spectrum at once. The `SFH2701`_ is small enough that if I just point it at the projected spectrum directly I will
|
||||
already get only a small part of the spectrum inside its 0.6mm active area.
|
||||
|
||||
To convert the photodiode's tiny photocurrent into a measurable voltage I built another copy of the `transimpedance
|
||||
amplifier`_ circuit I already used in the `multichannel LED driver`_. A `transimpedance amplifier`_ is an
|
||||
amplifiert that produces a large voltage from a small current. The weird name comes from the fact that it works kind of
|
||||
like an amplified resistor (which can be generalized as an *impedance* electrically). Apply a current to a resistor and
|
||||
you get a voltage. A transimpedance amplifiert does the same with the difference that its input always stays at 0V,
|
||||
making it look like an ideal current sink to the connected current source.
|
||||
|
||||
Transimpedance amplifiers are common in optoelectronics to convert small photocurrents to voltages. In this instance I
|
||||
built a very simple circuit with a dampened transimpedance amplifier stage followed by a simple RC filter for noise
|
||||
rejection and a regular non-inverting amplifier using another op-amp from the same chip to further boost the filtered
|
||||
transimpedance amplifier output. I put all the passives setting amplifier response (the gain-setting resistors and the
|
||||
filter resistor and capacitors) on a small removable adapter so I could easily change them if necessary. I put a small
|
||||
trimpot on the virtual ground both amplifers use as a reference so I could trim that if necessary.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/preamp_schematic.jpg" alt="A drawing of the photodiode preamplifier's schematic">
|
||||
<figcaption>The photodiode preamplifier schematic. Schematic drawn with an unlicensed copy of
|
||||
DaveCAD.</figcaption>
|
||||
</figure>
|
||||
|
||||
Following are pictures of the preamplifier board. The connectors on the top-left side are two copies of the analog
|
||||
signal for the ADC and a small panel meter. The SMA connector is used as the photodiode input since coax cables are
|
||||
generally low-leakage and have built-in shielding. The circuit is powered via the micro-USB connector and the analog
|
||||
ground bias voltage can be adjusted using the trimpot.
|
||||
|
||||
For easy replacement, all passives setting gain and frequency response are on a small, pluggable carrier PCB made from a
|
||||
SMD-to-DIP adapter.
|
||||
|
||||
Flying-wire construction is just fine for this low-frequency circuit. In a high-speed photodiode preamp, the
|
||||
transimpedance amplifier circuit would be highly sensitive to stray capacitance, but we're not aiming at high speed
|
||||
here.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<img src="images/preamp_front.jpg">
|
||||
<figcaption>The front side of the preamplifier board.</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/preamp_back.jpg">
|
||||
<figcaption>The wiring of the photodiode preamp.</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
Given a way to measure intensity what remains missing is a way to scan a single photodiode across the spectrum.
|
||||
|
||||
Scanning the projection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A cheap linear stage can be found in any old CD or DVD drive. These drives use a small linear stage based on a
|
||||
stepper-driven screw to move the laser unit radially. Removing the laser unit and connecting a leftover stepper driver
|
||||
module I was left with a small linear stage with about 45 steps per cm without microstepping enabled. The driver I used
|
||||
was an `A4988`_ module that required at least 8V motor drive voltage. I used a small micro USB-input boost converter
|
||||
module to generate a stable 10V supply for the motor driver, with the USB's 5V rail used as a logic supply for the motor
|
||||
driver.
|
||||
|
||||
The `SFH2701`_ can easily be mounted to the linear stage using a small SMD breakout board glued in place with thin wires
|
||||
connecting it to the transimpedance amplifier. The DVD drive linear stage is not very strong so it is important that
|
||||
this wire does not put too much strain on it.
|
||||
|
||||
Above the photodiode, I mounted a small piece of paper on the linear stage to be used as a projection screen to align
|
||||
the linear stage in front of the spectrometer viewing window. A line on the screen paper points to the photodiode die in
|
||||
parallel to the linear stage allowing precise alignment.
|
||||
|
||||
The whole unit with photodiode preamplifier, linear stage, photodiode and stepper motor driver finally looks like this:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/electronics_whole.jpg" alt="The complete electronics setup of the spectrograph. In the back
|
||||
there is the DVD drive stepper stage. In front of it, mounted on a piece of wood are a small USB-to-12V
|
||||
switching-regulator module to power the stepper motor in the top left, below on the bottom left is the
|
||||
photodiode preamp and on the right is a breadboard with the stepper driver module and lots of jumper wires
|
||||
interconnecting everything. On the right of the breadboard, a buspirate is attached to interface everything to a
|
||||
computer. On the bottom edge of the piece of wood, two LED panel meters are mounted for readout of the preamp
|
||||
output and the stepper supply voltages.">
|
||||
<figcaption>The complete electronics setup. The buspirate on the right interfaces to a computer and controls the
|
||||
stepper driver and ADC'es the preamp output. The two panel meters show the preamp output and stepper voltage for
|
||||
setup.</figcaption>
|
||||
</figure>
|
||||
|
||||
The projection of the spectrum can be adjusted by moving the light source relative to the entry slot and by moving
|
||||
around the grating DVD.
|
||||
|
||||
The capture process
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To capture a spectrum, first the light source has to be mounted near the spectrograph's entry slot. The LED tape I
|
||||
tested I just taped face-down directly into it. Next, the grating DVD has to be adjusted to make sure the spectrum
|
||||
covers a sensible part of the photodiode's path. Mostly, this boils down to adjusting the photodiode distance and height
|
||||
to match the vertical extent and wiggling the grating DVD to adjust the projection's horizontal position.
|
||||
|
||||
After the optics are set-up, the photodiode preamplifier has to be adjusted. In my experiments, most LED tape at 5GΩ
|
||||
required a high-ish amplification. The goal in this step is to maximize the peak response of the preamp to be just
|
||||
shy of its VCC rail to make best use of its dynamic range. To adjust the pre-amp, I took several very coarsely-spaced
|
||||
measurements to give me an estimate of the peak while I did not yet know its precise location.
|
||||
|
||||
Since stray daylight totally swamped out the weak projection of the LED's spectrum I shielded the entire setup with a
|
||||
small box made of black cardboard and two black t-shirts on top. This shielding proved adequate for all my measurements
|
||||
but I had to be careful not to accidentially move the DVD that was stuck into the spectrograph with the shielding
|
||||
t-shirts.
|
||||
|
||||
For capturing a single spectrum I wrote a small python script that will automatically move the stepper in adjustable
|
||||
intervals and take two measurements at each point, one with the LED tape off that can be used for offset calibration and
|
||||
one with the LED tape on. All measurements are stored in a sqlite database that can then be accesssed from other
|
||||
scripts.
|
||||
|
||||
I built a small script that shows the progress of the current run and an jupyter notebook for data analysis. The jupyter
|
||||
notebook is capable of live-updating a graph with the in-progress spectrum's data. This was quite useful as a sanity
|
||||
check for when I made some mistake easy to spot in the resulting data.
|
||||
|
||||
After one color channel is captured, the LED tape has to be manually set to the next color and the next measurement can
|
||||
begin.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/raw_plot_cheap_rgb.svg" alt="A plot with three wide peaks, two large peaks on both sides and
|
||||
one smaller one in the middle. The middle one overlaps the two on the sides. The large ones are about 2.5V in
|
||||
amplitude. Overall, the plot is about 300 stepper steps wide with each peak being around 130 steps wide.">
|
||||
<figcaption>A plot of the raw preamp output voltage versus stepper position. From left to right, the three peaks
|
||||
are blue, green and red. Step 0 corresponds to the bottommost stepper position and the shortest wavelength.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Data analysis
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Data analysis consists of three major steps: Offset- and stray light removal, wavelength and amplitude calibration and
|
||||
color space mapping.
|
||||
|
||||
Offset removal
|
||||
**************
|
||||
The first task is to remove the offset caused by dark current as well as stray light of the LED's bright primary
|
||||
reflection on the DVD. The LED is very bright and only a small part of its light gets reflected by the grating towards
|
||||
the photodiode screen. The remaining part of the light is reflected onto the table in front of the DVD spectrograph.
|
||||
Though I covered all of this with black cardboard, some of that light ultimately gets reflected onto the photodiode.
|
||||
This causes a large offset, in particular in the blue part of the spectrum since in this part the photodiode is closest
|
||||
to the spectrograph's opening.
|
||||
|
||||
The composite offset can be approximated with a second-order polynomial that is fitted to all the data outside of the
|
||||
main peak's area. Since at this point the wavelength of each data point is still unknown this is done with a rough first
|
||||
estimate of the three colors' peaks' locations and widths.
|
||||
|
||||
Wavelength- and amplitude calibration
|
||||
*************************************
|
||||
The photodiode's response is strongly wavelength-dependent. In particular in the blue band, the photodiode's sensitivity
|
||||
gets very poor down to about 20% at the edge to ultraviolet. This effect is strong enough to move the apparent location
|
||||
of the blue peak towards red.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/photodiode_sensitivity.svg" alt="A plot of photodiode sensitivity against wavelength relative
|
||||
to peak sensitivity at 820nm. The sensitivity rises from 20% at 380nm approximately linearly to 80% at 620nm,
|
||||
then the rise rolls off.">
|
||||
<figcaption>A plot of the photodiode's relative sensitivity in the visible spectrum. The sensitivity is
|
||||
normalized against its peak at 820nm.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
The problem is that in order to remove this non-linearity, we would already have to know the wavelength of the measured
|
||||
light. Since I don't, I settled for a two-step process. First, a coarse wavelength calibration is done relative to the
|
||||
red peak and the short-wavelength edge of the blue peak. The photodiode measurements are then sensitivity-corrected
|
||||
using this coarse measurement. Then all three channel peaks are measured in the resulting data and a fine wavelength
|
||||
estimate is produced by a least-squares fit of a linear function. This fine estimate is then used for a second
|
||||
sensitivity correction of all original measurements and the scale is changed from stepper motor step count to
|
||||
wavelength in nanometers.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/processed_plot_cheap_rgb.svg" alt="A plot with three wide peaks, all three of different
|
||||
heights. The leftmost peak is highest at 6nA, the middle peak lowest at 1.6nA and the rightmost peak in between
|
||||
at 4nA. The middle one overlaps the two on the sides. Overall, the plot spans about 300nm on its x axis with
|
||||
each peak being around 100nm wide.">
|
||||
<figcaption>A plot of the processed measurements. From left to right, the three peaks are blue, green and red.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
.. FIXME re-do these measurements, avoiding clipping
|
||||
.. FIXME re-do calibration using CCFL
|
||||
.. FIXME calibration for brightness imbalance due to wedge-shaped projection of spectrum
|
||||
|
||||
Color space mapping
|
||||
*******************
|
||||
Finally, to achieve the objective of measuring the LED tape's channels' precise color coordinates the measured spetra
|
||||
have to be matched against the color spaces' *color matching functions*. The color matching functions describe how
|
||||
strong the color space's idealized *standard observer* would react to light at a particular wavelength. Going from a
|
||||
measured spectrum to color coordinates XYZ works by integrating over the product of the measurement and each color
|
||||
coordinate's color matching function.
|
||||
|
||||
The result are three color coordinates X, Y and Z for each channel R, G and B yielding nine coordinates in total. When
|
||||
written as a matrix conversion between XYZ color space and LED-RGB color space is as simple as multiplying that matrix
|
||||
(or its inverse) and a vector from one of the color spaces.
|
||||
|
||||
In XYZ space, the set of colors that can be produced with this LED tape is described by the `parallelepiped`_ spanned by
|
||||
the three channel's XYZ vectors. In the following figures, you can see a three-dimensional model of the RGB LED's color
|
||||
space (colorful) as well as sRGB (white) for comparison plotted within CIE 1931 XYZ. There is no natural map to scale
|
||||
both so for this illustration the LED color space has been scaled to fit. These figures were made with blender and a few
|
||||
lines of python. The blender project file including all settings and the python script to generate the color space
|
||||
models can be found in the `project repo`_.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<video controls loop>
|
||||
<source src="video/led_within_srgb_scale=1.0.mkv" type="video/h264">
|
||||
<source src="video/led_within_srgb_scale=1.0.webm" type="video/webm">
|
||||
Your browser does not support the HTML5 video tag.
|
||||
</video>
|
||||
<figcaption>Illustration of the measured LED color space scaled to fit within XYZ with sRGB (light gray) for
|
||||
comparison. The thick, white line is the spectral locus.
|
||||
|
||||
<a href="video/led_within_srgb_scale=1.0.mkv">mkv/h264 download</a> /
|
||||
<a href="video/led_within_srgb_scale=1.0.webm">webm download</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
As you can see, the result is pretty disappointing. The LED's color space parallepiped is very narrow, which is because
|
||||
the blue channel is much brighter than the other two channels. An easy fix for this is to scale-up the RGB space and
|
||||
drop any values outside XYZ. The scaling factor is a trade-off between color space coverage and brightness. You can
|
||||
produce the most colors when you clip all channels to brightness of the weakest channel (green in this case), but that
|
||||
will make the result very dim. Scaling brightness like that stretches the RGB parallelepiped along its major axis. Up to
|
||||
a point the number of possible colors (the gamut) increases at expense of maximum brightness. When the parallelepiped is
|
||||
stretched far enought for all three channel vectors to be outside the 1,1,1 XYZ-cube, maximum brightness continues to
|
||||
decrease but the gamut stays constant. I don't know a simple scientific way to solve this problem, so I just played
|
||||
around with a couple of factors and settled on 2.5 as a reasonable compromise. Below is an illustration.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure>
|
||||
<video controls loop>
|
||||
<source src="video/led_within_srgb_fancy_camera_path_scale=2.5.mkv" type="video/h264">
|
||||
<source src="video/led_within_srgb_fancy_camera_path_scale=2.5.webm" type="video/webm">
|
||||
Your browser does not support the HTML5 video tag.
|
||||
</video>
|
||||
<figcaption>Illustration of the measured LED color space at scale factor 2.5 within XYZ with sRGB (light gray)
|
||||
for comparison. The thick, white line is the spectral locus.
|
||||
|
||||
<a href="video/led_within_srgb_fancy_camera_path_scale=2.5.mkv">mkv/h264 download</a> /
|
||||
<a href="video/led_within_srgb_fancy_camera_path_scale=2.5.webm">webm download</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Firmware implementation
|
||||
-----------------------
|
||||
In the end, the above measurements yield two matrices: One for mapping XYZ to RGB, and one for mapping RGB to XYZ. Of
|
||||
the several versions of CIE XYZ I chose the CIE 1931 XYZ color space as a basis for the firmware because it is most
|
||||
popular. Mapping a color coordinate in one color space to the other is as simple as performing nine floating-point
|
||||
multiplications and six additions. Mapping Lab or Lch to RGB is done by first mapping Lab/Lch to XYZ, then XYZ to RGB.
|
||||
Lab to XYZ is somewhat complex since it requires a floating-point power for gamma correction, but any self-respecting
|
||||
libc will have one of those so this is still no problem. Lch also requires floating-point sine and cosine functions, but
|
||||
these should still be no problem on most hardware.
|
||||
|
||||
My implementation of these conversions in the ESP8266 firmware of my `Wifi LED driver`_ can be found `on Github`_. You
|
||||
can view the Jupyter notebook most of the analysis above `here <http://nbviewer.jupyter.org/github/jaseg/led_drv/blob/master/doc/Spectrum%20Measurement.ipynb>`__.
|
||||
|
||||
.. _`on Github`: https://github.com/jaseg/esp_led_drv/blob/master/user/led_controller.c
|
||||
.. _`project repo`: https://github.com/jaseg/led_drv
|
||||
.. _`Wifi LED driver`: {{<ref "blog/wifi-led-driver/index.rst">}}
|
||||
.. _`small driver`: {{<ref "blog/wifi-led-driver/index.rst">}}
|
||||
.. _`multichannel LED driver`: {{<ref "blog/multichannel-led-driver/index.rst">}}
|
||||
.. _`sRGB`: https://en.wikipedia.org/wiki/SRGB
|
||||
.. _`CC BY-SA 3.0`: https://creativecommons.org/licenses/by-sa/3.0
|
||||
.. _`Color spaces`: https://en.wikipedia.org/wiki/Color_space
|
||||
.. _`HSV`: https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
.. _`CIE Lab/LCh`: https://en.wikipedia.org/wiki/Lab_color_space
|
||||
.. _`XYZ (CIE 1931)`: https://en.wikipedia.org/wiki/CIE_1931_color_space
|
||||
.. _`camera obscura`: https://en.wikipedia.org/wiki/Pinhole_camera
|
||||
.. _`article by two researchers from the National Science Museum in Tokyo`: http://www.candac.ca/candacweb/sites/default/files/BuildaSpectroscope.pdf
|
||||
.. _`spectrograph`: https://en.wikipedia.org/wiki/Ultraviolet%E2%80%93visible_spectroscopy
|
||||
.. _`monochromator`: https://en.wikipedia.org/wiki/Monochromator
|
||||
.. _`diffraction grating`: https://en.wikipedia.org/wiki/Diffraction_grating
|
||||
.. _`SFH2701`: https://dammedia.osram.info/media/resource/hires/osram-dam-2495903/SFH%202701.pdf
|
||||
.. _`BPW34`: http://www.vishay.com/docs/81521/bpw34.pdf
|
||||
.. _`transimpedance amplifier`: https://en.wikipedia.org/wiki/Transimpedance_amplifier
|
||||
.. _`A4988`: https://www.pololu.com/file/0J450/A4988.pdf
|
||||
.. _`parallelepiped`: https://en.wikipedia.org/wiki/Parallelepiped
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
title: "How to make cgit serve PDF files as direct downloads"
|
||||
date: 2025-11-17T23:42:00+01:00
|
||||
summary: >
|
||||
cgit is great, but by default when you click on a PDF file in a repository content listing it will show you a
|
||||
hexdump of the file. You can access the actual file by clicking the "plain" link on top of the listing, but that's
|
||||
not only annoying, for large PDF files rendering the hexdump can also hang browser tabs.
|
||||
---
|
||||
|
||||
cgit is great, but by default when you click on a PDF file in a repository content listing it will show you a
|
||||
hexdump of the file. You can access the actual file by clicking the "plain" link on top of the listing, but that's
|
||||
not only annoying, for large PDF files rendering the hexdump can also hang browser tabs.
|
||||
|
||||
I found a quick and easy solution to this problem, which I'm documenting here because it seems nobody on the
|
||||
internet has really done this before, and the usual AI assistants (ChatGPT and Claude) are both deeply confused.
|
||||
|
||||
You just add a simple rewrite rule to your nginx config that 302-redirects requests to ``/tree/.../foobar.pdf`` to
|
||||
``/plain/.../foobar.pdf``. Here's the rule, make sure you put them in your nginx config *before* the location directive
|
||||
proxying requests to cgit.
|
||||
|
||||
.. code:: nginx
|
||||
|
||||
location ~ ^/([^/]+)/tree/(.*\.pdf)$ {
|
||||
return 302 /$1/plain/$2;
|
||||
}
|
||||
|
||||
|
|
@ -1,462 +0,0 @@
|
|||
---
|
||||
title: "32-Channel LED tape driver"
|
||||
date: 2018-05-02T11:31:14+02:00
|
||||
summary: >
|
||||
Together, a friend and I outfitted the small staircase at Berlin's Chaos Computer Club with nice, shiny RGB-WW LED
|
||||
tape for ambient lighting. For this installation, I made a 32-channel LED driver that achieves high dynamic range on
|
||||
all 32 channels using a cheap microcontroller by using Binary Code Modulation.
|
||||
---
|
||||
|
||||
Theoretical basics
|
||||
==================
|
||||
|
||||
Together, a friend and I outfitted the small staircase at Berlin's Chaos Computer Club with nice, shiny RGB-WW LED tape
|
||||
for ambient lighting. This tape is like regular RGB tape but with an additional warm white channel, which makes for much
|
||||
more natural pastels and whites. There are several variants of RGBW tape. Cheap ones have separate RGB and white LEDs,
|
||||
which is fine for indirect lighting but does not work for direct lighting. Since we wanted to mount our tape in channels
|
||||
at the front of the steps, we had to use the slightly more expensive variant with integrated RGBW LEDs. These are LEDs
|
||||
in the 5050 (5.0mm by 5.0mm) form factor common with RGB LEDs that have a small section divided off for the white
|
||||
channel. The red, green and blue LED chips sit together in the larger section covered with clear epoxy and the white
|
||||
channel is made up from the usual blue LED inside a yellow phosphor in the smaller section.
|
||||
|
||||
Since we wanted to light up all of 15 steps, and for greatest visual effect we would have liked to be able to control
|
||||
each step individually we had to find a way to control 60 channels of LED tape with a reasonable amount of hardware.
|
||||
|
||||
LED tape has integrated series resistors and runs off a fixed 12V or 24V constant-voltage supply. This means you don't
|
||||
need a complex constant-current driver as you'd need with high-power LEDs. You can just hook up a section of LED tape
|
||||
to a beefy MOSFET to control it. Traditionally, you would do *Pulse Width Modulation* (PWM) on the MOSFET's input to
|
||||
control the LED tape's brightness.
|
||||
|
||||
Pulse Width Modulation
|
||||
----------------------
|
||||
|
||||
`Pulse Width Modulation`_ is a technique of controlling the brightness of a load such as an LED with a digital signal.
|
||||
The basic idea is that if you turn the LED on and off much too fast for anyone to notice, you can control its power by
|
||||
changing how long you turn it on versus how long you leave it off.
|
||||
|
||||
PWM divides each second into a large number of periods. At the beginning of each period, you turn the LED on. After
|
||||
that, you wait a certain time until you turn it off. Then, you wait for the next period to begin. The periods are always
|
||||
the same length but you can set when you turn off the LED. If you turn it off right away, it's off almost all the time
|
||||
and it looks like it's off to your eye. If you turn it off right at the end, it's on almost all the time and it looks
|
||||
super bright to your eye. Now, if you turn it off halfway into the cycle, it's on half the time and it will look to your
|
||||
eye as half as bright as before. This means that you can control the LED's brightness with only a digital signal and
|
||||
good timing.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/pwm_schema.jpg" alt="A visualization of PWM at different duty cycles.">
|
||||
<figcaption>Waveforms of two PWM cycles at different duty cycles.</figcaption>
|
||||
</figure>
|
||||
|
||||
PWM works great if you have a dedicated PWM output on your microcontroller. It's extremely simple in both hardware and
|
||||
software. Unfortunately for us, controlling 32 channels with PWM is not that easy. Cheap microcontrollers only have `a
|
||||
handful of hardware PWM outputs`_, so we'd either have to do everything in software, bit-banging our LED modulation, or
|
||||
we'd have to use a dedicated chip.
|
||||
|
||||
Doing PWM in software is both error-prone and slow. Since the maximum dynamic range of a PWM signal is limited by the
|
||||
shortest duty cycle it can do, software PWM being slow means it has poor PWM resolution at maybe 8 bits at most. Poor
|
||||
color resolution is not a problem if all you're doing is to fade around the `HSV rainbow`_, but for ambient lighting
|
||||
where you *really* want to control the brightness down to a faint shimmer you need all the color resolution you can get.
|
||||
|
||||
If you rule out software PWM, what remains are dedicated `hardware PWM controllers`_. Most of these have either of three
|
||||
issues:
|
||||
|
||||
* They're expensive
|
||||
* They don't have generous PWM resolution either (12 bits if you're lucky)
|
||||
* They're meant to drive small LEDs such as a 7-segment display directly and you can't just hook up a MOSFET to their
|
||||
output
|
||||
|
||||
This means we're stuck in a dilemma between two poor solutions if we'd want to do PWM. Luckily for us, PWM is not the
|
||||
only modulation in town.
|
||||
|
||||
.. _`Pulse Width Modulation`: https://en.wikipedia.org/wiki/Pulse-width_modulation
|
||||
.. _`a handful of hardware PWM outputs`: https://www.nxp.com/parametricSearch#/&c=c731_c380_c173_c161_c163&page=1
|
||||
.. _`HSV rainbow`: https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
.. _`hardware PWM controllers`: http://www.ti.com/lit/ds/symlink/tlc5940.pdf
|
||||
|
||||
Binary Code Modulation
|
||||
----------------------
|
||||
|
||||
PWM is the bread-and-butter of the maker crowd. Everyone and their cat is doing it and it works really well most of the
|
||||
time. Unbeknownst to most of the maker crowd, there is however another popular modulation method that's mostly used in
|
||||
professional LED systems: Enter `*Binary Code Modulation* (BCM) <http://www.batsocks.co.uk/readme/art_bcm_1.htm>`_.
|
||||
|
||||
BCM is to PWM sort of what barcodes are to handwriting. While PWM is easy to understand and simple to implement if all
|
||||
you have is a counter and an IO pin, BCM is more complicated. On the other hand, computers can do complicated and BCM
|
||||
really shines in multi-channel applications.
|
||||
|
||||
Similar to PWM, BCM works by turning on and off the LED in short periods fast enough to make your eye perceive it as
|
||||
partially on all the time. In PWM the channel's brightness is linearly dependent on its duty cycle, i.e. the percentage
|
||||
it is turned on. In PWM the duty cycle D is the total period T divided by the on period T_on. The issue with doing PWM
|
||||
on many channels at once is that you have to turn off each channel at the exact time to match its duty cycle.
|
||||
Controlling many IO pins at once with precise timing is really hard to do in software.
|
||||
|
||||
BCM avoids this by further dividing each period into smaller periods which we'll call *bit periods* and splitting each
|
||||
channel's duty cycle into chunks the size of these bit periods. The amazingly elegant thing in BCM now is that as you
|
||||
can guess from the name these bit periods are weighted in powers of two. Say the shortest bit period lasts 1
|
||||
microsecond. Then the second-shortest bit period is 2 microseconds and the third is 4, the fifth 8, the sixth 16 and so
|
||||
on.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/bcm_schema.jpg" alt="A visualization of BCM at different duty cycles.">
|
||||
<figcaption>Waveforms of a single 4-bit BCM cycle at different duty cycles. This BCM can produce 16 different
|
||||
levels.</figcaption>
|
||||
</figure>
|
||||
|
||||
Staggered like this, you turn on the LED for integer value of microseconds by turning it on in the bit periods
|
||||
corresponding to the binary bits of that value. If I want my LED to light for 19 microseconds every period, I turn it on
|
||||
in the 16 microsecond bit period, the 2 microsecond bit period and the 1 microsecond bit period and leave it off for the
|
||||
4 and 8 mircosecond bit periods.
|
||||
|
||||
Now, how this is better instead of just more complicated than plain old PWM might not be clear yet. But consider this:
|
||||
Turning on and off a large number of channels, each at its own arbitrary time is hard because doing the timing in
|
||||
software is hard. We can't use hardware timers since we only have two or three of those, and we have 32 channels.
|
||||
However, we can use one hardware timer to trigger a really cheap external latch to turn on or off the 32 channels all at
|
||||
once. With this setup, we can only controll all channels at once, but we can do so with very precise timing.
|
||||
|
||||
All we need to do is to set our timer to the durations of the BCM bit periods, and we can get the same result as we'd
|
||||
get with PWM with only one hardware timer and a bit of code that is not timing-critical anymore.
|
||||
|
||||
Applications of Binary Code Modulation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
BCM is a truly wondrous technique, and outside of hobbyist circles it is in fact very widely known. Though we're using
|
||||
it to control just 32 channels here, you can do much more channels without any problems. The most common application
|
||||
where BCM is invariably used is *any* kind of LED screen. Controlling the thousands and thousands of LEDs in an LED
|
||||
screen with PWM with a dedicated timer for each LED would not be feasible. With BCM, all you need to dedicate to a
|
||||
single LED is a flipflop (or part of one if you're multiplexing). In fact, there is a whole range of `ICs with no other
|
||||
purpose than to enable BCM on large LED matrices <http://www.vabolis.lt/stuff/MBI5026.pdf>`_. Basically, these are a
|
||||
high-speed shift register with latched outputs much like the venerable 74HC595_, only their outputs are constant-current
|
||||
sinks made so that you can directly connect an LED to them.
|
||||
|
||||
.. _74HC595: http://www.ti.com/lit/ds/symlink/sn74hc595.pdf
|
||||
|
||||
Running BCM on LED tape
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In our case, we don't need any special driver chips to control our LED tape. We just connect the outputs of a 74HC595_
|
||||
shift register to one MOSFET_ each, and then we directly connect the LED tape to these MOSFETs. The MOSFETs allow us to
|
||||
drive a couple of amps into the LED tape from the weak outputs of the shift register.
|
||||
|
||||
The BCM timing is done by hooking up two timer channels of our microcontroller to the shift registers *strobe* and
|
||||
*reset* inputs. We set the timer to PWM mode so we can generate pulses with precise timing. At the beginning of each
|
||||
bit period, a pulse will strobe the data for this bit period that we shifted in previously. At the end of the bit
|
||||
period, one pulse will reset the shift register and one will strobe the freshly-reset zeros into the outputs.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/olsndot_output_schematic.jpg" alt="From left to right, we see the STM32, one of the shift
|
||||
registers, and the LEDs and MOSFETs. The LED tape is driven to ground by the MOSFETs, which are in turn directly
|
||||
driven from the shift register outputs. The shift register is wired up to the STM32 with its clock and data
|
||||
inputs on SCK and MOSI and its RESET and STROBE inputs on channel 2 and 3 of timer 1.">
|
||||
<figcaption>
|
||||
The schematic of a single output of this LED driver. Multiple shift register stages can be cascaded.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Our implementation of this system runs on an STM32F030F4P6_, the smallest, cheapest ARM microcontroller you can get from
|
||||
ST. This microcontroller has only 16kB of flash and 1kB of RAM, but that's plenty for our use. We use its SPI controller
|
||||
to feed the modulation data to the shift registers really fast, and we use two timer channels to control the shift
|
||||
registers' reset and strobe.
|
||||
|
||||
We can easily cascade shift registers without any ill side-effects, and even hundreds of channels should be no problem
|
||||
for this setup. The only reason we chose to stick to a 32-channel board is the mechanics of it. We thought it would be
|
||||
easier to have several small boards instead of having one huge board with loads of connectors and cables coming off it.
|
||||
|
||||
The BOM cost per channel for our system is 3ct for a reasonable MOSFET, about 1ct for one eighth of a shift register
|
||||
plus less than a cent for one resistor between shift register and MOSFET. In the end, the connectors are more expensive
|
||||
than the driving circuitry.
|
||||
|
||||
.. _MOSFET: https://en.wikipedia.org/wiki/MOSFET
|
||||
.. _STM32F030F4P6: http://www.st.com/resource/en/datasheet/stm32f030f4.pdf
|
||||
|
||||
Hardware design
|
||||
===============
|
||||
|
||||
From this starting point, we made a very prototype-y hardware design for a 32-channel 12V LED tape driver. The design is
|
||||
based on the STM32F030F4P6_ driving the shift registers as explained above. The system is controlled through an RS485_
|
||||
bus that is connected up to the microcontroller's UART using an MAX485_-compatible RS485 transceiver. The LED tape is
|
||||
connected using 9-pin SUB-D_ connectors since they are cheap and good enough for the small current of our short segments
|
||||
of LED tape. The MOSFETs we use are small SOT-23_ logic-level MOSFETs. In various prototypes we used both International
|
||||
Rectifier's IRLML6244_ as well as Alpha & Omega Semiconductor's AO3400_. Both are good up to about 30V/5A. Since we're
|
||||
only driving about 2m of LED tape per channel we're not going above about 0.5A and the MOSFETs don't even get warm.
|
||||
|
||||
.. _RS485: https://en.wikipedia.org/wiki/RS-485
|
||||
.. _MAX485: https://datasheets.maximintegrated.com/en/ds/MAX1487-MAX491.pdf
|
||||
.. _IRLML6244: https://www.infineon.com/dgdl/?fileId=5546d462533600a4015356686fed261f
|
||||
.. _AO3400: http://aosmd.com/pdfs/datasheet/AO3400.pdf
|
||||
.. _SUB-D: https://en.wikipedia.org/wiki/D-subminiature
|
||||
.. _SOT-23: http://www.nxp.com/documents/outline_drawing/SOT23.pdf
|
||||
|
||||
Switching nonlinearities
|
||||
------------------------
|
||||
During testing of our initial prototype, we noticed that the brightness seemed to jump around when fading to very low
|
||||
values. It turned out that our extremely simple LED driving circuit consisting of only the shift register directly
|
||||
driving a MOSFET, which in turn directly drives the LED tape was maybe a little bit too simple. After some measurements
|
||||
it turned out that we were looking at about 6Vpp of ringing on the driver's output voltage. The picture below is the
|
||||
voltrage we saw on our oscilloscope on the LED tape.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/driver_ringing_strong.jpg" alt="Strong ringing on the LED voltage waveform edge at about
|
||||
100% overshoot during about 70% of the cycle time.">
|
||||
<figcaption>Bad ringing on the LED output voltage caused by wiring inductance. Note that the effect on the
|
||||
actual LED current is less bad than this looks since the LED's V/I curve is nonlinear.</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Dynamic switching behavior: Cause and Effect
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A bit of LTSpice_ action later we found that the inductance of the few metres of cable leading to the LED tape is the
|
||||
likely culprit. The figure below is the schematic used for the simulations.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/driver_output_ltspice_schematic.jpg" alt="The LTSpice schematic of one output of the driver,
|
||||
taking into account the shift register's output ESR and the wiring ESL.">
|
||||
<figcaption>The schematic of the simulation in LTSpice</figcaption>
|
||||
</figure>
|
||||
|
||||
As tested, the driver does not include any per-output smoothing so the ~.5A transient on each BCM cycle hits the cable
|
||||
in full. Combined with the cable inductance, this works out to a considerable lag of the rising edge of the LED
|
||||
current, and bad ringing on its falling edge. Below is the voltage on the LED output from an LTSpice simulation of our
|
||||
driver.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/overshoot_sim_r0.svg" alt="The result of the LTSpice simulation of our driver output. The LED
|
||||
current shows similar ringing to what we measured using the oscilloscope. Interestingly, the gate voltage shows
|
||||
strong ringing, too.">
|
||||
<figcaption>The result of our LTSpice simulation. This simulation assumes 1µH of wiring inductance and 50Ω of
|
||||
output impedance on the part of the shift register. The ringing at the gate visible in the gate voltage graph is
|
||||
due to feed-through of the ringing at the output through the MOSFET's parasitic Cgd.</figcaption>
|
||||
</figure>
|
||||
|
||||
We were able to reduce the rining and limit the effect somewhat by putting a 220Ω series resistor in between the shift
|
||||
register output and the MOSFET gate. This resistor forms an RC circuit with the MOSFET's nanofarad or two of gate
|
||||
capacitance. The result of this is that the LED current passing the wire's ESL rises slightly more slowly and thus the
|
||||
series inductance gets excited slightly less, and the overshoot decreases. Below is a picture of the waveform with the
|
||||
damping resistor in place and a picture of our measurement for comparison. The resistor values don't agree perfectly
|
||||
since the estimated ESL and stray capacitance of the wiring is probably way off.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/driver_ringing_weak.jpg" alt="Weak ringing on the LED voltage waveform edge at about 30%
|
||||
overshoot during about 20% of the cycle time.">
|
||||
<figcaption>Adding a resistor in front of the MOSFET gate to slow the transition damped the ringing somewhat,
|
||||
but ultimately it cannot be eliminated entirely. Note how you can actually see the miller plateau on the
|
||||
trailing edge of this signal.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/overshoot_sim_r100.svg" alt="The result of the LTSpice simulation of our driver output with an
|
||||
extra 100 Ohms between shift register output and MOSFET gate. Similar to the oscilloscope measurement the
|
||||
ringing is much reduced in its amplitude.">
|
||||
<figcaption>The LTSpice simulation result with the same parameters as above but with an extra 100Ω between the
|
||||
shfit register's output and the MOSFET's gate.</figcaption>
|
||||
</figure>
|
||||
|
||||
A side effect of this fix is that now the effective on-time of the LED tape is much longer than the duty cycle at the
|
||||
shift register's output at very small duty cycles (1µs or less). This is caused by the MOSFET's `miller
|
||||
plateau`_. For illustration, below is a graph of both the excitation waveform (the boxy line) and the resulting LED
|
||||
current (the other ones) both without damping (top) and with 220Ω damping (bottom). As you can see the effective duty
|
||||
cycle of the LED current is not at all equal to the 50% duty cycle of the excitation square wave.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/asymmetric_iled.svg" alt="The result of an LTSpice simulation of the LED duty cycle without and
|
||||
with damping. Dampening widens the LED current waveform from 50% duty cycle with sharp edges to about 80% duty
|
||||
cycle with soft edges.">
|
||||
<figcaption>Simulated LED duty cycle with and without damping. The damping resistance used in this simulation
|
||||
was 220Ω.</figcaption>
|
||||
</figure>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/asymmetric_vgate.svg" alt="The gate voltages in the spice simulation above. The undamped
|
||||
response shows sharp edges with the miller plateau being a barely noticeable step, but with strong ringing on
|
||||
the trailing edge. The damped response shows RC-like slow-edges, but has wide miller plateaus on both edges
|
||||
adding up to about 50% of the pulse width.">
|
||||
<figcaption>The MOSFET gate voltage from the simulation in the figure above. You can clearly see how the miller
|
||||
plateau (the horizontal part of the trace at about 1V) is getting much wider with added damped, and how the
|
||||
resulting gate charge/discharge curve is not at all that of a capacitor anymore.</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
In conclusion, we have three major causes for our calculated LED brightness not matching reality:
|
||||
|
||||
* Ringing of the equivalent series inductance of the wiring leading up to the LED tape
|
||||
* Miller plateau lag
|
||||
* The damping resistor and the MOSFET gate forming an RC filter that helps with wire ESL ringing but worsens the miller
|
||||
plateau issue and deforms the LED current edges.
|
||||
|
||||
Added up, these three effects yield a picture that agrees well with our simulations and measurements. The overall effect
|
||||
is neglegible at long period durations (>10µs), but gets really bad at short period durations (<1µs). The effect is
|
||||
non-linear, so correcting for it is not as simple as adding an offset.
|
||||
|
||||
.. _LTSpice: http://www.analog.com/en/design-center/design-tools-and-calculators/ltspice-simulator.html
|
||||
.. _`miller plateau`: https://www.vishay.com/docs/68214/turnonprocess.pdf
|
||||
|
||||
Measuring LED tape brightness
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In order to correct for the nonlinearities mentioned above, we decided to implement a lookup table mapping BCM period to
|
||||
actual timer setting. That is, each row of the table contains the actual period length we need to set the
|
||||
microcontroller's timer to in order to get our intended brightness steps.
|
||||
|
||||
To calibrate our driver, we needed a setup for reproducible measurement of the relative brightness of our LED tape at
|
||||
different settings. Absolute brightness is not of interest to us as the eye can't perceive it. To perform the
|
||||
calibration, the LED driver is set to enable each single BCM period in turn, i.e. brightness values 1, 2, 4, 8, 16 etc.
|
||||
|
||||
The setup we used to measure the LED tape's brightness consists of a bunch of LED tape stuck into a tin can for
|
||||
shielding against both stray light and electromagnetic interference and a photodiode looking at the LED tape. We used
|
||||
the venerable BPW34_ photodiode in our setup as I had a bunch leftover from another project and because they are quite
|
||||
sensitive owing to their physically large die area.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/linearization_setup.jpg" alt="The led measurement setup consists of several PCBs and a
|
||||
breadboard linked with a bunch of wires and a big tin can to shield the LEDs and the photodiode. A large sub-D
|
||||
connector is put into the top of the tin can as a feed-through for the LED tape's control signals and the
|
||||
photodiode signal. In the background the control laptop is visible.">
|
||||
<figcaption>The LED brighness measurement setup. The big tin can contains a bunch of LED tape and the
|
||||
photodiode. The breadboard on the right is used for the photodiode preamplifier and for jumpering around the LED
|
||||
tape's channels. The red board next to it is the buspirate used as ADC. The board on the bottom left is a
|
||||
TTL-to-RS485 converter and the board in the middle is the unit under test.</figcaption>
|
||||
</figure>
|
||||
|
||||
The photodiode's photocurrent is converted into a voltage using a very simple transimpedance amplifier based around a
|
||||
MCP6002_ opamp that was damped into oblivion with a couple nanofarads of capacitance in its feedback loop. The MCP6002_
|
||||
is a fine choice here since I had a bunch and because it is a CMOS opamp, meaning it has low bias current that would
|
||||
mess up our measurements. For many applications, opamp bias current is not a big issue but when using the opamp to
|
||||
directly measure very small currents at its input it quickly swamps out the signal for most BJT-input types.
|
||||
|
||||
The transimpedance amplifier's output is read from the computer using the ADC input of a buspirate USB thinggamajob. In
|
||||
general I would not recommend the buspirate as a tool for this job since it's ADC is not particularly good and it's
|
||||
programming interface is positively atrocious, but it was what I had and it beat first wiring up one of the dedicated
|
||||
ADC chips I had in my parts bin.
|
||||
|
||||
The computer runs a small python script cycling the LED tape through all its BCM period settings and taking a brightness
|
||||
measurement at each step. Later on, these measurements can be plotted to visualize the resulting slope's linearity, and
|
||||
we can even do a simulation of the resulting brightness for all possible control values by just adding the measured
|
||||
photocurrents for a certain BCM setpoint just as our retinas would do.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/driver_linearity_raw.svg" alt="">
|
||||
<figcaption>
|
||||
A plot of the measured brightness of our LED tape for each BCM period. The brightness values are normalized
|
||||
to the value measured at the LSB setpoint (brightness=1/65535). Ideally, this plot would show a straight
|
||||
line with slope 1. Obviously, it doesn't. The bend in the curve is caused by the above-mentioned duty cycle
|
||||
offset adding an offset to all brightness values. Shown is both the raw data (light), which has essentially zero
|
||||
measurement error and a linear fit (dark).
|
||||
|
||||
The plot is in log-log to approximate how the human eye would perceive brightness, i.e. highly sensitive at
|
||||
low values but not very sensitive at all at large values.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
While it would be possible to fully automate the optimization of BCM driver lookup tables, we needed only one and in the
|
||||
end I just sat down and manually tweaked the ideal values we initially calculated until I liked the result. You can see
|
||||
the resulting brightness curve below.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<img src="images/uncorrected_brightness_sim.svg" alt="">
|
||||
<figcaption>
|
||||
Calculated brightness curve for the uncorrected BCM setup. As you can see, at low setpoints the result
|
||||
is about as smooth as sandpaper, which is well in line with our observations. At high setpoints the
|
||||
offset gets swamped out and the nonlinearity in the low bits is not visible anymore.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/corrected_brightness_sim.svg" alt="">
|
||||
<figcaption>
|
||||
Brightness curve for the corrected BCM setup extrapolated using actual measurements. Looks as buttery
|
||||
smooth in real life as it does in this plot.
|
||||
</figcaption>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
.. _BPW34: http://www.vishay.com/docs/81521/bpw34.pdf
|
||||
.. _MCP6002: http://ww1.microchip.com/downloads/en/DeviceDoc/21733j.pdf
|
||||
|
||||
Controlling the driver
|
||||
----------------------
|
||||
|
||||
Now that our driver was behaving linear enough that you couldn't see it actually wasn't we needed a nice way to control
|
||||
it from a computer of our choice. In the ultimate application (our staircase) we'll use a raspberry pi for this. Since
|
||||
we already settled on an RS485_ bus for its robustness and simplicity, we had to device a protocol to control the driver
|
||||
over this bus. Here, we settled on a simple, COBS_-based protocol for the reasons I wrote about in `How to talk to your
|
||||
microcontroller over serial <serial-protocols>`_.
|
||||
|
||||
To address our driver nodes, we modified the Makefile to build a random 32-bit MAC into each firmware image. The
|
||||
protocol has only five message types:
|
||||
|
||||
1. A 0-byte *ping* packet, to which each node would reply with its own address in the
|
||||
first 100ms after boot. This can be used to initially discover the addresses of all nodes connected to the bus. You'd
|
||||
spam the bus with *ping* packets, and then hit reset on each node in turn. The control computer would then receive
|
||||
each device's MAC address as you hit reset.
|
||||
2. A 4-byte *address* packet that says which device that the following packet is for. This way of us using the packet
|
||||
length instead of a packet type field is not particularly elegant, but our system is simple enough and it was easy to
|
||||
implement.
|
||||
3. A 64-byte *frame buffer* packet that contains 16 bits of left-aligned brightness data for every channel
|
||||
4. A one-byte *get status* packet that tells the device to respond with...
|
||||
5. ...a 27-byte status packet containing a brief description of the firmware (version number, channel count, bit depth
|
||||
etc.) as well as the device's current life stats (VCC, temperature, uptime, UART frame errors etc.).
|
||||
|
||||
Wrapped up in a nice python interface we can now easily enumerate any drivers we connect to a bus, query their status
|
||||
and control their outputs.
|
||||
|
||||
.. _COBS: https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<a href="images/olsndot_schematic.png">
|
||||
<img src="images/olsndot_schematic.png" alt="A picture of the LED driver schematic">
|
||||
</a>
|
||||
<figcaption>The LED driver <a href="images/olsndot_schematic.png">schematic</a></figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<a href="images/olsndot_pcb.png">
|
||||
<img src="images/olsndot_pcb.png" alt="A picture of the LED driver PCB layout">
|
||||
</a>
|
||||
<figcaption>The LED driver <a href="images/olsndot_pcb.png">PCB layout</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
Putting some thought into the control circuitry and software, you can easily control large numbers of channels of LEDs
|
||||
using extremely inexpensive driving hardware without any compromises on dynamic range. The design we settled on can
|
||||
drive 32 channels of LED tape with a dynamic range of 14bit at a BOM cost of below 10€. All it really takes is a couple
|
||||
of shift registers and a mildly bored STM32 microcontroller.
|
||||
|
||||
Get a PDF file of the schematic and PCB layout `here <olsndot_v02_schematics_and_pcb.pdf>`__ or download the CAD files
|
||||
and the firmware sources `from github <https://github.com/jaseg/led_drv>`_. You can view the Jupyter notebook used to
|
||||
analyze the brightness measurement data `here <http://nbviewer.jupyter.org/github/jaseg/led_drv/blob/master/doc/Run_analysis.ipynb>`__.
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
---
|
||||
title: "Housekeeping note: git.jaseg.de has moved"
|
||||
date: 2026-05-30T10:00:00+02:00
|
||||
summary: >
|
||||
A small note: As part of moving the servers and website to a new, more suitable host for my Ashen and Yanartas
|
||||
projects as well as the creation of yasec, my freelance consulting business, I've moved git.jaseg.de from a custom
|
||||
cgit/gitolite setup to a forgejo instance. This may have broken some links, especially deep links into the old cgit.
|
||||
If you notice any broken links, please reach out through email.
|
||||
---
|
||||
|
||||
A small note: As part of moving the servers and website to a new, more suitable host for my Ashen_ and Yanartas_
|
||||
projects as well as the creation of yasec_, my freelance consulting business, I've moved `git.jaseg.de
|
||||
<https://git.jaseg.de/>`__ from a custom cgit/gitolite setup to a forgejo instance. This may have broken some links,
|
||||
especially deep links into the old cgit. If you notice any broken links, please reach out through email.
|
||||
|
||||
.. _Ashen: https://yasec.de/projects/ashen/
|
||||
.. _Yanartas: https://yasec.de/projects/yanartas/
|
||||
.. _yasec: https://yasec.de/
|
||||
|
Before Width: | Height: | Size: 87 KiB |
|
|
@ -1,104 +0,0 @@
|
|||
---
|
||||
title: "New paper: Monitoring Tamper-Sensing Meshes Using Low-Cost, Embedded Time-Domain Reflectometry"
|
||||
date: 2025-10-20T23:42:00+01:00
|
||||
summary: >
|
||||
I've got a new paper accepted at CHES, to be published in TCHES 2026/1 around beginning of December and out `on
|
||||
eprint now <https://eprint.iacr.org/2025/1962>`__. The topic of the paper is a way of monitoring a tamper-sensing
|
||||
mesh through time-domain reflectometry using very cheap components. The end result is a circuit that costs about
|
||||
10 € in parts that is able to measure TDR responses with a few hundred picoseconds of resolution.
|
||||
---
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="pic_board_setup_2_small.jpg" alt="A PCB with several chips sitting on a table with another PCB
|
||||
with only traces on it plugged in through a board-edge connector. The first PCB looks not very complex.">
|
||||
<figcaption>
|
||||
The final setup. On the right is the measurement board, and on the left is the mesh test specimen plugged
|
||||
in. In a real application, you would integrate both into your target circuit.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
I've got a new paper accepted at CHES, to be published in TCHES 2026/1 around beginning of December and out
|
||||
`on eprint now <https://eprint.iacr.org/2025/1962>`__. The topic of the paper is a way of monitoring a tamper-sensing
|
||||
mesh through time-domain reflectometry using very cheap components. The end result is a circuit that costs about 10 € in
|
||||
parts that is able to measure TDR responses with a few hundred picoseconds of resolution.
|
||||
|
||||
Tamper-Sensing meshes are squiggly circuit traces that are used to tamper-proof high-security devices like hardware
|
||||
security modules, ATM pin pads and countertop card payment terminals. Any area where you would like to prevent an
|
||||
attacker from drilling or sawing through in a physical attack, you completely cover with one or more such circuit traces
|
||||
in a meandering pattern. I've written up some work on a KiCad plugin for creating these meshes `in another post
|
||||
<{{< ref "blog/kicad-mesh-plugin" >}}>`__.
|
||||
|
||||
Up to now, the state of the art in monitoring these security meshes has mostly been finding ways to precisely monitor
|
||||
their ohmic resistance in the analog domain. This has the disadvantage of both being fairly complex in circuitry and of
|
||||
presenting a steep trade-off between sensitivity and false-positive rate since all you get out of the whole mesh is a
|
||||
single analog measurement containing maybe 12 to 16 bits of entropy. There have been a few papers on using more advanced
|
||||
RF techniques, but they all either required really expensive circuitry and/or highly customized meshes that for instance
|
||||
couldn't easily be fitted into arbitrary shapes.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="fig_edge_risetime.png" alt="Four plots showing edge response for four different chips: 74LVC2G157,
|
||||
MAX3748, TDP0604 and PI3HDX12211. The first two are fairly slow at about 1 ns risetime, while the last two are
|
||||
very fast at around 300 ps risetime.">
|
||||
<figcaption>
|
||||
The sampling edges as measured by the board itself. As you can see, using a cheap microcontroller and some
|
||||
cheap display signal redriver ICs along with commodity RF schottkies you can get pretty spicy edges on a
|
||||
budget. <a href="fig_edge_risetime.pdf">Link to full resolution.</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
In this paper, I wrote up a method using the high-resolution timer of an inexpensive `STM32G4-series microcontroller
|
||||
<https://www.st.com/resource/en/datasheet/stm32g474cb.pdf>`__ together with a DisplayPort/HDMI "redriver" chips meant for
|
||||
amplifying high-speed display signals to create fast pulse edges. I characterized several chips, with the best
|
||||
performers being TI's `TDP0604 <https://www.ti.com/product/TDP0604>`__ and Diodes' `PI3HDX12211
|
||||
<https://www.diodes.com/part/view/PI3HDX12211>`__, coming in at 2 to 5 € depending on where and how much you buy. The
|
||||
fast edges generated by these drivers are then fed to a set of four-diode sampling gates using cheap RF schottky diodes
|
||||
to create a really cheap but fast time-domain reflectometer. Using this TDRD circuit, a security mesh can be monitored
|
||||
much more precisely than before, since the circuit creates a sort of fingerprint of the mesh's trace along its length.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<img src="pic_74lvc_small.jpg" alt="">
|
||||
<figcaption>
|
||||
74LVC2G157
|
||||
</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="pic_max3748_small.jpg" alt="">
|
||||
<figcaption>
|
||||
MAX3748
|
||||
</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="pic_tdp0604_small.jpg" alt="">
|
||||
<figcaption>
|
||||
TDP0604
|
||||
</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="pic_pi3hdx_small.jpg" alt="">
|
||||
<figcaption>
|
||||
PI3HDX12211
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
One of the fun highlights of this project to me was micro-soldering test boards using different redriver ICs. Above, you
|
||||
can see the result of that soldering work. I was really happy with my cheap aliexpress microscope and with my fancy
|
||||
titanium tweezers!
|
||||
|
||||
Have a look into the paper, where I wrote up details on the circuitry as well as a whole bunch of (>1000!) measurements
|
||||
characterizing the system. As it turns out, it's really sensitive to attacks while being reasonably robust to
|
||||
environmental disturbances. In fact, it's so sensitive that the circuit can distinguish multiple identical (!) copies of
|
||||
the same mesh produces by JLCPCB from their manufacturing tolerances such as FR-4 fiber weave alignment.
|
||||
|
||||
You can find a preprint of the paper `on eprint <https://eprint.iacr.org/2025/1962>`__, and I'll update this post with a
|
||||
link to the published version of the paper when it becomes available. The eprint is identical to the published version
|
||||
as of now.
|
||||
|
||||
The source code of the project is available at `https://git.jaseg.de/sampling-mesh-monitor.git <https://git.jaseg.de/sampling-mesh-monitor.git>`__.
|
||||
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 744 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
|
@ -1,644 +0,0 @@
|
|||
<svg width="400" height="850" viewBox="0 0 400 850" version="1.1" id="diagram" inkscape:version="1.4.3 (0d15f75042, 2025-12-25)" sodipodi:docname="template diagram.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" inkscape:zoom="0.38363396" inkscape:cx="624.293" inkscape:cy="1773.8263" inkscape:window-width="1920" inkscape:window-height="1011" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="layer1"></sodipodi:namedview>
|
||||
<defs id="defs1"></defs>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1">
|
||||
<g id="g161" transform="matrix(1.6362345,0,0,1.6333326,-42.18288,17.818863)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path156" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path157" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g160" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g159" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path158" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path159" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="use159" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path249" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path250" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="use160" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path251" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path252" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g231" transform="matrix(-1.6362345,0,0,-1.6333326,442.18287,832.18114)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path226" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path227" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g230" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g229" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path228" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path229" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="use229" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path253" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path254" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="use230" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path255" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path256" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g266" transform="matrix(1.6362345,0,0,1.6333326,22.62511,17.818863)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path257" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path258" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g265" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g260" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path259" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path260" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g262" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path261" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path262" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g264" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path263" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path264" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g340" transform="matrix(-1.6362345,0,0,-1.6333326,377.37488,832.18114)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path331" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path332" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g339" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g334" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path333" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path334" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g336" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path335" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path336" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g338" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path337" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path338" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g351" transform="matrix(1.6362345,0,0,1.6333326,87.4331,17.818863)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path342" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path343" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g350" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g345" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path344" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path345" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g347" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path346" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path347" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g349" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path348" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path349" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g425" transform="matrix(-1.6362345,0,0,-1.6333326,312.56689,832.18114)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path416" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path417" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g424" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g419" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path418" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path419" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g421" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path420" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path421" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g423" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path422" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path423" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g436" transform="matrix(1.6362345,0,0,1.6333326,152.2411,17.818863)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path427" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path428" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g435" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g430" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path429" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path430" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g432" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path431" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path432" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g434" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path433" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path434" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g510" transform="matrix(-1.6362345,0,0,-1.6333326,247.75889,832.18114)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path501" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path502" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g509" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g504" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path503" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path504" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g506" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path505" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path506" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g508" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path507" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path508" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g521" transform="matrix(1.6362345,0,0,1.6333326,217.04909,17.818863)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path512" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path513" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g520" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g515" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path514" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path515" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g517" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path516" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path517" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g519" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path518" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path519" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g-syms">
|
||||
<g transform="translate(46.034012,244.1913)"><g transform="translate(-119.39419,0.17868006)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 119.74254,49.769006 8.58542,-5.325948 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817" sodipodi:nodetypes="ccsssc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 119.30817,0 5,4.9999995 c -1.16153,1.0407303 -2.04339,1.4798376 -1.93989,3.1740022 0.0887,1.4518899 1.93989,2.8212553 1.93989,4.5928673" sodipodi:nodetypes="ccsc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 124.30817,4.9999995 c 0,0 4.51153,-4.68883452 7.5,-4.99999988146971 C 136.27168,-0.4647495 139.82053,4.9999996 144.30817,4.9999995 c 4.48764,-1e-7 8.03649,-5.4647489 12.5,-4.99999988146971 C 159.79664,0.31116494 164.30817,4.9999995 164.30817,4.9999995 166.80972,4.5558443 168.47639,2.8891778 169.30817,0" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 164.30817,4.9999995 c -10.19596,7.5693475 -5.03109,14.1255265 -20,13.9440995 h -20 v 20.036653" sodipodi:nodetypes="cccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 128.52503,18.944099 v 26.0559"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 132.86354,18.944099 v 8.161327" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 132.86354,31.098111 v 3.588242"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 164.30817,4.9999995 c 2.71027,1.0765294 4.37694,2.8984926 5,5.4658895" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 166.75159,6.6041964 C 160.73941,11.685736 162.3096,19.40526 144.30817,18.944099" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 161.36361,2.3663116 C 153.77676,7.3193075 155.36824,18.9441 144.30817,18.944099" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="160.1546" cy="40.071251" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 158.66296,36.616342 V 28.455015" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 158.66296,24.46233 V 20.874088"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 162.05717,36.616342 V 23.72634" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(110.84201,244.1913)"><g transform="translate(-360.56911,-0.32499981)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 367.2067,4.1382117 h 13.00315" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 390.92836,4.1382117 h 12.60441" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 360.56911,0.79099163 5,5.00000097 c 0,0 4.49103,-4.79558397 7.5,-5.11281697 4.47693,-0.471998 6.30362,5.11281797 12.5,5.11281697 6.19637,-1e-6 8.02307,-5.58481397 12.5,-5.11281697 3.00897,0.317233 7.5,5.11281697 7.5,5.11281697 l 5,-5.00000097" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 365.56911,5.7909925 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 360.56911,49.859008 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,1e-6 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 365.56911,5.7909925 c 2.46694,17.8627875 15.37482,18.1852565 25.55077,19.8898465 7.98978,1.338381 13.98961,3.752574 13.98961,10.51405 0,10.438995 -11.41847,8.805111 -20,8.805111" sodipodi:nodetypes="cssc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 405.56911,5.7909925 c 2.2374,2.5816135 1.45401,9.0440415 -2.40843,8.3202305 -0.64228,-0.120362 -1.50716,-0.601996 -1.9994,-1.359794" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="372.6095" cy="35.620834" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="384.18478" cy="35.620834" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="395.7601" cy="35.620834" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 365.56911,5.7909925 c 0.67367,15.9969505 2.48253,19.4048495 25.55077,19.8898465" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 365.56911,5.7909925 c 6.07836,7.0954705 7.63962,15.0532565 25.55077,19.8898465" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(175.64999,244.1913)"><g transform="translate(-780.08865)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 780.08865,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 780.08865,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30363,5.11281804 12.5,5.11281704 6.19638,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 825.08863,4.9999997 c 0.21696,4.3500699 -3.34386,7.0986003 -4.38366,9.6616903 -0.88903,2.19143 0.32027,5.73553 4.47838,6.51253 l -0.0947,-16.1742203" sodipodi:nodetypes="cscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 825.08863,44.967621 c 0.21696,-4.35007 -3.34386,-7.0986 -4.38366,-9.66169 -0.88903,-2.19143 0.32027,-5.73553 4.47838,-6.51253 l -0.0947,16.17422" sodipodi:nodetypes="cscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 785.08865,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 781.61647,4.9229684 V 35.139809 m 0,4.243939 v 4.15906" sodipodi:nodetypes="cccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 788.68965,47.956504 V 18.079177"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="793.10529" cy="8.4932022" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 809.52664,46.315201 h 13.98521"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 823.64134,3.6689583 H 810.17411"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 805.08865,45 c -5.50339,0.275105 -7.13991,-5.644712 -3.36706,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 805.08865,45 c 5.88891,-0.02336 7.01944,-6.401058 3.36705,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 805.0978,26.85729 V 7.8887922" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 803.49892,33.515251 c 2.14158,-3.100165 1.45111,-4.394055 1.64017,-7.560652 -0.22335,4.072372 0.66679,6.340174 1.71881,7.496886" sodipodi:nodetypes="ccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 800.93368,11.019091 V 25.336892" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 809.21555,17.476138 V 28.846156" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 805.07014,35.966673 v 3.389599" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(240.45799,244.1913)"><g transform="translate(-60.320579,-0.72048732)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 60.320579,50.649983 5,-5.000001 c 0,0 4.491029,4.795584 7.5,5.112817 4.476927,0.471998 6.303625,-5.112818 12.5,-5.112817 6.196375,10e-7 8.023072,5.584814 12.500001,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 85.320579,45.649982 V 0.64998316"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 90.450739,47.159351 V 4.5724897" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 80.207844,9.9316237 V 29.256542"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 110.32058,10.036075 c -2.65344,0.845735 -4.32011,2.814353 -5,5.905855 L 85.320579,0.64998316 l -20,15.29194684 c -1.534813,-4.341975 -3.278197,-4.929683 -5,-5.905855" sodipodi:nodetypes="ccccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.370059,45.622266 c 5.023416,-4.972461 2.081699,-8.525452 0,-7.413891" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 80.207844,32.527209 v 3.389599" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 104.86331,45.188027 c -5.02342,-4.972461 -2.0817,-8.525452 0,-7.413891" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="67.021469" cy="6.6147523" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="97.025314" cy="43.80703" r="3.75"></circle>
|
||||
</g></g>
|
||||
<g transform="translate(305.26599,244.1913)"><g transform="translate(-299.10327,-0.05640831)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 299.10327,0.11281562 5,5.00000108 c 0,0 4.49103,-4.79558413 7.5,-5.11281708146972 C 316.0802,-0.47199838 317.9069,5.1128177 324.10327,5.1128167 c 6.19637,-1e-6 8.02307,-5.58481408 12.5,-5.11281708146972 C 339.61224,0.31723257 344.10327,5.1128167 344.10327,5.1128167 l 5,-5.00000108" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 304.10327,5.1128167 V 18.944099 c 0,0 4.29426,5.75201 7.5,6.120911 4.60897,0.530378 7.86066,-6.143792 12.5,-6.120911" sodipodi:nodetypes="ccsc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 307.18364,2.3049292 V 22.386495"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 304.10327,18.944099 h -5"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 349.10327,18.944099 h -5 V 45 45"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 299.10327,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 344.10327,26.011521 h 5"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 344.10327,36.009434 h 5"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="324.13705" cy="34.92308" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="324.23035" cy="12.325731" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 324.10327,18.944099 c 8.05987,-0.174575 14.08837,1.735136 15.80413,8.31924 1.69321,6.497581 -0.62193,15.428971 -7.59368,21.123698" sodipodi:nodetypes="csc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(305.26599,557.1087)"><g transform="translate(-299.10327,-0.05640831)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 299.10327,0.11281562 5,5.00000108 c 0,0 4.49103,-4.79558413 7.5,-5.11281708146972 C 316.0802,-0.47199838 317.9069,5.1128177 324.10327,5.1128167 c 6.19637,-1e-6 8.02307,-5.58481408 12.5,-5.11281708146972 C 339.61224,0.31723257 344.10327,5.1128167 344.10327,5.1128167 l 5,-5.00000108" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 304.10327,5.1128167 V 18.944099 c 0,0 4.29426,5.75201 7.5,6.120911 4.60897,0.530378 7.86066,-6.143792 12.5,-6.120911" sodipodi:nodetypes="ccsc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 307.18364,2.3049292 V 22.386495"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 304.10327,18.944099 h -5"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 349.10327,18.944099 h -5 V 45 45"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 299.10327,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 344.10327,26.011521 h 5"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 344.10327,36.009434 h 5"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="324.13705" cy="34.92308" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="324.23035" cy="12.325731" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 324.10327,18.944099 c 8.05987,-0.174575 14.08837,1.735136 15.80413,8.31924 1.69321,6.497581 -0.62193,15.428971 -7.59368,21.123698" sodipodi:nodetypes="csc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(240.45799,557.1087)"><g transform="translate(-360.56911,-0.32499981)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 367.2067,4.1382117 h 13.00315" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 390.92836,4.1382117 h 12.60441" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 360.56911,0.79099163 5,5.00000097 c 0,0 4.49103,-4.79558397 7.5,-5.11281697 4.47693,-0.471998 6.30362,5.11281797 12.5,5.11281697 6.19637,-1e-6 8.02307,-5.58481397 12.5,-5.11281697 3.00897,0.317233 7.5,5.11281697 7.5,5.11281697 l 5,-5.00000097" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 365.56911,5.7909925 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 360.56911,49.859008 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,1e-6 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 365.56911,5.7909925 c 2.46694,17.8627875 15.37482,18.1852565 25.55077,19.8898465 7.98978,1.338381 13.98961,3.752574 13.98961,10.51405 0,10.438995 -11.41847,8.805111 -20,8.805111" sodipodi:nodetypes="cssc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 405.56911,5.7909925 c 2.2374,2.5816135 1.45401,9.0440415 -2.40843,8.3202305 -0.64228,-0.120362 -1.50716,-0.601996 -1.9994,-1.359794" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="372.6095" cy="35.620834" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="384.18478" cy="35.620834" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="395.7601" cy="35.620834" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 365.56911,5.7909925 c 0.67367,15.9969505 2.48253,19.4048495 25.55077,19.8898465" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 365.56911,5.7909925 c 6.07836,7.0954705 7.63962,15.0532565 25.55077,19.8898465" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(175.64999,557.1087)"><g transform="translate(-959.87612,0.16568683)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 959.87614,-1.393323e-6 5,5.000001093323 c 0,0 4.49103,-4.79558409 7.5,-5.11281709 4.47693,-0.471998 6.30362,5.11281809 12.5,5.11281709 6.19637,-1e-6 8.02307,-5.58481409 12.5,-5.11281709 3.00896,0.317233 7.49996,5.11281709 7.49996,5.11281709 l 5,-5.000001093323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,13.9441 c 0,0 -4.5115,4.688835 -7.49996,5 -4.46351,0.464749 -8.01236,-5 -12.5,-5 -4.48764,10e-7 -8.03649,5.464749 -12.5,5 -2.98847,-0.311165 -7.5,-5 -7.5,-5 l -5,5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,29.234324 c 0,0 -4.5115,-4.688835 -7.49996,-5 -4.46351,-0.464749 -8.01236,5 -12.5,5 -4.48764,-1e-6 -8.03649,-5.464749 -12.5,-5 -2.98847,0.311165 -7.5,5 -7.5,5 l -5,-5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 964.87614,4.9999995 V 44.999999 l -5,5"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1009.8761,50 -5,-5.000001"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="996.3548" cy="32.081917" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="973.36859" cy="32.081917" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.64157,18.944099 h 8.05261"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.64157,23.844193 h 8.05261"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 964.87614,44.999999 c 1.52794,1.567889 3.49586,3.824705 5.90289,4.150034 3.31701,0.448321 6.61329,-1.761337 3.01008,-5.359858" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,36.553965 v 8.446034 c -5.26884,10e-7 -5.30302,-3.178959 -8.446,-2.656165 -2.50728,0.417055 -4.208,5.345071 -8.79175,4.932502 -2.8184,-0.253675 -6.58902,-4.444943 -2.41965,-8.123103" sodipodi:nodetypes="ccssc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.67593,39.861999 v 5.979297"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 977.96735,40.935207 v 5.979297"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 966.11974,3.3509071 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 989.88688,3.3509071 h 13.50782" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(110.84201,557.1087)"><g transform="translate(-239.41804,-0.03527479)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 254.1946,10.41937 C 257.4772,8.1822111 257.31825,1.9621626 251.91804,-3.8146971e-7 L 244.41804,4.9999995 239.41804,0" sodipodi:nodetypes="cccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 244.41804,4.9999995 V 25" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 239.41804,30.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,1e-6 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 280.33775,4.9999995 0.0947,23.4848985" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 284.41804,9.5668763 V 4.9999995 L 289.41804,0 m -5,14.776561 v 6.251623 m 0,3.971817 V 45 l 5,5" sodipodi:nodetypes="cccccccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 276.91804,30.112817 277.01276,45" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 276.91804,30.112817 c 4.1581,0.777006 5.36741,4.321099 4.47838,6.512529 -1.0398,2.563095 -4.60062,4.024586 -4.38366,8.374654" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="264.41803" cy="8.75" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 276.66834,9.9296362 V 18.9441"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 244.41804,25 c -2.53342,6.279089 -2.53342,10.205714 0,11.779874" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 239.41804,49.929551 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(46.034012,557.1087)"><g transform="translate(-540.22908)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30362,5.11281804 12.5,5.11281704 6.19637,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 545.22908,4.9999995 c 0,11.3734135 7.5,13.9441005 20,13.9441005 13.64309,0 20,10.820016 20,26.055899" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,42.117692 V 18.9441" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 585.22908,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,9.8540227 V 5.3896022" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,16.492135 V 12.027714" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="40.150173" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="28.237993" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 547.84097,2.5706087 c 0.72144,10.3666863 6.51748,15.1776933 17.38811,16.3734903" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 550.84902,0.51234009 C 553.06979,13.271479 558.62122,16.154037 565.22908,18.944099" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 581.95532,21.481266 V 7.1771697" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 583.56973,46.341819 H 570.0619" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 560.22411,46.341819 H 546.71628" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 570.01078,3.5152919 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(305.26599,494.52521)"><g transform="translate(-540.22908)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30362,5.11281804 12.5,5.11281704 6.19637,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 545.22908,4.9999995 c 0,11.3734135 7.5,13.9441005 20,13.9441005 13.64309,0 20,10.820016 20,26.055899" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,42.117692 V 18.9441" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 585.22908,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,9.8540227 V 5.3896022" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,16.492135 V 12.027714" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="40.150173" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="28.237993" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 547.84097,2.5706087 c 0.72144,10.3666863 6.51748,15.1776933 17.38811,16.3734903" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 550.84902,0.51234009 C 553.06979,13.271479 558.62122,16.154037 565.22908,18.944099" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 581.95532,21.481266 V 7.1771697" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 583.56973,46.341819 H 570.0619" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 560.22411,46.341819 H 546.71628" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 570.01078,3.5152919 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(240.45799,494.52521)"><g transform="translate(-180)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 180,0 5,4.9999995 c 0,0 4.51153,-4.68883452 7.5,-4.99999988146971 C 196.96351,-0.4647495 200.51236,4.9999996 205,4.9999995 c 4.48764,-1e-7 8.03649,-5.4647489 12.5,-4.99999988146971 C 220.48847,0.31116494 225,4.9999995 225,4.9999995 L 230,0" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 185,16.415384 c 0,0 4.51153,4.688835 7.5,5 4.46351,0.464749 8.01236,-5 12.5,-5 4.48764,1e-6 8.03649,5.464749 12.5,5 2.98847,-0.311165 7.5,-5 7.5,-5 l 5,5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 185,36.353587 c 0,0 4.51153,-4.688835 7.5,-5 4.46351,-0.464749 8.01236,5 12.5,5 4.48764,0 8.03649,-5.464749 12.5,-5 2.98847,0.311165 7.5,5 7.5,5 l 5,-5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 180,50 5,-5 c 0,0 4.51153,4.688835 7.5,5 4.46351,0.464749 8.01236,-5 12.5,-5 4.48764,0 8.03649,5.464749 12.5,5 2.98847,-0.311165 7.5,-5 7.5,-5 l 5,5" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 225,4.9999995 c 3.83777,3.8750224 3.02666,8.4473215 0,11.4153845" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 222.49008,18.70183 c 3.83777,3.875023 5.16123,10.492988 -0.18333,15.217039" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 225,36.353587 c 3.28037,3.234007 1.98398,6.662437 0,8.646413" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="186.47734" cy="26.556124" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 190.22733,26.556123 h 17.98699"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 212.67905,26.556123 h 8.41916" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 205.19514,22.7441 h 6.71248" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 186.78681,3.3231001 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 199.58221,30.153165 h 7.35985" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 209.60505,3.3231001 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 186.78681,46.676899 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 209.60505,46.676899 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(175.64999,494.52521)"><g transform="translate(-899.73404,-0.05640831)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 906.24846,46.417268 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 899.73404,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 899.73404,0.11281562 5,5.00000098 c 0,0 4.49103,-4.79558398 7.5,-5.11281698146972 C 916.71097,-0.47199838 918.53767,5.1128176 924.73404,5.1128166 c 6.19638,-1e-6 8.02307,-5.58481398 12.5,-5.11281698146972 C 940.24301,0.31723262 944.73404,5.1128166 944.73404,5.1128166 l 5,-5.00000098" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 904.73404,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 944.73404,4.9999995 c 0,4.2039086 -2.77016,13.9440995 -20,13.9440995 -5.34243,0 -14.43333,2.886895 -14.93213,24.215625" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 910.02737,38.962031 C 910.64384,34.311443 918.3477,18.9441 929.67796,18.642079" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 910.02737,38.962031 c 3.98934,-6.00506 6.16541,-11.466138 19.65059,-20.319952" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="936.4093" cy="8.3030958" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 929.71179,46.417268 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 908.69979,4.9999995 V 16.824321" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 901.12678,43.087811 V 26.810194"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 944.73404,44.999999 c -4.12576,-3.512349 -0.46775,-10.554494 2.43592,-6.718382" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 925.08406,5.1734743 c -8.03691,-0.158222 -8.29336,5.4713477 -13.77731,5.5725837" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="936.31976" cy="38.753807" r="3.75"></circle>
|
||||
</g></g>
|
||||
<g transform="translate(110.84201,494.52521)"><g transform="translate(-780.08865)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 780.08865,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 780.08865,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30363,5.11281804 12.5,5.11281704 6.19638,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 825.08863,4.9999997 c 0.21696,4.3500699 -3.34386,7.0986003 -4.38366,9.6616903 -0.88903,2.19143 0.32027,5.73553 4.47838,6.51253 l -0.0947,-16.1742203" sodipodi:nodetypes="cscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 825.08863,44.967621 c 0.21696,-4.35007 -3.34386,-7.0986 -4.38366,-9.66169 -0.88903,-2.19143 0.32027,-5.73553 4.47838,-6.51253 l -0.0947,16.17422" sodipodi:nodetypes="cscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 785.08865,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 781.61647,4.9229684 V 35.139809 m 0,4.243939 v 4.15906" sodipodi:nodetypes="cccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 788.68965,47.956504 V 18.079177"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="793.10529" cy="8.4932022" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 809.52664,46.315201 h 13.98521"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 823.64134,3.6689583 H 810.17411"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 805.08865,45 c -5.50339,0.275105 -7.13991,-5.644712 -3.36706,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 805.08865,45 c 5.88891,-0.02336 7.01944,-6.401058 3.36705,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 805.0978,26.85729 V 7.8887922" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 803.49892,33.515251 c 2.14158,-3.100165 1.45111,-4.394055 1.64017,-7.560652 -0.22335,4.072372 0.66679,6.340174 1.71881,7.496886" sodipodi:nodetypes="ccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 800.93368,11.019091 V 25.336892" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 809.21555,17.476138 V 28.846156" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 805.07014,35.966673 v 3.389599" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(46.034012,494.52521)"><g transform="translate(-540.22908)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30362,5.11281804 12.5,5.11281704 6.19637,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 545.22908,4.9999995 c 0,11.3734135 7.5,13.9441005 20,13.9441005 13.64309,0 20,10.820016 20,26.055899" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,42.117692 V 18.9441" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 585.22908,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,9.8540227 V 5.3896022" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,16.492135 V 12.027714" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="40.150173" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="28.237993" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 547.84097,2.5706087 c 0.72144,10.3666863 6.51748,15.1776933 17.38811,16.3734903" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 550.84902,0.51234009 C 553.06979,13.271479 558.62122,16.154037 565.22908,18.944099" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 581.95532,21.481266 V 7.1771697" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 583.56973,46.341819 H 570.0619" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 560.22411,46.341819 H 546.71628" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 570.01078,3.5152919 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(305.26599,431.94174)"><g transform="translate(-959.87612,0.16568683)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 959.87614,-1.393323e-6 5,5.000001093323 c 0,0 4.49103,-4.79558409 7.5,-5.11281709 4.47693,-0.471998 6.30362,5.11281809 12.5,5.11281709 6.19637,-1e-6 8.02307,-5.58481409 12.5,-5.11281709 3.00896,0.317233 7.49996,5.11281709 7.49996,5.11281709 l 5,-5.000001093323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,13.9441 c 0,0 -4.5115,4.688835 -7.49996,5 -4.46351,0.464749 -8.01236,-5 -12.5,-5 -4.48764,10e-7 -8.03649,5.464749 -12.5,5 -2.98847,-0.311165 -7.5,-5 -7.5,-5 l -5,5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,29.234324 c 0,0 -4.5115,-4.688835 -7.49996,-5 -4.46351,-0.464749 -8.01236,5 -12.5,5 -4.48764,-1e-6 -8.03649,-5.464749 -12.5,-5 -2.98847,0.311165 -7.5,5 -7.5,5 l -5,-5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 964.87614,4.9999995 V 44.999999 l -5,5"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1009.8761,50 -5,-5.000001"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="996.3548" cy="32.081917" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="973.36859" cy="32.081917" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.64157,18.944099 h 8.05261"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.64157,23.844193 h 8.05261"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 964.87614,44.999999 c 1.52794,1.567889 3.49586,3.824705 5.90289,4.150034 3.31701,0.448321 6.61329,-1.761337 3.01008,-5.359858" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,36.553965 v 8.446034 c -5.26884,10e-7 -5.30302,-3.178959 -8.446,-2.656165 -2.50728,0.417055 -4.208,5.345071 -8.79175,4.932502 -2.8184,-0.253675 -6.58902,-4.444943 -2.41965,-8.123103" sodipodi:nodetypes="ccssc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.67593,39.861999 v 5.979297"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 977.96735,40.935207 v 5.979297"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 966.11974,3.3509071 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 989.88688,3.3509071 h 13.50782" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(240.45799,431.94174)"><g transform="translate(-959.87612,0.16568683)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 959.87614,-1.393323e-6 5,5.000001093323 c 0,0 4.49103,-4.79558409 7.5,-5.11281709 4.47693,-0.471998 6.30362,5.11281809 12.5,5.11281709 6.19637,-1e-6 8.02307,-5.58481409 12.5,-5.11281709 3.00896,0.317233 7.49996,5.11281709 7.49996,5.11281709 l 5,-5.000001093323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,13.9441 c 0,0 -4.5115,4.688835 -7.49996,5 -4.46351,0.464749 -8.01236,-5 -12.5,-5 -4.48764,10e-7 -8.03649,5.464749 -12.5,5 -2.98847,-0.311165 -7.5,-5 -7.5,-5 l -5,5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,29.234324 c 0,0 -4.5115,-4.688835 -7.49996,-5 -4.46351,-0.464749 -8.01236,5 -12.5,5 -4.48764,-1e-6 -8.03649,-5.464749 -12.5,-5 -2.98847,0.311165 -7.5,5 -7.5,5 l -5,-5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 964.87614,4.9999995 V 44.999999 l -5,5"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1009.8761,50 -5,-5.000001"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="996.3548" cy="32.081917" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="973.36859" cy="32.081917" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.64157,18.944099 h 8.05261"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.64157,23.844193 h 8.05261"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 964.87614,44.999999 c 1.52794,1.567889 3.49586,3.824705 5.90289,4.150034 3.31701,0.448321 6.61329,-1.761337 3.01008,-5.359858" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 1004.8761,36.553965 v 8.446034 c -5.26884,10e-7 -5.30302,-3.178959 -8.446,-2.656165 -2.50728,0.417055 -4.208,5.345071 -8.79175,4.932502 -2.8184,-0.253675 -6.58902,-4.444943 -2.41965,-8.123103" sodipodi:nodetypes="ccssc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 980.67593,39.861999 v 5.979297"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 977.96735,40.935207 v 5.979297"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 966.11974,3.3509071 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 989.88688,3.3509071 h 13.50782" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(175.64999,431.94174)"><g transform="translate(-540.22908)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30362,5.11281804 12.5,5.11281704 6.19637,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 545.22908,4.9999995 c 0,11.3734135 7.5,13.9441005 20,13.9441005 13.64309,0 20,10.820016 20,26.055899" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,42.117692 V 18.9441" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 585.22908,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,9.8540227 V 5.3896022" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,16.492135 V 12.027714" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="40.150173" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="28.237993" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 547.84097,2.5706087 c 0.72144,10.3666863 6.51748,15.1776933 17.38811,16.3734903" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 550.84902,0.51234009 C 553.06979,13.271479 558.62122,16.154037 565.22908,18.944099" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 581.95532,21.481266 V 7.1771697" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 583.56973,46.341819 H 570.0619" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 560.22411,46.341819 H 546.71628" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 570.01078,3.5152919 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(110.84201,431.94174)"><g transform="translate(-659.83213)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 659.83213,-1.333323e-6 5,5.000001033323 c 0,0 4.49103,-4.79558403 7.5,-5.11281703 4.47693,-0.471998 6.30363,5.11281803 12.5,5.11281703 6.19637,-1e-6 8.02307,-5.58481403 12.5,-5.11281703 3.00897,0.317233 7.5,5.11281703 7.5,5.11281703 l 5,-5.000001033323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 704.83213,4.9999995 V 44.999999" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 659.83213,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 704.83213,4.9999995 c 0,11.7291445 -12.49032,11.9447335 -20,13.9440995 -17.1817,4.574433 -20,9.177692 -20,26.0559" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="696.87048" cy="7.6883817" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 684.83213,18.944099 C 669.70946,25.630335 667.65062,34.578608 667.243,47.261515" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,18.944099 c -9.52802,6.736316 -14.22762,17.808835 -14.43982,30.512904" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 701.49255,43.009851 V 23.684932"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 701.49255,20.414266 V 17.024667" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 708.10713,29.283604 V 9.9586847"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 664.80706,4.9417592 c 4.23654,4.6444599 7.1266,3.676801 9.61027,-0.5386796" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,45.017665 c -5.50339,0.275105 -7.13991,-5.644712 -3.36705,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,45.017665 c 5.88891,-0.02336 7.01944,-6.401058 3.36706,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(46.034012,431.94174)"><g transform="translate(-180)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 180,0 5,4.9999995 c 0,0 4.51153,-4.68883452 7.5,-4.99999988146971 C 196.96351,-0.4647495 200.51236,4.9999996 205,4.9999995 c 4.48764,-1e-7 8.03649,-5.4647489 12.5,-4.99999988146971 C 220.48847,0.31116494 225,4.9999995 225,4.9999995 L 230,0" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 185,16.415384 c 0,0 4.51153,4.688835 7.5,5 4.46351,0.464749 8.01236,-5 12.5,-5 4.48764,1e-6 8.03649,5.464749 12.5,5 2.98847,-0.311165 7.5,-5 7.5,-5 l 5,5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 185,36.353587 c 0,0 4.51153,-4.688835 7.5,-5 4.46351,-0.464749 8.01236,5 12.5,5 4.48764,0 8.03649,-5.464749 12.5,-5 2.98847,0.311165 7.5,5 7.5,5 l 5,-5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 180,50 5,-5 c 0,0 4.51153,4.688835 7.5,5 4.46351,0.464749 8.01236,-5 12.5,-5 4.48764,0 8.03649,5.464749 12.5,5 2.98847,-0.311165 7.5,-5 7.5,-5 l 5,5" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 225,4.9999995 c 3.83777,3.8750224 3.02666,8.4473215 0,11.4153845" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 222.49008,18.70183 c 3.83777,3.875023 5.16123,10.492988 -0.18333,15.217039" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 225,36.353587 c 3.28037,3.234007 1.98398,6.662437 0,8.646413" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="186.47734" cy="26.556124" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 190.22733,26.556123 h 17.98699"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 212.67905,26.556123 h 8.41916" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 205.19514,22.7441 h 6.71248" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 186.78681,3.3231001 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 199.58221,30.153165 h 7.35985" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 209.60505,3.3231001 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 186.78681,46.676899 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 209.60505,46.676899 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(305.26599,369.35828)"><g transform="translate(-239.41804,-0.03527479)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 254.1946,10.41937 C 257.4772,8.1822111 257.31825,1.9621626 251.91804,-3.8146971e-7 L 244.41804,4.9999995 239.41804,0" sodipodi:nodetypes="cccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 244.41804,4.9999995 V 25" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 239.41804,30.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,1e-6 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 280.33775,4.9999995 0.0947,23.4848985" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 284.41804,9.5668763 V 4.9999995 L 289.41804,0 m -5,14.776561 v 6.251623 m 0,3.971817 V 45 l 5,5" sodipodi:nodetypes="cccccccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 276.91804,30.112817 277.01276,45" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 276.91804,30.112817 c 4.1581,0.777006 5.36741,4.321099 4.47838,6.512529 -1.0398,2.563095 -4.60062,4.024586 -4.38366,8.374654" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="264.41803" cy="8.75" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 276.66834,9.9296362 V 18.9441"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 244.41804,25 c -2.53342,6.279089 -2.53342,10.205714 0,11.779874" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 239.41804,49.929551 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(240.45799,369.35828)"><g transform="translate(-839.96599,-0.07049614)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 839.96599,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 839.96599,0.14099127 5,5.00000103 c 0,0 4.49103,-4.79558403 7.5,-5.11281703 4.47693,-0.471998 6.30362,5.11281803 12.5,5.11281703 6.19637,-1e-6 8.02307,-5.58481403 12.5,-5.11281703 3.00897,0.317233 7.5,5.11281703 7.5,5.11281703 l 5,-5.00000103" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 844.96599,5.1409922 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 844.96599,4.9999995 c 3.99322,3.9769456 13.80418,3.0859442 20,2.8197421 12.87775,-0.5532898 20,2.0148091 20,11.1243574 0,10.948921 -4.5367,26.055901 -20,26.0559 -6.66667,0 -20,0 -20,0" sodipodi:nodetypes="csssc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="884.82922" cy="38.535431" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 864.5731,7.7504074 c 11.43924,0.6588661 20,5.6821266 20,17.0478896" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 864.96599,7.8197415 c 10.72409,4.1364695 17.25979,9.7959875 19.60711,16.9785555" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 848.53314,10.185391 V 40.646373"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 841.60592,33.602271 V 17.515065"></path>
|
||||
</g></g>
|
||||
<g transform="translate(175.64999,369.35828)"><g transform="translate(-839.96599,-0.07049614)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 839.96599,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 839.96599,0.14099127 5,5.00000103 c 0,0 4.49103,-4.79558403 7.5,-5.11281703 4.47693,-0.471998 6.30362,5.11281803 12.5,5.11281703 6.19637,-1e-6 8.02307,-5.58481403 12.5,-5.11281703 3.00897,0.317233 7.5,5.11281703 7.5,5.11281703 l 5,-5.00000103" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 844.96599,5.1409922 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 844.96599,4.9999995 c 3.99322,3.9769456 13.80418,3.0859442 20,2.8197421 12.87775,-0.5532898 20,2.0148091 20,11.1243574 0,10.948921 -4.5367,26.055901 -20,26.0559 -6.66667,0 -20,0 -20,0" sodipodi:nodetypes="csssc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="884.82922" cy="38.535431" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 864.5731,7.7504074 c 11.43924,0.6588661 20,5.6821266 20,17.0478896" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 864.96599,7.8197415 c 10.72409,4.1364695 17.25979,9.7959875 19.60711,16.9785555" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 848.53314,10.185391 V 40.646373"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 841.60592,33.602271 V 17.515065"></path>
|
||||
</g></g>
|
||||
<g transform="translate(110.84201,369.35828)"><g transform="translate(-659.83213)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 659.83213,-1.333323e-6 5,5.000001033323 c 0,0 4.49103,-4.79558403 7.5,-5.11281703 4.47693,-0.471998 6.30363,5.11281803 12.5,5.11281703 6.19637,-1e-6 8.02307,-5.58481403 12.5,-5.11281703 3.00897,0.317233 7.5,5.11281703 7.5,5.11281703 l 5,-5.000001033323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 704.83213,4.9999995 V 44.999999" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 659.83213,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 704.83213,4.9999995 c 0,11.7291445 -12.49032,11.9447335 -20,13.9440995 -17.1817,4.574433 -20,9.177692 -20,26.0559" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="696.87048" cy="7.6883817" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 684.83213,18.944099 C 669.70946,25.630335 667.65062,34.578608 667.243,47.261515" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,18.944099 c -9.52802,6.736316 -14.22762,17.808835 -14.43982,30.512904" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 701.49255,43.009851 V 23.684932"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 701.49255,20.414266 V 17.024667" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 708.10713,29.283604 V 9.9586847"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 664.80706,4.9417592 c 4.23654,4.6444599 7.1266,3.676801 9.61027,-0.5386796" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,45.017665 c -5.50339,0.275105 -7.13991,-5.644712 -3.36705,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,45.017665 c 5.88891,-0.02336 7.01944,-6.401058 3.36706,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(46.034012,369.35828)"><g transform="translate(-239.41804,-0.03527479)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 254.1946,10.41937 C 257.4772,8.1822111 257.31825,1.9621626 251.91804,-3.8146971e-7 L 244.41804,4.9999995 239.41804,0" sodipodi:nodetypes="cccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 244.41804,4.9999995 V 25" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 239.41804,30.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,1e-6 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 280.33775,4.9999995 0.0947,23.4848985" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 284.41804,9.5668763 V 4.9999995 L 289.41804,0 m -5,14.776561 v 6.251623 m 0,3.971817 V 45 l 5,5" sodipodi:nodetypes="cccccccc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 276.91804,30.112817 277.01276,45" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 276.91804,30.112817 c 4.1581,0.777006 5.36741,4.321099 4.47838,6.512529 -1.0398,2.563095 -4.60062,4.024586 -4.38366,8.374654" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="264.41803" cy="8.75" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 276.66834,9.9296362 V 18.9441"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 244.41804,25 c -2.53342,6.279089 -2.53342,10.205714 0,11.779874" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 239.41804,49.929551 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(305.26599,306.77478)"><g transform="translate(-540.22908)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30362,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 540.22908,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30362,5.11281804 12.5,5.11281704 6.19637,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 545.22908,4.9999995 c 0,11.3734135 7.5,13.9441005 20,13.9441005 13.64309,0 20,10.820016 20,26.055899" sodipodi:nodetypes="csc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,42.117692 V 18.9441" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 585.22908,4.9999995 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,9.8540227 V 5.3896022" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 588.39962,16.492135 V 12.027714" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="40.150173" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="553.70709" cy="28.237993" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 547.84097,2.5706087 c 0.72144,10.3666863 6.51748,15.1776933 17.38811,16.3734903" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 550.84902,0.51234009 C 553.06979,13.271479 558.62122,16.154037 565.22908,18.944099" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 581.95532,21.481266 V 7.1771697" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 583.56973,46.341819 H 570.0619" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 560.22411,46.341819 H 546.71628" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 570.01078,3.5152919 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(240.45799,306.77478)"><g transform="translate(-600.02515)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 600.02515,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 600.02515,-1.343323e-6 5,5.000001043323 c 0,0 4.49103,-4.79558404 7.5,-5.11281704 4.47693,-0.471998 6.30363,5.11281804 12.5,5.11281704 6.19638,-1e-6 8.02307,-5.58481404 12.5,-5.11281704 3.00897,0.317233 7.5,5.11281704 7.5,5.11281704 l 5,-5.000001043323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 605.02515,44.999999 v -26.0559 c 0,-10.7075006 11.87297,-13.9440996 20,-13.9440995 8.12703,10e-8 20,2.4481228 20,13.9440995 v 26.0559" sodipodi:nodetypes="csssc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="625.02515" cy="13.356339" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="625.02515" cy="25.11352" r="3.75"></circle>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="625.02515" cy="36.870701" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 641.70821,47.991978 V 9.9548451" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 648.3355,34.141158 V 24.706539" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 645.02515,44.999999 c 2.56662,-2.217577 3.44929,-4.781039 3.36083,-8.398763" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 608.7605,10.226326 V 24.815834" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 606.59547,46.5256 h 13.27311"></path>
|
||||
</g></g>
|
||||
<g transform="translate(175.64999,306.77478)"><g transform="translate(-839.96599,-0.07049614)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 839.96599,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19638,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 839.96599,0.14099127 5,5.00000103 c 0,0 4.49103,-4.79558403 7.5,-5.11281703 4.47693,-0.471998 6.30362,5.11281803 12.5,5.11281703 6.19637,-1e-6 8.02307,-5.58481403 12.5,-5.11281703 3.00897,0.317233 7.5,5.11281703 7.5,5.11281703 l 5,-5.00000103" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 844.96599,5.1409922 V 44.999999"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 844.96599,4.9999995 c 3.99322,3.9769456 13.80418,3.0859442 20,2.8197421 12.87775,-0.5532898 20,2.0148091 20,11.1243574 0,10.948921 -4.5367,26.055901 -20,26.0559 -6.66667,0 -20,0 -20,0" sodipodi:nodetypes="csssc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="884.82922" cy="38.535431" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 864.5731,7.7504074 c 11.43924,0.6588661 20,5.6821266 20,17.0478896" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 864.96599,7.8197415 c 10.72409,4.1364695 17.25979,9.7959875 19.60711,16.9785555" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 848.53314,10.185391 V 40.646373"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 841.60592,33.602271 V 17.515065"></path>
|
||||
</g></g>
|
||||
<g transform="translate(110.84201,306.77478)"><g transform="translate(-659.83213)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 659.83213,-1.333323e-6 5,5.000001033323 c 0,0 4.49103,-4.79558403 7.5,-5.11281703 4.47693,-0.471998 6.30363,5.11281803 12.5,5.11281703 6.19637,-1e-6 8.02307,-5.58481403 12.5,-5.11281703 3.00897,0.317233 7.5,5.11281703 7.5,5.11281703 l 5,-5.000001033323" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 704.83213,4.9999995 V 44.999999" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 659.83213,50.000001 5,-5.000001 c 0,0 4.49103,4.795584 7.5,5.112817 4.47693,0.471998 6.30363,-5.112818 12.5,-5.112817 6.19637,10e-7 8.02307,5.584814 12.5,5.112817 3.00897,-0.317233 7.5,-5.112817 7.5,-5.112817 l 5,5.000001" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 704.83213,4.9999995 c 0,11.7291445 -12.49032,11.9447335 -20,13.9440995 -17.1817,4.574433 -20,9.177692 -20,26.0559" sodipodi:nodetypes="csc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="696.87048" cy="7.6883817" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 684.83213,18.944099 C 669.70946,25.630335 667.65062,34.578608 667.243,47.261515" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,18.944099 c -9.52802,6.736316 -14.22762,17.808835 -14.43982,30.512904" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 701.49255,43.009851 V 23.684932"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 701.49255,20.414266 V 17.024667" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="M 708.10713,29.283604 V 9.9586847"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 664.80706,4.9417592 c 4.23654,4.6444599 7.1266,3.676801 9.61027,-0.5386796" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,45.017665 c -5.50339,0.275105 -7.13991,-5.644712 -3.36705,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 684.83213,45.017665 c 5.88891,-0.02336 7.01944,-6.401058 3.36706,-9.670551" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
<g transform="translate(46.034012,306.77478)"><g transform="translate(-180)">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 180,0 5,4.9999995 c 0,0 4.51153,-4.68883452 7.5,-4.99999988146971 C 196.96351,-0.4647495 200.51236,4.9999996 205,4.9999995 c 4.48764,-1e-7 8.03649,-5.4647489 12.5,-4.99999988146971 C 220.48847,0.31116494 225,4.9999995 225,4.9999995 L 230,0" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 185,16.415384 c 0,0 4.51153,4.688835 7.5,5 4.46351,0.464749 8.01236,-5 12.5,-5 4.48764,1e-6 8.03649,5.464749 12.5,5 2.98847,-0.311165 7.5,-5 7.5,-5 l 5,5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 185,36.353587 c 0,0 4.51153,-4.688835 7.5,-5 4.46351,-0.464749 8.01236,5 12.5,5 4.48764,0 8.03649,-5.464749 12.5,-5 2.98847,0.311165 7.5,5 7.5,5 l 5,-5" sodipodi:nodetypes="cssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 180,50 5,-5 c 0,0 4.51153,4.688835 7.5,5 4.46351,0.464749 8.01236,-5 12.5,-5 4.48764,0 8.03649,5.464749 12.5,5 2.98847,-0.311165 7.5,-5 7.5,-5 l 5,5" sodipodi:nodetypes="ccssscc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 225,4.9999995 c 3.83777,3.8750224 3.02666,8.4473215 0,11.4153845" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 222.49008,18.70183 c 3.83777,3.875023 5.16123,10.492988 -0.18333,15.217039" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 225,36.353587 c 3.28037,3.234007 1.98398,6.662437 0,8.646413" sodipodi:nodetypes="cc"></path>
|
||||
<circle style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" cx="186.47734" cy="26.556124" r="3.75"></circle>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 190.22733,26.556123 h 17.98699"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 212.67905,26.556123 h 8.41916" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 205.19514,22.7441 h 6.71248" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 186.78681,3.3231001 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 199.58221,30.153165 h 7.35985" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 209.60505,3.3231001 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 186.78681,46.676899 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 209.60505,46.676899 h 13.50783" sodipodi:nodetypes="cc"></path>
|
||||
</g></g>
|
||||
</g>
|
||||
<g id="g595" transform="matrix(-1.6362345,0,0,-1.6333326,182.9509,832.18114)" style="stroke-width:0.611702">
|
||||
<path style="fill:none;stroke:#1a1612;stroke-width:0.795213;stroke-linejoin:round" d="m 68.149631,31.197703 c 0,9.770054 1.633339,10.77165 1.39284,12.394846 -0.335503,2.264404 -3.244757,3.910571 -2.984656,6.184869 0.300617,2.628565 5.257282,3.554334 4.974426,6.18487 -0.350874,3.263087 -8.510033,3.0148 -7.660616,6.184869 1.106015,4.127703 11.242201,3.101664 11.242201,6.184869 0,3.607307 -15.619695,2.566157 -15.619695,6.184869 0,3.614359 19.897703,2.621887 19.897703,6.184869 0,3.75446 -23.877243,1.246235 -23.877243,6.184869 0,4.604241 26.762408,2.202345 26.762408,6.18487 0,4.938662 -28.752178,1.240232 -28.752178,6.184869 0,4.022258 30.542973,2.476658 30.542973,6.184868 0,3.9277 -16.689788,1.9652 -16.813558,6.18487" id="path586" sodipodi:nodetypes="csssssssssssc"></path>
|
||||
<path sodipodi:type="star" style="fill:none;stroke:#1a1612;stroke-width:1.96726;stroke-linejoin:round" id="path587" inkscape:flatsided="false" sodipodi:sides="5" sodipodi:cx="62.703754" sodipodi:cy="14.808579" sodipodi:r1="8.7163172" sodipodi:r2="0" sodipodi:arg1="-1.5707963" sodipodi:arg2="-0.94247777" inkscape:rounded="0" inkscape:randomized="0" d="m 62.703755,6.0922623 -10e-7,8.7163167 8.289711,-2.693489 -8.289711,2.693489 5.123323,7.051649 -5.123323,-7.051649 -5.123322,7.051649 5.123322,-7.051649 -8.28971,-2.69349 8.28971,2.69349 z" inkscape:transform-center-y="-0.33614362" transform="matrix(0.40458695,0,0,0.40385681,43.088824,19.682788)"></path>
|
||||
<g id="g594" transform="matrix(0,0.54268778,-0.54411215,0,143.53866,83.286238)" style="stroke-width:1.1257">
|
||||
<g id="g589" transform="translate(21.79311,5.7426701)" style="stroke-width:1.1257">
|
||||
<path id="path588" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path589" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g591" transform="rotate(-120,58.638254,130.62948)" style="stroke-width:1.1257">
|
||||
<path id="path590" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path591" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
<g id="g593" transform="rotate(120,55.322722,143.67014)" style="stroke-width:1.1257">
|
||||
<path id="path592" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,132.83617 c -4.35007,0.21696 -7.0986,-3.34386 -9.66169,-4.38366 -2.19143,-0.88903 -5.73553,0.32027 -6.51253,4.47838 l 16.17422,-0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
<path id="path593" style="fill:none;stroke:#1a1612;stroke-width:1.46341;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 65.772127,135.72078 c -4.35007,-0.21696 -7.0986,3.34386 -9.66169,4.38366 -2.19143,0.88903 -5.73553,-0.32027 -6.51253,-4.47838 l 16.17422,0.0947" sodipodi:nodetypes="cscc"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 153 KiB |
|
|
@ -1,39 +0,0 @@
|
|||
---
|
||||
title: "New Artwork: Pixação Experiments"
|
||||
date: 2026-05-30T09:44:44+02:00
|
||||
summary: >
|
||||
I published a piece of algorithmic art fusing a Pixação graffiti-inspired lettering style with a layout similar to a
|
||||
Thai Haw-taew (five row) Yantra blessing tattoo.
|
||||
---
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="example-2026-05-30.svg" style="max-width: 20em" class="dark-mode-invert" alt="A drawing in the style
|
||||
of a sak yant five line thai buddhist blessing tattoo, with lettering not in khmer but in pichação
|
||||
graffiti-inspired letters reading out random hexadecimal characters. (they’re quite hard to read though). The
|
||||
graphic is kept in simple, constistent thickness, black lines on white background. The letters are ornate, with
|
||||
some parts looking leaf-like, and all have a square outer shape.">
|
||||
</figure>
|
||||
|
||||
I made a small piece of algorithmic art fusing a Pixação graffiti-inspired lettering style with a layout similar to a
|
||||
Thai Haw-taew (five row) Yantra blessing tattoo because it looks cool. Please have a look at `the live version here.
|
||||
</pixacao>`__
|
||||
|
||||
The artwork encodes the latest `NIST Randomness Beacon`_ at the time of viewing, so it's the same for everyone viewing
|
||||
it simultaneously but it changes unpredictably roughly every 1-3 minutes. The beacon's hexadecimal content is inserted
|
||||
into the artwork with a Pixação-inspired font that I created. The artwork is loosely based on a Thai buddhist Sak Yant
|
||||
tattoo, but deviates from it in some details because it's not intended to be a buddhist spiritual artifact.
|
||||
|
||||
Keeping with the spirit of the five row Sak Yant tattoo it is inspired by, this artwork also conveys a blessing.
|
||||
However, where a five row Sak Yant tattoo blesses its wearer, this artwork is meant to bless your computer when you
|
||||
print it out and place it near it. Its blessing provides protection from unforseen circumstances by encoding the most
|
||||
unforseen of things: 120 bit of the 512 bit of entropy in a NIST v2.0 interoperable randomness beacon.
|
||||
|
||||
If you enjoy it, please feel free to share it with your friends. If you print it and you want to share, I'd love to see
|
||||
a photo of it. You can reach me through my email or on mastodon.
|
||||
|
||||
`Here's the live version of the artwork. </pixacao>`__
|
||||
|
||||
.. _`NIST Randomness Beacon`: https://csrc.nist.gov/Projects/interoperable-randomness-beacons/beacon-20
|
||||
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
---
|
||||
title: "Private Contact Discovery"
|
||||
date: 2019-06-22T10:30:00+08:00
|
||||
summary: >
|
||||
I gave a short introduction into Private Contact Discovery protocols at our university workgroup.
|
||||
---
|
||||
|
||||
Private Contact Discovery (PCD) is the formal name for the problem modern smartphone messenger applications have on
|
||||
installation: Given a user's address book, find out which of their contacts also use the same messenger without the
|
||||
messenger's servers learning anything about the user's address book. The widespread non-private way to do this is to
|
||||
simply upload the user's address book to the app's operator's servers and do an SQL JOIN keyed on the phone number field
|
||||
against the database of registered users. People have tried sprinkling some hashes over these phone numbers in an
|
||||
attempt to improve privacy, but obviously running a brute-force preimage attack given a domain of maybe a few billion
|
||||
valid inputs is not cryptographically hard.
|
||||
|
||||
Private Contact Discovery can be phrased in terms of Private Set Intersection (PSI), the cryptographic problem of having
|
||||
two parties holding one set each find the intersection of their sets without disclosing any other information. PSI has
|
||||
been an active field of research for a while and already yielded useful results for some use cases. Alas, none of those
|
||||
results were truly practical yet for usage in PCD in a typical messenger application. They would require too much CPU
|
||||
time or too much data to be transferred.
|
||||
|
||||
At USENIX Security 2019, Researchers from technical universities Graz and Darmstadt published a paper titled *Private
|
||||
Contact Discovery at Scale*
|
||||
(`eprint <https://eprint.iacr.org/2019/517>`__ | `PDF <https://eprint.iacr.org/2019/517.pdf>`__).
|
||||
In this paper, they basically optimize the hell out of existing cryptographic solutions to private contact discovery,
|
||||
jumping from a still-impractical state of the art right to practicality. Their scheme allows a client with 1k contacts
|
||||
to run PCD against a server with 1B contacts in about 3s on a phone. The main disadvantage of their scheme is that it
|
||||
requires the client to in advance download a compressed database of all users, that clocks in at about 1GB for 1B users.
|
||||
|
||||
I found this paper very interesting for its immediate practical applicability. As an excuse to dig into the topic some
|
||||
more, I gave a short presentation at my university lab's research seminar on this paper
|
||||
(slides: `PDF <mori_semi_psi_talk.pdf>`__ | `ODP <mori_semi_psi_talk.odp>`__).
|
||||
|
||||
Even if you're not working on secure communication systems on a day-to-day basis this paper might interest you. If
|
||||
you're working with social account information of any kind I can highly recommend giving it a look. Not only might your
|
||||
users benefit from improved privacy, but your company might be able to avoid a bunch of data protection and
|
||||
accountability issues by simply not producing as much sensitive data in the first place.
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
---
|
||||
title: "How to talk to your microcontroller over serial"
|
||||
date: 2018-05-19T08:09:46+02:00
|
||||
---
|
||||
|
||||
Scroll to the end for the `TL;DR <Conclusion_>`_.
|
||||
|
||||
In this article I will give an overview on the protocols spoken on serial ports, highlighting common pitfalls. I will
|
||||
summarize some points on how to design a serial protocol that is simple to implement and works reliably even under error
|
||||
conditions.
|
||||
|
||||
If you have done low-level microcontroller firmware you will regularly have had to stuff some data up a serial port to
|
||||
another microcontroller or to a computer. In the age of USB, an old-school serial port is still the simplest and
|
||||
quickest way to get communication to a control computer up and running. Integrating a ten thousand-line USB stack into
|
||||
your firmware and writing the necessary low-level drivers on the host side might take days. Poking a few registers to
|
||||
set up your UART to talk to an external hardware USB to serial converter is a matter of minutes.
|
||||
|
||||
This simplicity is treacherous, though. Oftentimes, you start writing your serial protocol as needs arise. Things might
|
||||
start harmless with something like ``SET_LED ON\n``, but as the code grows it is easy to end up in a hot mess of command
|
||||
modes, protocol states that breaks under stress. The ways in which serial protocols break are manifold. The simplest one
|
||||
is that at some point a character is mangled, leading to both ends of the conversation ending up in misaligned protocol
|
||||
states. With a fragile protocol, you might end up in a state that is hard to recover from. In extreme cases, this leads
|
||||
to code such as `this gem`_ performing some sort of arcane ritual to get back to some known state, and all just because
|
||||
someone did not do their homework. Below we'll embark on a journey through the lands of protocol design, exploring the
|
||||
facets of this deceptively simple problem.
|
||||
|
||||
.. _`this gem`: https://github.com/juhasch/pyBusPirateLite/blob/dece35f6e421d4f6a007d1db98d148e2f2126ebb/pyBusPirateLite/base.py#L113
|
||||
|
||||
Text-based serial protocols
|
||||
===========================
|
||||
|
||||
The first serial protocol you've likely written is a human-readable, text-based one. Text-based protocols have the big
|
||||
advantage that you can just print them on a terminal and you can immediately see what's happening. In most cases you can
|
||||
even type out the protocol with your bare hands, meaning that you don't really need a debugging tool beyond a serial
|
||||
console.
|
||||
|
||||
However, text-based protocols also have a number of disadvantages. Depending on your application, these might not matter
|
||||
and in many cases a text-based protocol is the most sensible solution. But then, in some cases they might and it's good
|
||||
to know when you hit one of them.
|
||||
|
||||
Problems
|
||||
--------
|
||||
|
||||
Low information density
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Generally, you won't be able to stuff much more than four or five bit of information down a serial port using a
|
||||
single byte of a human-readable protocol. In many cases you will get much less. If you have 10 commands that are only
|
||||
issued a couple times a second nobody cares that you spend maybe ten bytes per command on nice, verbose strings such as
|
||||
``SET LED``. But if you're trying to squeeze a half-kilobyte framebuffer down the line you might start to notice the
|
||||
difference between hex and base-64 encoding, and a binary protocol might really be more up to the job.
|
||||
|
||||
Complex parsing code
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
On the computer side of thing, with the whole phalanx of an operating system, the standard library of your programming
|
||||
language of choice and for all intents and purposes unlimted CPU and memory resources to spare you can easily parse
|
||||
anything spoken on a serial port in real time, even at a blazing fast full Megabaud. The microcontroller side however is
|
||||
an entirely different beast. On a small microcontroller, printf_ alone will eat about half your flash. On most small
|
||||
microcontrollers, you just won't get a regex library even though it would make parsing textual commands *so much
|
||||
simpler*. Lacking these resources, you might end up hand-knitting a lot of low-level C code to do something seemingly
|
||||
simple such as parsing ``set_channel (13, 1.1333)\n``. These issues have to be taken into account in the protocol design
|
||||
from the beginning. For example, you don't really need matching parentheses, don't use them.
|
||||
|
||||
Fragile protocol state
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Say you have a ``SET_DISPLAY`` command. Now say your display can display four lines of text. The obvious approach to this
|
||||
is probably the SMTP_ or HTTP_ way of sending ``SET_DISPLAY\nThis is line 1\nThis is line 2\n\n``. This would certainly
|
||||
work, but it is very fragile. With this protocol, you're in trouble if at any point the terminating second newline
|
||||
character gets mangled (say, someone unplugs the cable, or the control computer reboots, or a cosmic ray hits something
|
||||
and ``0x10 '\n'`` turns into ``0x50 'P'``).
|
||||
|
||||
.. _SMTP: https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol
|
||||
.. _HTTP: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
|
||||
|
||||
Timeouts don't work
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You might try to solve the problem of your protocol state machine tangling up with a timeout. "If I don't get a valid
|
||||
command for more than 200ms I go back to default state." But consider the above example. Say, your control computer
|
||||
sends a ``SET_DISPLAY`` command every 100ms. If in one of them the state machine tangles up, the parser hangs since the
|
||||
timeout is never hit, because a new line of text is arriving every 100ms.
|
||||
|
||||
Framing is hard
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
You might also try to drop the second newline and using a convention such as ``SET_DISPLAY`` is followed by two lines of
|
||||
text, then commands resume.". This works as long as your display contents never look like commands. If you are only ever
|
||||
displaying the same three messages on a character LCD that might work, but if you're displaying binary framebuffer
|
||||
data you've lost.
|
||||
|
||||
Solutions
|
||||
---------
|
||||
|
||||
Keep the state machine simple
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In a text-based protocol, always use a single line of text to represent a single command. Don't do protocol states or
|
||||
modes where you can toggle between different interpretations for a line. If you have to send human-readable text as part
|
||||
of a command (such as ``SET_DISPLAY``), escape it so it doesn't contain any newlines.
|
||||
|
||||
This way, you keep your protocol state machine simple. If at any time your serial trips and flips a bit or looses a byte
|
||||
your protocol will recover on the next newline character, returning to its base state.
|
||||
|
||||
Encode numbers in hex when possible
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Printing a number in hexadecimal is a very tidy operation, even on the smalest 8-bit microcontrollers. In contrast,
|
||||
printing decimal requires both division and remainder in a loop which might get annoyingly code- and time-intensive on
|
||||
large numbers (say a 32-bit int) and small microcontrollers.
|
||||
|
||||
If you have to send fractional values, consider their precision. Instead of sending a 12 bit ADC result as a 32-bit
|
||||
float formatted like ``0.176513671875`` sending ``0x2d3`` and dividing by 4096 on the host might be more sensible. If you
|
||||
really have to communicate big floats and you can't take the overhead of including both printf_ and scanf_ you can
|
||||
use hexadecimal floating point, which is basically ``hex((int)foo) + "." + hex((int)(65536*(foo - (int)foo)))`` for four
|
||||
digits. You can also just hex-encode the binary IEEE-754_ representation of the float, sending ``hex(*(int *)&float)``.
|
||||
Most programming languages will have a `simple, built-in means to parse this sort of thing
|
||||
<https://docs.python.org/3.5/library/struct.html>`__.
|
||||
|
||||
.. _printf: http://git.musl-libc.org/cgit/musl/tree/src/stdio/vfprintf.c
|
||||
.. _scanf: http://git.musl-libc.org/cgit/musl/tree/src/stdio/vfscanf.c
|
||||
.. _IEEE-754: https://en.wikipedia.org/wiki/IEEE_754
|
||||
|
||||
Escape multiline strings
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you have to send arbitrary strings, escape special characters. This not only has the advantage of yielding a robust
|
||||
protocol: It also ensures you can actually see everything that's going on when debugging. The string ``"\r\n"`` is easy to
|
||||
distinguish from ``"\n"`` while your terminal emulator might not care.
|
||||
|
||||
The simplest encoding to use is the C-style backslash encoding. Host-side, most languages will have a `built-in means of
|
||||
escaping a string like that <https://docs.python.org/3.5/library/codecs.html#text-encodings>`__.
|
||||
|
||||
Encoding binary data
|
||||
--------------------
|
||||
|
||||
For binary data, hex and base-64 are the most common encodings. Since hex is simpler to implement I'd go with it unless
|
||||
I really need the 30% bandwidth improvement base-64 brings.
|
||||
|
||||
Binary serial protocols
|
||||
=======================
|
||||
|
||||
In contrast to anything human-readable, binary protocols are generally more bandwidth-efficient and are easier to format
|
||||
and parse. However, binary protocols come with their own version of the caveats we discussed for text-based protocols.
|
||||
|
||||
The framing problem in binary protocols
|
||||
---------------------------------------
|
||||
|
||||
The most basic problems with binary protocols as with text-based ones is framing, i.e. splitting up the continuous
|
||||
serial data stream into discrete packets. The issue is that it is that you have to somehow mark boundaries between
|
||||
frames. The simplest way would be to use some special character to delimit frames, but then any 8-bit character you
|
||||
could choose could also occur within a frame.
|
||||
|
||||
SLIP/PPP-like special character framing
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Some protocols solve this problem much like we have solved it above for strings in line-based protocols, by escaping any
|
||||
occurence of the special delimiter character within frames. That is, if you want to use ``0x00`` as a delimiter, you would
|
||||
encode a packet containing ``0xde 0xad 0x00 0xbe 0xef`` as something like ``0xde 0xad 0x01 0x02 0xbe 0xef``, replacing the
|
||||
null byte with a magic sequence. This framing works, but is has one critical disadvantage: The length of the resulting
|
||||
escaped data is dependent on the raw data, and in the worst case twice as long. In a raw packet consisting entirely of
|
||||
null bytes, every byte must be escaped with two escape bytes. This means that in this case the packet length doubles,
|
||||
and in this particular case we're even less efficient than base-64.
|
||||
|
||||
Highly variable packet length is also bad since it makes it very hard to make any timing guarantees for our protocol.
|
||||
|
||||
9-bit framing
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
A framing mode sometimes used is to configure the UARTs to transmit 9-bit characters and to use the 9th bit to designate
|
||||
control characters. This works really well, and gives plenty of control characters to work with. The main problem with
|
||||
this is that a 9-bit serial interface is highly nonstandard and you need UARTs on both ends that actually support this
|
||||
mode. Another issue is that though more efficient than both delmitier-based and purely text-based protocols, it still
|
||||
incurs an extra about 10% of bandwidth overhead. This is not a lot if all you're sending is a little command every now
|
||||
and then, but if you're trying to push large amounts of data through your serial it's still bad.
|
||||
|
||||
COBS
|
||||
~~~~
|
||||
|
||||
Given the limitations of the two above-mentioned framing formats, we really want something better. The `Serial Line
|
||||
Internet Protocol (SLIP)`_ as well as the `Point to Point Protocol (PPP)`_, standardized in 1988 and 1994 respectively,
|
||||
both use escape sequences. This might come as a surprise, but humanity has actually still made significant technological
|
||||
progress on protocols for 8-bit serial interfaces until the turn of the millennium. In 1999, `Consistent Overhead Byte
|
||||
Stuffing (COBS)`_ (`wiki <https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing>`__) was published by a few
|
||||
researchers from Apple Computer and Stanford University. As a reaction on the bandwidth doubling problem present in
|
||||
PPP_, COBS *always* has an overhead of a single byte, no matter what or how long a packet's content is.
|
||||
|
||||
COBS uses the null byte as a delimiter interleaves all the raw packet data and a `run-length encoding`_ of the non-zero
|
||||
portions of the raw packet. That is, it prepends the number of bytes until the first zero byte to the packet, plus one.
|
||||
Then it takes all the leading non-zero bytes of the packet, unmodified. Then, it again encodes the distance from the
|
||||
first zero to the second zero, plus one. And then it takes the second non-zero run of bytes unmodified. And so on. At
|
||||
the end, the packet is terminated with a zero byte.
|
||||
|
||||
The result of this scheme is that the encoded packet does not contain any zero bytes, as every zero byte has been
|
||||
replaced with the number of bytes until the next zero byte, plus one, and that can't be zero. Both formatter and parser
|
||||
each have to keep a counter running to keep track of the distances between zero bytes. The first byte of the packet
|
||||
initializes that counter and is dropped by the parser. After that, every encoded byte received results in one raw byte
|
||||
parsed.
|
||||
|
||||
While this might sound more complicated than the escaping explained above, the gains in predictability and efficiency
|
||||
are worth it. An implementation of encoder and decoder should each be about ten lines of C or two lines of Python. A
|
||||
minor asymmetry of the protocol is that while decoding can be done in-place, encoding either needs two passes or you
|
||||
need to scan forward for the next null byte.
|
||||
|
||||
.. _`Point to Point Protocol (PPP)`: https://en.wikipedia.org/wiki/Point-to-Point_Protocol
|
||||
.. _PPP: https://en.wikipedia.org/wiki/Point-to-Point_Protocol
|
||||
.. _`Serial Line Internet Protocol (SLIP)`: https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol
|
||||
.. _`Consistent Overhead Byte Stuffing (COBS)`: http://www.stuartcheshire.org/papers/COBSforToN.pdf
|
||||
.. _`Point-to-Point Protocol (PPP)`: https://en.wikipedia.org/wiki/Point-to-Point_Protocol
|
||||
.. _`run-length encoding`: https://en.wikipedia.org/wiki/Run-length_encoding
|
||||
|
||||
State machines and error recovery
|
||||
---------------------------------
|
||||
|
||||
In binary protocols even more than in textual ones it is tempting to build complex state machines triggering actions on
|
||||
a sequence of protocol packets. Please resist that temptation. As with textual protocols keeping the protocol state to
|
||||
the minimum possible allows for a self-synchronizing protocol. A serial protocol should be designed such that if due to
|
||||
a dropped packet or two both ends will naturally re-synchronize within another packet or two. A simple way of doing that
|
||||
is to always transmit one semantic command per packet and to design these commands in the most idempotent_ way possible.
|
||||
For example, when filling a framebuffer piece by piece, include the offset in each piece instead of keeping track of it
|
||||
on the receiving side.
|
||||
|
||||
.. _idempotent: https://en.wikipedia.org/wiki/Idempotence#Computer_science_meaning
|
||||
|
||||
Conclusion
|
||||
==========
|
||||
|
||||
Here's your five-step guide to serial bliss:
|
||||
|
||||
1. Unless you have super-special requirements, always use the slowest you can get away with from 9600Bd, 115200Bd or
|
||||
1MBd. 8N1 framing if you're talking to anything but another microcontroller on the same board. Using common values
|
||||
like these makes it easier when you'll inevitably have to guess these at some point in the future ;)
|
||||
2. If you're doing something simple and speed is not a particular concern, use a human-readable text-based protocol. Use
|
||||
one command/reply per line, begin each line with some sort of command word and format numbers in hexadecimal. Bonus
|
||||
points for the device replying to unknown commands with a human-readable status message and printing a brief protocol
|
||||
overview on boot.
|
||||
3. If you're doing something even slightly nontrivial or need moderate throughput (>1k commands per second or >20 byte of
|
||||
data per command) use a COBS-based protocol. A good starting point is a ``[target MAC][command ID][command
|
||||
arguments]`` packet format for multidrop busses. For single-drop you may decide to drop the MAC address.
|
||||
4. Always include some sort of "status" command that prints life stats such as VCC, temperature, serial framing errors
|
||||
and uptime. You'll need some sort of ping command anyway and that command might as well do something useful.
|
||||
5. If at all possible, keep your protocol context-free across packets/lines. That is, a certain command should always be
|
||||
self-contained, and no command should change the meaning of the next packet/line/command that is sent. This is really
|
||||
important to allow for self-synchronization. If you really need to break up something into multiple commands, say you
|
||||
want to set a large framebuffer in pieces, do it in a idempotent_ way: Instead of sending something like ``FRAMEBUFFER
|
||||
INCOMING:\n[byte 0-16]\n[byte 17-32]\n[...]\nEND OF FRAME`` rather send ``FRAMEBUFFER DATA FOR OFFSET 0: [byte
|
||||
0-16]\nFRAMEBUFFER DATA FOR OFFSET 17: [byte 17-32]\n[...]\nSWAP BUFFERS\n``.
|
||||
|
||||
|
Before Width: | Height: | Size: 574 KiB |
|
|
@ -1,246 +0,0 @@
|
|||
---
|
||||
title: "Theia Attack Resistance and Digital Identity"
|
||||
date: 2020-09-09T15:00:00+02:00
|
||||
draft: true
|
||||
---
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure class="header">
|
||||
<img src="images/succulents.jpg">
|
||||
<figcaption>Photo by <a href="https://unsplash.com/@timbennettcreative">Tim Bennett</a> on
|
||||
<a href="https://unsplash.com/">Unsplash</a></figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Theia in Cyberspace
|
||||
===================
|
||||
|
||||
In informatics, the term *distributed system* is used to describe the aggregate behavior of a complex network made up of
|
||||
individual computers. For decades, computer scientists to some success have been trying to figure out how exactly the
|
||||
individual computers that make up such a distributed system need to be programmed for the resulting amalgamation to
|
||||
behave in a predictable, maybe even a desirable way. Though seemingly simple on its surface, this problem has a
|
||||
surprising depth to it that has yielded research questions for a whole field for several decades now. One particular
|
||||
as-of-yet unsolved problem is resistance against *theia attacks* (or "sybil" attacks in older terminology).
|
||||
|
||||
Named after the 1973 book by Flora Rheta Schreiber on dissociative identity disorder, a sybil attack is an
|
||||
attack where one computer in a distributed system pretends to be multiple computers to gain an advantage. From your
|
||||
author's standpoint, naming a type of computer security attack after a medical condition was an unfortunate choice.
|
||||
For this reason this post uses the term *Theia attack* to refer to the same concept. Theia is a greek godess of light
|
||||
and glitter and the name alludes to the attacker performing something alike an optical illusion, causing the attacked
|
||||
to perceive multiple distinct images that in the end are all only reflections of the same attacker.
|
||||
|
||||
The core insight of computer science research on theia attacks is that there cannot be any technological way of
|
||||
preventing such an attack, and any practical countermeasure must be grounded in some authority or ground truth that is
|
||||
external to the systems—bridging from technology to its social or political context.
|
||||
|
||||
Looking around, we can see a parallel between this question ("which computer is a real computer?") and a social issue
|
||||
that recently has been growing in importance: Just like computers can pretend to be other computers, they can also
|
||||
pretend to be humans. As can humans. Be it within the context of election manipulation or down-to-earth astroturfing_
|
||||
the recurring issue is that in today's online communities, it is hard for an individual to tell who of their online
|
||||
acquaintances are who they seem to be. Different platforms attempt different solutions to this problem, and all fail in
|
||||
some way or another. Facebook employs good old snitching, turning people against each other and asking them "Do you know
|
||||
this person?". Twitter is more laid-back and avoids this Stasi_ methodology in favor of requiring a working mobile phone
|
||||
number from its subjects, essentially short-circuiting identity verification to the phone company's check of their
|
||||
subscriber's national passport.
|
||||
|
||||
.. the preceding is a simplified representation of these platform's practices. In particular facebook uses several
|
||||
methods depending on the case. I think this abbreviated discussion should be ok for the sake of the argument. I am
|
||||
not 100% certain on the accuracy on the accuracy of the statement though. Does fb still do the snitching thing? Is
|
||||
twitter usually content with a phone number?
|
||||
|
||||
Trusting Crypto-Anarchist Authorities
|
||||
=====================================
|
||||
|
||||
Beyond these centralistic solutions to the problem, crypto-anarchists and anarcho-capitalists have been brewing on some
|
||||
interesting novel approaches to online identity based on *blockchain* distributed ledger technology. Distributed
|
||||
ledgers are a distributed systems design pattern that yields a system that works like an append-only logbook.
|
||||
Participants can create new entries in this logbook, but no one—neither the original author, nor other participants—can
|
||||
retroactively change a logbook entry once it has been written. In the blockchain model, past entries are essentially
|
||||
written into stone. This near-perfect immutability is what opens them for a number of use cases from cryptographic
|
||||
pseudo-currencies [#cryptocurrency]_.
|
||||
|
||||
An overview over a variety of these unconventional blockchain identity verification approaches can be found in `this
|
||||
unpublished 2020 survey by Siddarth, Ivliev, Siri and Berman <https://arxiv.org/ftp/arxiv/papers/2008/2008.05300.pdf>`_.
|
||||
They walk their readers through a number of different projects that try to solve the question "Is this human who they
|
||||
pretend to be?" using joint socio-technological approaches. In the following few sections, you may find a short outline
|
||||
of a small selection of them. The conlusion of this post will be a commentary on these approaches, and on the underlying
|
||||
problem of identity in a digital world.
|
||||
|
||||
.. BrightID
|
||||
|
||||
In one scheme, identity is determined by "notary" computers that aggregate large amounts of information on a user's
|
||||
social contacts. These computers then run an algorithm derived from the SybilGuard_, SybilLimit_ and SybilInfer_ lineage
|
||||
of random-walk based algorithms. These algorithms assume that authentic social graphs are small world graphs: Everyone
|
||||
knows everyone else through a friend's friend's friend. They also assume that there is an upper bound on how many
|
||||
connections with authentic users an attacker can forge: Anyone who is not embedded into the graph well enough is cut
|
||||
out. Like this, they put an upper limit on the number of theia identites an attacker can assume given a certian number
|
||||
of connections to real people.
|
||||
|
||||
Disregarding the catastrophic privacy issues of storing large amounts of data on social relationships on someone else's
|
||||
computer, this second assumption is where this model unfortunately breaks down. Applying common sense, it is completely
|
||||
realistic for an attacker to forge a large number of social connections: This is precisely what most of social media
|
||||
marketing is about! A more malicious angle on this would be to consider how in meatspace [#meatspacefn]_ multi-level
|
||||
marketing schemes are successful in coaxing people to abuse their social graphs to disastrous consequences to the
|
||||
well-being of themselves and others. Similar schemes would certainly be possible in cyberspace as well. An additional
|
||||
point to consider is that the upper limit SybilGuard_ and others place on the number of fake identities one can have is
|
||||
simply not that strict at all. An attacker could still get away with a reasonable number of false identities before
|
||||
getting caught by any such algorithm.
|
||||
|
||||
.. Duniter
|
||||
|
||||
In another scheme, identity is awarded to anyone who can convince several people already in the network to vouch for
|
||||
them, and who is at most a few degrees removed from one of several pre-determined celebrities. Apart from again being
|
||||
vulnerable to conmen and other scammers, this system has the glaring flaw of roundly refusing to recognize any person
|
||||
who is not willing or able to engage with multiple of its members. Along with the system's informal requirement for
|
||||
members to only vouch for people they have physically met this leads to a nonstarter in a cyberspace that grown
|
||||
specifically *because* it transcends national borders and physical distance—two most serious obstacles to in-person
|
||||
communication.
|
||||
|
||||
.. Idena Network
|
||||
|
||||
The last scheme I will outline in this post is based around a set of `Turing tests`_; that is, quizzes that are designed
|
||||
to tell apart man and machine. In this system, all participants have to simultaneously undergo a Turing test once in a
|
||||
fortnight. The idea is that this limits the number of theia identities an attacker can assume since they can only solve
|
||||
that many Turing tests at the same time. The system uses a particular type of picture classification-based Turing test
|
||||
and does not seem to be designed with the blind or mentally disabled in mind with accessibility concerns nowhere to be
|
||||
found in the so-called "manifesto" published by its creators. But even ignoring that, the system obviously fails at an
|
||||
even more basic level: The idea that everyone takes a Turing test at the same time only works in a world without time
|
||||
zones. Or jobs for that matter. Also, it assumes that an attacker cannot simply hire a small army of people someplace
|
||||
else to fool the system.
|
||||
|
||||
.. _SybilLimit: https://www.comp.nus.edu.sg/~yuhf/yuh-sybillimit.pdf
|
||||
.. _SybilGuard: http://www.math.cmu.edu/~adf/research/SybilGuard.pdf
|
||||
.. _SybilInfer: https://www.princeton.edu/~pmittal/publications/sybilinfer-ndss09.pdf
|
||||
.. _`Turing Tests`: https://en.wikipedia.org/wiki/Turing_test
|
||||
|
||||
Identity between Cyberspace and Meatspace
|
||||
=========================================
|
||||
|
||||
A common thread in these solutions, from the Facebook'esque Stasi_ methods to the crypto-anarchist challenge-response
|
||||
utopias, is that they all approach digital identity as a question of Objective Truth™ that can unanimously be decided at
|
||||
a system level—or that can be externalized to the next larger system such as the state. Alas, the important question
|
||||
remains unasked:
|
||||
|
||||
What *is* identity?
|
||||
|
||||
The answer to this question certainly depends on the system being examined. For example, an important reason the
|
||||
capitalist corporations mentioned above require knowledge about their users' identity is to generate plausible
|
||||
statistics for the advertisers that form their customer base, similar to how a farmer will keep statics on yield and
|
||||
quality for the buyers of his crop. With this background, a full decoupling of platform accounts from a notion of legal
|
||||
identity seems at odds with the platform's business model—and we will have to adjust our expectations for reform
|
||||
accordingly.
|
||||
|
||||
A common thread among all systems mentioned above is that they all have a social component to them. For this common use
|
||||
case of social systems, I want to make a suggestion on how we can approach digital identity in a more practical, less
|
||||
discriminatory [#discriminatory]_ manner than any of the methods we discussed above. I think both using people's social
|
||||
connections and proxying the decisions of external authorities such as the state are bad systems to decide who is a
|
||||
person and who is not. I will now illustrate this point a bit. Let us think about how many digital identities a human
|
||||
beign might have. First, consider the case of n=0, someone who simply wants no business with the system at all. For
|
||||
simplicity, let us assume that we have solved this issue of consent, i.e. every person who is identified by the system
|
||||
consents to this practice. For n=1, the approaches outlined above all provide some approximate solution. States may not
|
||||
grant every human sufficient ID (e.g. children, the mentally disabled or prisoners might be left out), and the social
|
||||
systems might fail to catch people who simply do not have any friends, but otherwise their approximations hold. Maybe.
|
||||
But what about n=2, n=3, ...? None of these systems adequately consider cases where a human being might legitimately
|
||||
wish to hold multiple digital identities, non-maliciously.
|
||||
|
||||
Consider a hypothetical lesbian, conservative politician. An active social media presence is a core component of a
|
||||
modern politician's carreer. At the same time, "conservative homophobe" is still well within the realm of tautology and
|
||||
it would be legitimate for this politician to wish to not disclose a large fraction of their private life to the world
|
||||
at large. They might have a separate online identity for matters related to it. For this politician, the social
|
||||
relationship-based systems referenced above would either incorporate outing as a design feature, or they would force
|
||||
the politician to choose either of their two identities: To choose between private life and carreer. When deferring to
|
||||
the state as the decider over personhood, at least the platform's operator would know about the outrageously sensitive
|
||||
link between the politician's online identities. Clearly, no such solution can be considered socially just.
|
||||
|
||||
Let us try not to be caught up on saving the world at this point. The issue of conservative homophobia is out of the
|
||||
scope of our consideration, and it is not one that anyone can solve in the near future. Magical realism aside, least of
|
||||
all can some technological thing beckon this change. There is a case for legitimate uses of multiple, separate digital
|
||||
identities, and we do not have a technical or political answer to it. All hope is not lost yet, though. We can easily
|
||||
undo this gordian knot by acknowledging an unspoken assumption that underlies any social relationships between real
|
||||
people, past the procrustean bed of computer systems or organizational structures these relationships are cast into.
|
||||
|
||||
As a function of social interaction, digital identities conform to roles_ in sociological terminology, and are not
|
||||
at all the same as personhood_. Roles are subjective and arise from a relationship between people, and a single
|
||||
person might legitimately perform different roles depending on context.
|
||||
|
||||
When computer scientists or programmers are creating new systems, there always is an (often implicit) modelling stage.
|
||||
Formally, during this stage a domain expert and a modeller with a computer science background come together, each
|
||||
contributing their knowledge to form a model that is both appropriate for real-world use and practical from an
|
||||
engineering point of view. In practice, these two roles are often necessarily fulfilled by the same person, who is often
|
||||
also the programmer of the thing. This leads to many computer systems using poor models. A typical example of this issue
|
||||
are systems requiring a person's name that use three input fields labelled "First Name", "Middle Initial" and "Last
|
||||
Name". These systems are often created by US-American programmers, who are used to this naming schema from their lived
|
||||
experience. Unfortunately, this schema breaks down for those few billion people who use their last name first, who have
|
||||
more than one middle name, or who have multiple given names and do not normally use the first one of those.
|
||||
|
||||
Once a system creator's implicit assumptions have been encoded into the system like this, it is often very hard to get
|
||||
out of that situation. A pattern to use during careful modelling is to keep the model flexible to account for unforeseen
|
||||
corner cases. For example, when modelling a system requiring a person's name, one would have to ask what the name is
|
||||
used for. It may be the most sensible decision to simply ask the user for their name twice: Once in first name/last name
|
||||
format for e.g. tax purposes, and once with a free-form text field for e.g. displaying on their account page.
|
||||
|
||||
While for names, many systems already use some form of flexible model by e.g. having a *handle* or *nickname* separate
|
||||
from the *display name*, "social" systems still often are stuck with an identity model based around a concept of a
|
||||
single, rigid identity. In practice, people perform different roles_ in different circumstances. When asking for a
|
||||
person's identity, one would get wildly different answers from different people. A person's identity as perceived by
|
||||
others is coupled to their relationship more than to some underlying, biological or administrative truth. Thinking back
|
||||
to the straw man politician above, this is evident in subtle ways in almost all our everyday relationships: Some people
|
||||
may know me by my legal name, some by my online nickname. To some I may be a computer scientist, to some a flatmate.
|
||||
None of my friends and acquaintances have ever wanted to see my passport, or asked to take my DNA to ascertain that I am
|
||||
a distinct human being from the other humans they know. Likewise, identifying me by my social connections is impractical
|
||||
as it would require an exceedingly weird amount of what can only be described as snooping. Yet, this concept of a
|
||||
single, consistent, global, true identity is exactly what up to now all technological solutions to the identity problem
|
||||
are trying to achieve.
|
||||
|
||||
Building Bridges
|
||||
================
|
||||
|
||||
I think I can offer you one main take-aways from the discussion above.
|
||||
|
||||
During modelling social systems, focus on relationships—not identity.
|
||||
|
||||
Rephrased into more actionable points, as someone designing a social digital system, do the following:
|
||||
|
||||
0. Early in the design stages, take the time to consider fundamental modelling issues like this one. If you don't, you
|
||||
will likely get stuck with a sub-optimal model that will be hard to get rid of.
|
||||
1. Where possible, be flexible. Allow people to chose their own identifier. Don't require them to use their real names,
|
||||
they may not wish to disclose those or they may not be in a format that is useful to you (they may be too long, too
|
||||
short, too ubiquituous, in foreign characters etc.). A free-form text field with a reasonable length limit is a good
|
||||
approach here.
|
||||
2. Do not use credit cards or phone numbers to identify people. There are many people who do not have either, and
|
||||
scammers can simply buy this data in bulk on the darknet.
|
||||
3. Allow people to create multiple identites [#accountswitchopsec]_, and acknowledge the role of social relationships in
|
||||
your interaction features. People have very legitimate reasons to separate areas of their lifes, and it is not for
|
||||
you or your computer to decide who is who to whom. If your thing requires a global search function, re-consider the
|
||||
data protection aspects of your system. If you want to encourage social functions in the face of bots and trolls,
|
||||
make it easy for people to share their identities out-of-band, such as through a QR code or a copy-and-pasteable
|
||||
short link. If you require someone's legal name or address for billing purposes, unify these identities behind the
|
||||
scenes if at all and allow them to act as if fully independent in public.
|
||||
|
||||
While change of perspective comes with its share of user experience challenges, but also with a promise for a more
|
||||
human, more dignified online experience. Perhaps we can find a way to adapt cyberspace to humans, instead of continuing
|
||||
trying it the other way around.
|
||||
|
||||
.. _astroturfing: https://en.wikipedia.org/wiki/Astroturfing
|
||||
.. _Stasi: https://en.wikipedia.org/wiki/Stasi
|
||||
|
||||
.. [#cryptocurrency] Pseudo-currencies in that, while they provide some aspects of a regular currency such as ownership
|
||||
and transactions, they lack most others. Traditional currencies are backed by states, regulated by central banks
|
||||
tasked with maintaining their stability and ultimately provide accountability through law enforcement, courts
|
||||
and political elections.
|
||||
|
||||
.. [#discriminatory] Discriminatory as in discriminating against minorities, but also as in deciding what is and what is
|
||||
not.
|
||||
|
||||
.. [#accountswitchopsec] This does mean that you should not actively prevent people from creating multiple accounts. It
|
||||
does not necessarily entail building a proper user interface around this practice. If you do the latter, e.g. by
|
||||
offering a "switch identity" button or an identiy drop-down menu on a post submission form, you can easily
|
||||
encourage slip-ups that might disclose the connection between two identities, and you make it possible for
|
||||
someone hacking a single login to learn about this connection as well.
|
||||
|
||||
.. [#meatspacefn] Meatspace_ is where people physically are, as opposed to cyberspace
|
||||
|
||||
.. _Meatspace: https://dictionary.cambridge.org/dictionary/english/meatspace
|
||||
.. _roles: https://en.wikipedia.org/wiki/Role
|
||||
.. _personhood: https://en.wikipedia.org/wiki/Personhood
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
---
|
||||
title: "Identity between Cyberspace and Meatspace"
|
||||
date: 2020-09-09T15:00:00+02:00
|
||||
draft: true
|
||||
---
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure class="header" data-pagefind-ignore>
|
||||
<img src="images/succulents.jpg">
|
||||
<figcaption>Photo by <a href="https://unsplash.com/@timbennettcreative">Tim Bennett</a> on
|
||||
<a href="https://unsplash.com/">Unsplash</a></figcaption>
|
||||
</figure>
|
||||
|
||||
Identity in Cyberspace
|
||||
======================
|
||||
|
||||
.. Identity is a frequent problem
|
||||
.. Easy solutions abound
|
||||
.. Precise modelling is uncommon
|
||||
.. True identity is sensitive, hard to handle
|
||||
..
|
||||
.. Often, conversational features emphasized -> true identity is unnecessary
|
||||
.. Social role theory
|
||||
.. Call to action
|
||||
|
||||
Most computer systems that interface with humans have a concept of user identity. The data structures used for its
|
||||
storage vary, but usually one *account* corresponds to one human *user*. In many applications, the system operator tries
|
||||
to ensure that one user cannot create multiple accounts. In online social networks, astrotufing_ and trolling are easier
|
||||
to fight when limits are imposed on account creation. In online stores, fraud prevention means the store operator needs
|
||||
their customers legal identity and the operator must be able to ban offending customers. In mobile messaging systems,
|
||||
users have to be able to find each other by some identifier such as name or phone number, and this identifier has to be
|
||||
unique and hard to forge.
|
||||
|
||||
Today, in systems that allow anyone to create an account have largely converged to require either an email address or a
|
||||
mobile phone number. Email addresses are used by systems that are less vulnerable to abuse and that are used on laptop
|
||||
or desktop computers. Mobile phone numbers are abundantly used in smartphone apps, as well as in systems more prone to
|
||||
abuse such as online social networks or ecommerce. Both are easily verified using a confirmation email or SMS.
|
||||
|
||||
When designing or programming an online system, it is uncommon that the precise real-world semantics of accounts are
|
||||
modelled. Most computer systems use ad-hoc data models. During their creation, their programmers implicit assumptions
|
||||
about the world are encoded into these data models. Most of the time this works fine, but it does lead to significant
|
||||
blind spots that can make systems break down for a fraction of their users.
|
||||
|
||||
Lives in Meatspace
|
||||
==================
|
||||
|
||||
A consequence of the proliferation of phone numbers being used to identify people is that most people will not be able
|
||||
to create multiple accounts. *"That's the point!"* you might say, but while we want to prevent scammers, spammers and
|
||||
boored schoolchildren from messing with our systems, everybody else may have legitimate reasons to have more than one
|
||||
account.
|
||||
|
||||
We can apply sociology's model of roles_ to understand this issue. In sociology, a role is the comprehensive pattern of
|
||||
rules and expectations that govern an individual's behavior corresponding to their social position. A key fact is that
|
||||
most people occupy mutliple roles. A parent may also be a company employee or a wife and perform accordingly given the
|
||||
circumstances. Systems that tie digital identity to legal personhood through the contracts behind phone numbers impede
|
||||
their users' attempts at role separation. Effects of this are e.g. that nowadays employers routinely screen applicants'
|
||||
social media accounts for unacceptable content.
|
||||
|
||||
While this role conflict merely amounts to a minor inconvenience to most there are many to who it poses an existential
|
||||
problem. Consider an LGBT+ person living in a repressive country or a politically conservative person living in a
|
||||
very liberal city. Both have legitimate reasons to strictly separate parts of their private lives from others. For both,
|
||||
much is at stake. Yet, both will have to practically circumvent most online systems registration barriers to implement
|
||||
this separation.
|
||||
|
||||
Trusting the User
|
||||
=================
|
||||
|
||||
While there is no single solution to these issues, there are several possible mitigations. The first and most important
|
||||
one is to systematically think about the system's data model when creating it. Which assumptions about the real world
|
||||
are inherent in it? Are these assumptions likely to cause issues? Ad-hoc models are easily created, but hard to get rid
|
||||
of when they start causing problems.
|
||||
|
||||
A general guideline on identity should be that hindering trolls by requiring things like phone numbers or credit card
|
||||
numbers is very likely to also be an obstacle to many entirely legitimate uses. Captchas_ or invitation links can help
|
||||
to keep out the trolls. Another approach is to limit the damage a troll can cause with things like effective moderation
|
||||
systems, reputation systems or by limiting the reach of newly created accounts.
|
||||
|
||||
Outside of e-commerce, actually tying a digital account to a real-world identity is very rarely necessary. The value of
|
||||
a messenger app is not in the names in its contacts list, but the conversations behind these names. When two people meet
|
||||
each other on the street, their interaction is shaped by a myriad of social factors—but *not* by them showing each other
|
||||
their photo ID.
|
||||
|
||||
Humans with their messy identities do not fit today's cyberspace well. Let's adapt cyberspace to humans, instead of
|
||||
trying it the other way around.
|
||||
|
||||
.. _astroturfing: https://en.wikipedia.org/wiki/Astroturfing
|
||||
.. _roles: https://en.wikipedia.org/wiki/Role
|
||||
.. _Captchas: https://link.springer.com/content/pdf/10.1007/3-540-39200-9_18.pdf
|
||||
|
||||
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
|
@ -1,219 +0,0 @@
|
|||
---
|
||||
title: "Ubiquiti EdgeRouter on Deutsche Telekom GPON Fiber"
|
||||
date: 2022-02-21T20:00:00+01:00
|
||||
summary: >
|
||||
Short tutorial on getting a Deutsche Telekom GPON internet connection running using a SFP ONU unit in an Ubiquiti
|
||||
EdgeRouter.
|
||||
---
|
||||
|
||||
Disclaimer
|
||||
==========
|
||||
|
||||
I provide this guide as a reference for other knowledgeable users without any warranty. Please feel free to use this as
|
||||
a resource but do not hold me responsible if this does not work for you. There is a significant chance that due to an
|
||||
error on my side or due to Telekom changing their setup this guide will not work for you, and you may end up having to
|
||||
pay for an unsuccessful Telekom technician visit. That is your own risk, and I do not assume any liability.
|
||||
|
||||
Tl;dr
|
||||
=====
|
||||
|
||||
The "Telekom Digitalisierungsbox Glasfasermodem" is a GPON ONT in SFP form factor that works with an Ubiquiti EdgeRouter
|
||||
6P's SFP port. You can order it from Telekom or other vendors using the Telekom P/N 40823569 or its EAN 4718937619382.
|
||||
It costs about the same as the separate plastic box modem, but saves a lot of space and does not require a separate
|
||||
power supply.
|
||||
|
||||
To configure, first access the SFP ONT's web interface at ``10.10.1.1`` by configuring your SPF port's IP to static
|
||||
``10.10.1.2``. User credentials are either admin/admin or admin/1234. In the web interface, set put PLOAM password into the
|
||||
"SLID" setting in ASCII mode, then save & reboot the device. Now, configure PPPoE on the router's SFP port using the
|
||||
PPPoE UID ``[anschlusskennung] [zugangsnummer] "#" [mitbenutzernummer] "@t-online.de"`` and your "Persönliches Kennwort" as
|
||||
PPPoE password. Set the VLAN to ``7``, and you are good to go.
|
||||
|
||||
Background
|
||||
==========
|
||||
|
||||
I moved into a new apartment that has a fiber internet connection operated by Deutsche Telekom. Having made some poor
|
||||
experiences with AVM's FritzBox brand of routers that is commonly used by German carriers, I decided to use my own
|
||||
Router instead of the one provided by Deutsche Telekom. Like other German providers, Telekom charges exorbitant amounts
|
||||
in monthly fees for their routers, so even though my choice ended up being a high-end piece of commercial equipment I
|
||||
will still be cheaper than going with Telekom's much shittier device when added up over a two-year contract period.
|
||||
|
||||
The hardware I chose is the Ubiquiti EdgeRouter 6P. This device is from Ubiquiti's commercial lineup and is intended to
|
||||
power something like a small branch office of a company. It comes in a small form factor (as opposed to larger rackmount
|
||||
units), it does not consume a lot of power, it has five PoE-capable Ethernet ports which I can directly connect up to
|
||||
the Ubiquiti Unifi UAP access point that I already have, and it has a powerful configuration interface. It can even
|
||||
act as a VPN endpoint!
|
||||
|
||||
Telekom's fiber internet offering for residential customers is GPON-based. GPON stands for "Gigabit Passive Optical
|
||||
Network" and means that instead of patching through one fiber or pair of fibers to each customer, several customers in
|
||||
one building are connected to a single fiber through optical splitters. These optical splitters are passive, i.e. they
|
||||
are just fancy pieces of glass and fibers and do not require electrical power. The advantage of GPON is lower initial
|
||||
cost for the operator, the disadvantage is that competing providers can only ever hope to get traffic handed through by
|
||||
Telekom and will never be able to use their own equipment on the "network" end of the fiber.
|
||||
|
||||
Telekom wants you to connect to its fiber network through a small plastic box that they call "modem", and that the rest
|
||||
of the world calls "ONT", or Optical Network Terminator. Telekom's ONT has an upstream optical port with an LC
|
||||
connector, and a regular RJ45 ethernet port downstream. The "modem" in fact contains an entire linux system that
|
||||
terminates the ITU-standard suite of protocols that is used to manage what happens on the fiber, e.g. scheduling of
|
||||
transmission slots and adjustment of transmitter laser power.
|
||||
|
||||
Looking at Telekom's plastic box ONT and my nice and shiny EdgeRouter, I was not a fan of this solution. Doing some
|
||||
research I found out that you can in fact get GPON ONTs in an SFP module form factor. My EdgeRouter has an SFP slot, so
|
||||
if I could get one of these that is compatible with Telekom's GPON flavor I could theoretically just plug it into my
|
||||
EdgeRouter's SFP slot with no separate power supply needed, saving a lot of space in the process.
|
||||
|
||||
Finding a GPON SFP ONT that is compatible with Telekom's network turned out to be the hard part. While there are lots of
|
||||
commercial devices that look like they *should be* compatible, I could not be sure and I did not feel like sinking lots
|
||||
of money and weeks of trial and error into figuring out which are and which are not. After about half a dozen calls with
|
||||
various Telekom customer service departments I found the solution that ultimately ended up working: For their business
|
||||
customer fiber internet offering, Telekom uses the same GPON standard, but different ONT equipment. Their router for
|
||||
business customers is called "Digitalisierungsbox" and it in fact comes with an SFP GPON ONT. And, as it turns out, you
|
||||
can order that SFP GPON ONT separately for about 50 € (the same as the plastic box one) from either Telekom or a number
|
||||
of independent online stores. The Telekom part number of the thing is 40823569, the EAN is 4718937619382.
|
||||
|
||||
Below is a list of steps that I had to undertake in order to get my EdgeRouter/SFP ONT setup to work.
|
||||
|
||||
Hardware Setup
|
||||
==============
|
||||
|
||||
The hardware setup is really simple. The SFP ONU is plugged into the EdgeRouter's SFP port. The ONU is connected to
|
||||
the Telekom Fiber through the LC/APC to SC/APC adapter cable that is included in its package. Telekom's technician will
|
||||
install an LC/APC coupler to join both cables. To configure the EdgeRouter, connect yourself through an ethernet cable
|
||||
*on port 2*. Ubiquiti's setup wizards assume the WAN interface is either port 1 or the SFP port (port 5), and default to
|
||||
use port 2 as their LAN interface even when port 5 is configured as the only WAN port. The default IP for the EdgeRouter
|
||||
is ``192.168.1.1``, and the default UID/PW is ubnt/ubnt.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Getting access to the SFP ONU's config interface
|
||||
------------------------------------------------
|
||||
|
||||
In this section I am assuming you want to configure the SFP ONU while it is plugged into the EdgeRouter from a laptop
|
||||
connected to the EdgeRouter's ethernet port 2. To do this, we have to first configure the right IP/subnet on the
|
||||
EdgeRouter's SFP interface, then patch connections between the SFP ONU and the laptop through the EdgeRouter.
|
||||
|
||||
1. First, inside the EdgeRouter's config interface we need to configure a static IP with accompanying SNAT rule on the
|
||||
SFP port to allow us to access the SFP module's web interface through the laptop connected to the EdgeRouter. For
|
||||
this, configure the eth5 interface (which is the SFP port) to use the static IP ``10.10.1.2/24``.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure style="width: 20em">
|
||||
<a href="images/edgerouter_sfp_config.png">
|
||||
<img src="images/edgerouter_sfp_config.png" alt="The EdgeRouter's graphical configuration interface showing IP
|
||||
address 10.10.1.2/24 being configured for interface eth5, which is the SFP interface." data-pagefind-ignore>
|
||||
</a>
|
||||
<figcaption>SFP interface configuration to access the SFP ONU from a laptop connected to the EdgeRouter's LAN
|
||||
port</figcaption>
|
||||
</figure>
|
||||
|
||||
2. With the SFP port assigned an IP address, we need to add a NAT rule to forward connections from the configuration
|
||||
laptop on eth2 to the SFP port. We do this by adding a source NAT rule with masquerading enabled, for the TCP
|
||||
protocol, with destination address ``10.10.1.0/24`` (the SFP config interface's private network).
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure style="width: 20em">
|
||||
<a href="images/edgerouter_snat_config.png">
|
||||
<img src="images/edgerouter_snat_config.png" alt="The EdgeRouter's graphical configuration interface showing a
|
||||
source NAT being configured for interface eth5 for TCP protocol connections to destination address 10.10.1.1
|
||||
using masquerading." data-pagefind-ignore>
|
||||
</a>
|
||||
<figcaption>Source NAT configuration to access the SFP ONU from LAN. eth5, masquerading on, TCP, destination
|
||||
10.10.1.1 (the SFP ONU's IP).</figcaption>
|
||||
</figure>
|
||||
|
||||
3. Finally, make sure that your laptop will actually use the EdgeRouter as its gateway for IPs within ``10.10.1.0/24``.
|
||||
On the laptop, disable any VPNs, disconnect your Wifi and make sure that IP r shows a default route pointing at the
|
||||
EdgeRouter's ``192.168.1.1``. If that isn't the case, on Linux you can manually add the necessary route by using
|
||||
``sudo ip r a 10.10.1.0/24 via 192.168.1.1 dev enp5s0``
|
||||
|
||||
After setting up this temporary route, you should be able to access the SFP ONU's configuration web interface by
|
||||
pointing a browser at ``http://10.10.1.1/`` Just make sure you use plain-text HTTP here, not secure HTTP**S**. The
|
||||
default login credentials for the device are admin/1234.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure style="width: 30em">
|
||||
<a href="images/sfp_onu_web_if.png">
|
||||
<img src="images/sfp_onu_web_if.png" alt="The SFP ONU configuration web interface is a basic-looking website with
|
||||
a big Zyxel logo on it. It has menu options named status, setup and management. It shows a system overview
|
||||
page that lists the device's uptime and software version." data-pagefind-ignore>
|
||||
</a>
|
||||
<figcaption>The SFP ONU's web interface.</figcaption>
|
||||
</figure>
|
||||
|
||||
Configuring the PLOAM password / SLID / ONT-Installationskennung
|
||||
----------------------------------------------------------------
|
||||
|
||||
On the SFP ONU's web interface, we only have to change one single setting: Under "Setup", we have to set what the SFP
|
||||
ONU calls "SLID" to the PLOAM password for the interface. Telekom calls this the "ONT-Installationskennung". You get
|
||||
this from your Telekom technician. In the config interface, select ASCII mode and enter the number using the format
|
||||
``ABCD000000`` with four capital letters followed by six zeros. If necessary, you can read the SFP ONU's serial number
|
||||
on this page.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure style="width: 30em">
|
||||
<a href="images/sfp_onu_ploam_pw_config.png">
|
||||
<img src="images/sfp_onu_ploam_pw_config.png" alt="The SFP ONU configuration web interface shows its SLID
|
||||
configuration page. A text field labelled SLID asks the user to enter a value of at most ten characters. As
|
||||
an example, abcdefg123 is listed." data-pagefind-ignore>
|
||||
</a>
|
||||
<figcaption>The SFP ONU's config interface to set SLID/PLOAM PW/ONT-Installationskennung.</figcaption>
|
||||
</figure>
|
||||
|
||||
Press "Save Config" on the top right of the web page, then select "Reset ONU" and click "Apply" under the "Reset ONU"
|
||||
link on the left. Make sure to not select the factory reset option instead.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure style="width: 30em">
|
||||
<a href="images/sfp_onu_reset.png">
|
||||
<img src="images/sfp_onu_reset.png" alt="The SFP ONU configuration web interface shows its reset ONU page. There
|
||||
are two options labelled Reset ONU and Reset to factory default settings. The reset ONU option is
|
||||
selected." data-pagefind-ignore>
|
||||
</a>
|
||||
<figcaption>Rebooting the SFP ONU.</figcaption>
|
||||
</figure>
|
||||
|
||||
With the ONU configured, after the reset the "GPON Information" page from the left menu under "Status" from the top menu
|
||||
should show ``GPON Line Status: O5``. You can now remove the SNAT rule and IP address from the SFP interface in the
|
||||
EdgeRouter's config. I recommend this since there is no way to change the ONU's default credentials, and leaving the
|
||||
SNAT rule in place makes it vulnerable to attacks from your LAN. If you use the EdgeRouter's setup wizard in the next
|
||||
step, that wizard will reset all of these settings.
|
||||
|
||||
Configuring PPPoE and NAT
|
||||
-------------------------
|
||||
|
||||
Our ONU now has a low-level connection to Telekom's fiber network. The next step is to configure the EdgeRouter to
|
||||
authenticate with the ONU through PPPoE. The easiest way to do this is to use the EdgeRouter's "Basic Setup" wizard as
|
||||
described in the `EdgeOS User Guide`. In the wizard, select the SFP port (``eth5``) as the internet/WAN port. Select
|
||||
``Internet Connection Type`` as ``PPPoE``, then enter the PPPoE credentials you got from your Telekom technician. The
|
||||
password is your "Persönliches Kennwort" that you also use to log in to your customer account on Telekom's website. The
|
||||
account name is ``[anschlusskennung] [zugangsnummer] "#" [mitbenutzernummer] "@t-online.de"``, so something like
|
||||
``002712345678012345678901#0001@t-online.de``. Enable "Internet connection is on VLAN" and enter VLAN ID ``7``. This is
|
||||
necessary because of the way Telekom set up their triple play (TV/phone/internet) service. After following through with
|
||||
the wizard, your internet should be already working on port 2 of the router. Note that despite selecting the SFP port as
|
||||
the router's WAN port, the wizard will still reserve port 1 (``eth0``) for another WAN interface, so you will only be
|
||||
able to access the configuration interface through port 2 (``eth1``) after the wizard is done. You can of course change
|
||||
this later.
|
||||
|
||||
That's it, you're done and your internet should be working!
|
||||
|
||||
Having Fun with the SPF GPON ONU
|
||||
================================
|
||||
|
||||
If you want to dig deeper into the internals of Telekom's GPON implementation, the SFP ONU's firmware is a great
|
||||
starting point. Default credentials are all admin/admin or admin/1234 and you can even get a regular busybox shell on
|
||||
the device through SSH. The device's firmware is based on OpenWRT, and the source for large parts of the core control
|
||||
components can be found under open source licenses as well. While I would strictly advice you to not mess around with
|
||||
the actual modem settings because due to GPON you share a medium with your neighbors and might very well disrupt their
|
||||
internet if you mess up, inspecting the ONU's firmware is a great way to learn about the inner workings of a modern GPON
|
||||
network.
|
||||
|
||||
If you are interested in messing around with the SFP ONU, there is a github repository where interesting thins are
|
||||
collected `here <https://github.com/xvzf/zyxel-gpon-sfp/issues>`__.
|
||||
|
||||
.. _`EdgeOS User Guide`: https://dl.ubnt.com/guides/edgemax/EdgeOS_UG.pdf
|
||||
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
---
|
||||
title: "Thor's Hammer"
|
||||
date: 2018-05-03T11:59:37+02:00
|
||||
summary: >
|
||||
In case you were having an inferiority complex because your friends' IBM Model M keyboards are so much louder than
|
||||
the shitty rubber dome freebie you got with your pc... Here's the solution: Thor's Hammer, a simple typing cadence
|
||||
enhancer for PS/2 keyboards.
|
||||
---
|
||||
|
||||
In case you were having an inferiority complex because your friends' IBM Model M keyboards are so much louder than the
|
||||
shitty rubber dome freebie you got with your pc... Here's the solution: Thor's Hammer, a simple typing cadence enhancer
|
||||
for `PS/2`_ keyboards.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<video controls loop>
|
||||
<source src="video/thors_hammer.mov" type="video/h264">
|
||||
<source src="video/thors_hammer.webm" type="video/webm">
|
||||
Your browser does not support the HTML5 video tag.
|
||||
</video>
|
||||
<figcaption>A demonstration of the completed project.
|
||||
|
||||
<a href="video/thors_hammer.mov">h264 download</a> /
|
||||
<a href="video/thors_hammer.webm">webm download</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
The connects to the keyboard's PS/2 clock line and briefly actuates a large solenoid on each key press. An interesting
|
||||
fact about PS/2 is that the clock line is only active as long as either the host computer or the input device actually
|
||||
want to send data. In case of a keyboard that's the case when a key is pressed or when the host changes the keyboard's
|
||||
LED state, otherwise the clock line is silent. We ignore the LED activity for now as it's generally coupled to key
|
||||
presses. By just triggering an NE555 configured as astable flipflop we can stretch each train of clock pulses to a
|
||||
pulse a few tens of milliseconds long that is enough to actuate the solenoid.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/thors_hammer_schematic.jpg" alt="The schematic of the PS2 driver">
|
||||
<figcaption>The schematic of the driver stretching the PS/2 clock pulses to drive the solenoid.</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Since PS/2 sends each key press and key release separately this circuit will pulse twice per keystroke. It would be
|
||||
possible to ignore one of them but I figure the added noise just adds to the experience.
|
||||
|
||||
Built on a breadboard, the circuit looks like this.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/thors_hammer_breadboard.jpg" alt="The circuit built on a breadboard">
|
||||
<figcaption>The completed circuit built up on a breadboard and attached to a keyboard.</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Since my solenoid did not have a tensioning spring I used a rubber band and some vinyl tape to make an adjustable
|
||||
tensioner. The small orange USB hub serves as an end-stop because I had nothing else of the right shape. The sound and
|
||||
resonance of the thing can be adjusted to taste by moving the end stop, adjusting the tensioning rubber and tuning the
|
||||
excitation duration using the potentiometer. My particular solenoid was a bit slow so I added some pieces of circuit
|
||||
board as shims between the plunger and the case to limit the plunger's travel inside the solenoid core.
|
||||
|
||||
.. _`PS/2`: https://en.wikipedia.org/wiki/PS/2_port
|
||||
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
---
|
||||
title: "Wifi Led Driver"
|
||||
date: 2018-05-02T11:31:03+02:00
|
||||
summary: >
|
||||
After the multichannel LED driver was completed, I was just getting used to controlling LEDs at 14-bit resolution.
|
||||
I liked the board we designed in this project, but at 32 channels it was a bit large for most use cases. Sometimes I
|
||||
just want to pop a piece of LED tape or two somewhere, but I don't need a full 32 channels of control. I ended up
|
||||
thinking that a smaller version of the 32-channel driver that didn't require a separate control computer would be
|
||||
handy. So I sat down and designed a variant of the design with only 8 channels instead of 32 and an on-board ESP8266
|
||||
module instead of the RS485 transceiver for WiFi connectivity.
|
||||
---
|
||||
|
||||
Project motivation
|
||||
==================
|
||||
|
||||
.. FIXME finished project picture with LED tape
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/board_in_case.small.jpg">
|
||||
<figcaption>The completed driver board installed in the 3D-printed case. This device can now be connected to
|
||||
12V and two segments of LED tape that can then be controlled trough Wifi. The ESP8266 module goes on the pin
|
||||
header on the left and was removed for this picture.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
After the `multichannel LED driver`_ was completed, I was just getting used to controlling LEDs at 14-bit resolution.
|
||||
I liked the board we designed in this project, but at 32 channels it was a bit large for most use cases. Sometimes I
|
||||
just want to pop a piece of LED tape or two somewhere, but I don't need a full 32 channels of control. I ended up
|
||||
thinking that a smaller version of the 32-channel driver that didn't require a separate control computer would be
|
||||
handy. So I sat down and designed a variant of the design with only 8 channels instead of 32 and an on-board ESP8266_
|
||||
module instead of the RS485_ transceiver for WiFi connectivity.
|
||||
|
||||
The Electronics
|
||||
===============
|
||||
|
||||
The schematic was mostly copy-pasted from the 32-channel design. The PCB was designed from scratch. This time, I went
|
||||
for a 5x7cm form factor to allow for enough room for all connectors and to give the ESP8266_'s WiFi antenna enough
|
||||
space. The board has two 5-pin Phoenix-style_ for two RGB-White (RGBW) tapes and one 2-pin Phoenix-style_ connector for
|
||||
12V power input. The control circuitry and the serial protocol are unchanged, but the STM32_ now talks to an ESP-01_
|
||||
module running custom firmware.
|
||||
|
||||
The LEDs are driven using a 74HC595_ shift register controlling a bunch of AO3400_ MOSFETs_, with resistors in front of
|
||||
the MOSFETs_' gates to slow down the transitions a bit to reduce brighntess nonlinearities and EMI_ resulting from
|
||||
ringing of the LED tape's wiring inductance.
|
||||
|
||||
The board has two spots for either `self-resettable fuses (polyfuses) <polyfuse_>`__ or regular melting-wire fuses_ in
|
||||
a small SMD_ package, one for each RGBW output. For low currents the self-resettable fuses should be okay but at higher
|
||||
currents their `trip times get long enough that they become unlikely to trip in time to save anything
|
||||
<littlefuse-16r-datasheet_>`__, so plain old non-resettable fuses would be the way to go there.
|
||||
|
||||
.. FIXME finished board photos
|
||||
.. FIXME board with test tape picture
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="subfigure" data-pagefind-ignore>
|
||||
<figure>
|
||||
<img src="images/schematic.png">
|
||||
<figcaption>
|
||||
The schematic of the driver board, with the ESP8266 on the top left, the STM32 microcontroller for LED
|
||||
modulation below, the shift register in the middle and the LED drivers and outputs on the right.
|
||||
<a href="resource/schematic_and_pcb.pdf">Download PDF</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="images/layout.png">
|
||||
<figcaption>
|
||||
The board layout with the top side being visible. The top side contains the footprint for the ESP8266, the
|
||||
microcontroller, fuses, filter cap, connectors and the shift register. The LEDs are connected on the left,
|
||||
with one connector per LED tape segment. The power input connector is on the bottom right. The LED driver
|
||||
MOSFETs are in small SOT-23 packages on the back of the board. Since this board is not intended for
|
||||
super-high currents, the MOSFETs are adequately cooled just through the board's copper planes.
|
||||
<a href="resource/schematic_and_pcb.pdf">Download PDF</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<figure data-pagefind-ignore>
|
||||
<img src="images/boards.small.jpg">
|
||||
<figcaption>The completed PCBs of this project (front) and the `multichannel LED driver`_ project the driver
|
||||
circuitry was derived from (back).
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
The Firmware
|
||||
============
|
||||
|
||||
The STM32_ firmware only had to be slightly modified to accomodate the reduced channel count since the protocol remains
|
||||
unchanged. The ESP firmware is based on esphttpd_ by Spritetm_. The modifications to the webserver firmware are pretty
|
||||
basic. First, the UART console has been disabled since I use the UART to talk to the STM32. The few bootloader messages
|
||||
popping out the UART on boot are not an issue, since they're unlikely to contain the fixed 32-bit address prefix the
|
||||
serial protocol requires for the STM32_ to do anything.
|
||||
|
||||
Second, I added LED control by adding drivers for the serial protocol and a bunch of colorspace conversion functions.
|
||||
When I first tested the prototype software, I noticed that color reproduction was extremely poor. When I just sent a
|
||||
HSV_ rainbow fade from a python command line, the result looked totally wrong. The fade did not seem to go at a constant
|
||||
speed and some colors, in particular yellow, orange and greens, were not visible at all. The problem turned out to be a
|
||||
stark mismatch of the red, green and blue channels of the LED tape and less-than-optimal color reproduction of the pure
|
||||
colors. I decided to properly measure the LED tape's color reproduction so I could compensate for it in software. This
|
||||
turned out to be an extremely interesting project, the details of which you can read in my `LED characterization`_
|
||||
article.
|
||||
|
||||
Third, I updated the built-in websites with some ad-hoc documentation on how to use the thing and a basic interface for
|
||||
LED control.
|
||||
|
||||
.. FIXME screenshot of firmware website
|
||||
|
||||
Making an enclosure
|
||||
===================
|
||||
|
||||
To be actually useful, the driver needed a robust enclosure. Bare PCBs are nice for prototyping, but for actually
|
||||
putting the thing anywhere it needs a case to protect it against random destruction.
|
||||
|
||||
The board has four mounting holes with comfortable spacing in its corners to allow easy mounting inside a 3D-printed
|
||||
case. The case itself is described in an OpenSCAD_ script. To make it look a little nicer, a little 3D relief is laid
|
||||
into the lid. The 3D relief is generated with a bit of blender magic. The source STL_ model is loaded into blender, then
|
||||
blender's amazingly flexible rendering system is used to export a depth map of a projection of the model as a PNG_ file.
|
||||
This depth map is then imported as a triangle mesh into OpenSCAD_.
|
||||
|
||||
For the relief to look good, I chose a rather high resolution for the depth map. This unfortunately leads to extreme
|
||||
memory use and processing time on the part of OpenSCAD_, but since I have access to a sufficiently fast machine that is
|
||||
not a problem. Just be careful if you try opening the OpenSCAD_ file on your machine, OpenSCAD_ will probably crash
|
||||
unless you're on a beefy machine or interrupt it when it starts auto-rendering the file.
|
||||
|
||||
The board is mounted into the enclosure using knurled insert nuts that are pressed into a 3D-printed hole using a bit of
|
||||
violence.
|
||||
|
||||
.. _`multichannel LED driver`: {{<ref "blog/multichannel-led-driver/index.rst">}}
|
||||
.. _`LED characterization`: {{<ref "blog/led-characterization/index.rst">}}
|
||||
.. _ESP8266: https://en.wikipedia.org/wiki/ESP8266
|
||||
.. _RS485: https://en.wikipedia.org/wiki/RS-485
|
||||
.. _Phoenix-style: https://www.phoenixcontact.com/online/portal/de?uri=pxc-oc-itemdetail:pid=1757019&library=dede&tab=1
|
||||
.. _STM32: http://www.st.com/resource/en/datasheet/stm32f030f4.pdf
|
||||
.. _ESP-01: http://www.watterott.com/de/ESP8266-WiFi-Serial-Transceiver-Modul
|
||||
.. _74HC595: http://www.ti.com/lit/ds/symlink/sn74hc595.pdf
|
||||
.. _AO3400: http://aosmd.com/pdfs/datasheet/AO3400.pdf
|
||||
.. _MOSFETs: https://en.wikipedia.org/wiki/MOSFET
|
||||
.. _EMI: https://en.wikipedia.org/wiki/Electromagnetic_interference
|
||||
.. _polyfuse: https://en.wikipedia.org/wiki/Resettable_fuse
|
||||
.. _SMD: https://en.wikipedia.org/wiki/Surface-mount_technology
|
||||
.. _fuses: https://en.wikipedia.org/wiki/Fuse_(electrical)
|
||||
.. _littlefuse-16r-datasheet: http://m.littelfuse.com/~/media/electronics/datasheets/resettable_ptcs/littelfuse_ptc_16r_datasheet.pdf.pdf
|
||||
.. _OpenSCAD: http://www.openscad.org/
|
||||
.. _STL: https://en.wikipedia.org/wiki/STL_(file_format)
|
||||
.. _PNG: https://en.wikipedia.org/wiki/Portable_Network_Graphics
|
||||
.. _esphttpd: https://github.com/Spritetm/esphttpd
|
||||
.. _Spritetm: http://spritesmods.com/
|
||||
.. _`HSV`: https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
---
|
||||
title: "wsdiff: Responsive diffs in plain HTML"
|
||||
date: 2025-07-25T23:42:00+01:00
|
||||
summary: >
|
||||
There's many tools that render diffs on the web, but almost none that work well on small screens such as phones. I
|
||||
fixed this by publishing wsdiff, a diffing tool written in Python that produces diffs as beautiful, responsive,
|
||||
static, self-contained HTML pages. wsdiffs wrap text to fit the window, and dynamically switch between unified and
|
||||
split diffs based on screen size using only CSS.
|
||||
---
|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
First off, have a demo. Because of the width of this page, the output will show an unified diff. To try out the split
|
||||
diff layout, make sure your browser window is wide enough and open the demo in a separate tab using `this link
|
||||
</wsdiff-example.html>`__.
|
||||
|
||||
wsdiff supports dark mode, try it out by toggling dark mode in your operating system!
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe src="/wsdiff-example.html" style="width: 100%; height: 30em; border: 1px #d0d0d0 solid" id="wsdiff example diff"></iframe>
|
||||
|
||||
Core Features
|
||||
-------------
|
||||
|
||||
There's many tools that render diffs on the web, but almost none that work well on small screens such as phones. I fixed
|
||||
this by publishing `wsdiff <https://pypi.org/project/wsdiff/>`__, a diffing tool written in Python that produces diffs
|
||||
as beautiful, responsive, static, self-contained HTML pages. wsdiffs wrap text to fit the window, and dynamically switch
|
||||
between unified and split diffs based on screen size using only CSS.
|
||||
|
||||
Responsive Line Wrapping
|
||||
........................
|
||||
|
||||
The first challenge I solved was wrapping source code lines to match the available screen space. Other tools often just
|
||||
show horizontal scroll bars, which is an okay workaround when you're mostly working with hard-wrapped source code on a
|
||||
laptop or desktop screen, but which results in catastrophic UX on any phone.
|
||||
|
||||
I solved line breaking with a combination of CSS-controlled, web-standard word breaking rules: ``overflow-wrap:
|
||||
anywhere`` for source code (`MDN link <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-wrap>`__) and
|
||||
``white-space: pre-wrap`` to preserve whitespace accurately (`MDN link
|
||||
<https://developer.mozilla.org/en-US/docs/Web/CSS/white-space>`__). To make both sides of the split diff align, and to
|
||||
align line numbers with wrapped source code lines, the diff is laid out using a `CSS grid layout`_. In side-by-side
|
||||
view, the layout has four columns: two for line numbers and two for the content. In unified view, the left ("old")
|
||||
content column is dropped, and the deleted or modified lines that are highlighted in it in side-by-side view are slotted
|
||||
into the remaining right column.
|
||||
|
||||
When soft-wrapping source code, text editors will often display a little curved arrow marker to indicate that a line was
|
||||
soft-wrapped, and that there is not actually a newline character in the file at that location. wsdiff solves this
|
||||
using the same technique I used for the soft-wrapping code blocks in this blog, described `here <{{<ref
|
||||
"blog/css-only-code-blocks/index.rst">}}>`__. It inserts a string of ``"\a↳\a↳\a↳\a↳\a↳..."`` into the line number
|
||||
element's ``::after`` pseudo-element. This string evaluates to a sequence of unicode arrows separated by line breaks,
|
||||
and starting with an empty line. The ``::after`` pseudo-element is positioned using ``position: absolute``, and the
|
||||
parent ``<span class="lineno">`` has ``position: relative`` set. This way, the arrow pseudo-element gets placed on top
|
||||
of the lineno span without affecting the layout at all. By setting ``overflow: clip`` on the parent ``<span
|
||||
class="lineno">``, the arrow pseudo-element gets cut off vertically wherever the parent line number element naturally
|
||||
ends. Since both the line and the line number element share a grid row, the line number element always matches the
|
||||
height of the soft-wrapped line.
|
||||
|
||||
Responsive Split/Unified Layout Selection
|
||||
.........................................
|
||||
|
||||
To dynamically change between unified and side-by-side views, wsdiff uses a web-standard `Media Query`_. By default, the
|
||||
page is laid out for side-by-side view. In the HTML source, the diff is listed as it is displayed in side-by-side view,
|
||||
with the old and new lines along with their line numbers interleaved.
|
||||
|
||||
The magic happens when the media query gets triggered by a narrow screen width. The media query re-adjusts the layout in
|
||||
four core steps:
|
||||
|
||||
1. All unchanged lines in the left (old) column are hidden.
|
||||
2. The left content column of the grid layout is hidden, so that now there are three columns: old line number, new line
|
||||
number, and unified content.
|
||||
3. All deleted or changed lines from the left (old) column are re-located to the right column. They naturally slot in
|
||||
in the right spot because they already appear in the right order in the HTML source.
|
||||
4. By slotting in the old lines in the right column, we have created gaps in the line number columns. Every deleted
|
||||
line has an empty cell in the new line number column, and every inserted line has one in the old line number column.
|
||||
The CSS adjusts the layout of these empty cells such that the border lines align nicely, and it overrides the
|
||||
newline markers so that they only show in the right (new) line number column, not both.
|
||||
|
||||
Since this is all CSS, it happens automatically and near-instantly. Since it is using only web standard features, it
|
||||
works across browsers and platforms.
|
||||
|
||||
Unchanged Line Folding in CSS
|
||||
.............................
|
||||
|
||||
When showing the diff of a large file, it is usually best to hide large runs of unchanged lines. wsdiff does this
|
||||
similar to code folding in text editors. When a long run of unchanged lines is detected, a marker is placed spanning the
|
||||
diff. This marker contains a checkbox that can be toggled to hide the unchanged lines. This feature is done completely
|
||||
in CSS using a ``:has(input[type="checkbox"]:checked)`` selector.
|
||||
|
||||
The actual mechanics are quite simple. To cleanly hide the lines, they must be placed in a container ``<div>``. That div
|
||||
has a CSS subgrid layout using ``display: grid; grid-template-columns: subgrid;``, meaning that its contents align to
|
||||
the surrounding diff grid.
|
||||
|
||||
Dark Mode
|
||||
.........
|
||||
|
||||
Integrating a website with the OS-level dark mode is surprisingly easy. All you need is a `Media Query`_ that selects
|
||||
for ``@media (prefers-color-scheme: dark)`` and you're good. wsdiff uses named colors using `CSS Custom Properties`_, so
|
||||
the actual dark mode media query only needs to override these color properties, and the rest of the CSS will be
|
||||
re-computed automatically.
|
||||
|
||||
Limitations: Text selection
|
||||
...........................
|
||||
|
||||
A limitation in having a combined, single HTML source for both side-by-side and unified diffs is that text selection
|
||||
only works naturally in either mode. You can't make text selection work in both simultaneously without re-sorting the
|
||||
lines in the HTML source, since there is no way to override the text selection order from pure CSS. In wsdiff, I worked
|
||||
around this issue by just disabling text selection on the unchanged lines in the left (old) column, so selecting text in
|
||||
the right column copies the unified diff as one would expect.
|
||||
|
||||
Try it yourself!
|
||||
----------------
|
||||
|
||||
You can find the demo from above at `this link </wsdiff-example.html>`__.
|
||||
|
||||
You can install wsdiff yourself `from PyPI <https://pypi.org/project/wsdiff/>`__:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
$ pip install -U wsdiff
|
||||
Successfully installed wsdiff-0.3.1
|
||||
$ wsdiff old.py new.py -o diff.html
|
||||
|
||||
.. _`CSS grid layout`: https://css-tricks.com/snippets/css/complete-guide-grid/
|
||||
.. _`Media Query`: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries
|
||||
.. _`CSS Custom Properties`: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
---
|
||||
title: "Impressum"
|
||||
noindex: true
|
||||
---
|
||||
|
||||
Impressum
|
||||
---------
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<p>
|
||||
Sebastian Götte<br/>
|
||||
c/o Praxis Dr. Götte<br/>
|
||||
Krankenhausstr. 1a<br/>
|
||||
54634 Bitburg<br/>
|
||||
imprint@jaseg.net
|
||||
</p>
|
||||
|
||||
Inhaltlich Verantwortlicher gem. § 55 II RStV: Sebastian Götte (Anschrift s.o.)
|
||||
|
||||
Lizenz dieser Seite
|
||||
-------------------
|
||||
|
||||
.. raw:: html
|
||||
|
||||
Dieses Werk ist lizenziert unter einer
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
||||
Creative Commons Namensnennung - Nicht-kommerziell - Weitergabe unter gleichen Bedingungen 4.0 International
|
||||
Lizenz (CC-BY-NC-SA)
|
||||
</a>.<br/>
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
||||
<img alt="Creative Commons Lizenzvertrag" style="border-width:0"
|
||||
src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" data-pagefind-ignore/>
|
||||
</a>
|
||||
|
||||
|
||||
Haftungsbeschränkung für Inhalte der Website
|
||||
--------------------------------------------
|
||||
|
||||
Gemäß § 7 Abs. 1 TMG ist der Verantwortliche der Website i. S. v. § 5 TMG für eigene Informationen, die er zur Nutzung
|
||||
bereithält, nach den allgemeinen Gesetzen verantwortlich.
|
||||
|
||||
Der Verantwortliche der vorbezeichneten Website übernimmt keine Haftung auf Aktualität, Richtigkeit und Vollständigkeit
|
||||
der auf dieser Website zur Verfügung gestellten Inhalte. Dies gilt nicht, wenn dem Verantwortlichen vorsätzliches oder
|
||||
grob fahrlässiges Verhalten vorzuwerfen ist. Die Inhalte wurden mit der größtmöglichen Sorgfalt erstellt. Dennoch kann
|
||||
die inhaltliche Richtigkeit insbesondere bei komplexen Themen nicht gewährleistet werden, so dass der Verantwortliche
|
||||
den Nutzern empfiehlt, bei wichtigen Informationen bei den zuständigen Stellen anzufragen oder rechtliche Beratung in
|
||||
Anspruch zu nehmen. Sofern kostenpflichtige Inhalte oder Dienste auf der Website zur Verfügung gestellt werden, handelt
|
||||
es sich dabei um unverbindliche Invitatio ad offerendum, welche lediglich zur Abgabe eines Angebots durch den Nutzer
|
||||
aufrufen und selbst kein verbindliches Angebot darstellen.
|
||||
|
||||
Gemäß §§ 8 ff. TMG ist der Website-Betreiber für fremde Inhalte, die er für einen Nutzer veröffentlicht, nicht
|
||||
verantwortlich, sofern
|
||||
|
||||
* er keine Kenntnis von der rechtswidrigen Handlung oder der Information hat und ihm im Falle von
|
||||
Schadensersatzansprüchen auch keine Tatsachen oder Umstände bekannt sind, aus denen die rechtswidrige Handlung oder
|
||||
die Information offensichtlich wird, oder
|
||||
|
||||
* er unverzüglich tätig geworden ist, um die Information zu entfernen oder den Zugang zu ihr zu sperren, sobald er
|
||||
diese Kenntnis erlangt hat.
|
||||
|
||||
Erlangt der Website-Betreiber Kenntnis von solchen rechtswidrigen Inhalten, werden diese unverzüglich entfernt.
|
||||
|
||||
Haftung für ausgehende Links
|
||||
----------------------------
|
||||
|
||||
Der Website-Betreiber verlinkt von seiner Website auf fremde Websites. Durch diese sog. „Hyperlinks“ wird der Nutzer
|
||||
direkt auf die fremde Website geleitet. Der Website-Betreiber hat dabei keinerlei Einfluss auf die Informationen der
|
||||
fremden Website. Daher kann keine Haftung für die Aktualität, Richtigkeit und Vollständigkeit der Inhalte der fremden
|
||||
Website übernommen werden. Der Website-Betreiber versichert jedoch, dass ihm zum Zeitpunkt des Setzens der Verlinkung
|
||||
keinerlei rechtliche Verstöße bekannt waren und er die fremde Website im Rahmen des Zumutbaren geprüft hat.
|
||||
|
||||
Erhält der Website-Betreiber Kenntnis von der Rechtswidrigkeit der verlinkten Inhalte, wird der entsprechende Link
|
||||
unverzüglich entfernt.
|
||||
|
||||
Urheberrechte
|
||||
-------------
|
||||
|
||||
Die auf dieser Webseite veröffentlichten Inhalte unterliegen dem deutschen Urheberrecht.
|
||||
|
||||
Als Urheber i. S. v. § 7 UrhG stehen dem Website-Betreiber die alleinigen und ausschließlichen Verwertungsrechte gemäß
|
||||
§§ 15 ff. UrhG zu. Eine Vervielfältigung oder Verwendung sämtlicher Inhalte der Website in fremden elektronischen oder
|
||||
gedruckten Medien ist ohne vorherige Zustimmung des Website-Betreibers untersagt und wird ggf. verfolgt.
|
||||
|
||||
Datenschutz
|
||||
-----------
|
||||
|
||||
Die Website kann grundsätzlich ohne Eingabe von personenbezogenen Daten wie z. B. Name oder E-Mail-Adresse genutzt
|
||||
werden. Sollte die Möglichkeit der Eingabe solcher Daten bestehen, so erfolgt die Mitteilung dieser Daten auf
|
||||
freiwilliger Basis durch den Nutzer. Eine Weitergabe dieser Daten an Dritte ist ohne die ausdrückliche Zustimmung durch
|
||||
den Nutzer ausgeschlossen.
|
||||
|
||||
Ein lückenloser Schutz der übermittelten Daten vor dem Zugriff durch unbefugte Dritte ist jedoch nicht möglich, da es im
|
||||
Bereich der Datenübermittlung im Internet zu Sicherheitslücken kommen kann. Der Website-Betreiber versucht jedoch, die
|
||||
Gefahr des unbefugten Zugriffs durch geeignete Maßnahmen zu unterbinden und im Falle der Kenntnis von einer
|
||||
Sicherheitslücke diese durch geeignete Maßnahmen zu schließen.
|
||||
|
||||
Der Nutzung der im Impressum veröffentlichten Kontaktdaten durch Dritte zur Übersendung von nicht ausdrücklich
|
||||
gewünschter Werbung, insbesondere Spam-Mails, wird widersprochen und im Falle der Nichtbeachtung ggf. mit rechtlichen
|
||||
Schritten verfolgt.
|
||||
|
||||
(Quelle: `BUSE HERZ GRUNST Rechtsanwälte <https://www.kanzlei-wirtschaftsrecht.berlin/aktuelles/kostenloser-muster-disclaimer>`__)
|
||||
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
---
|
||||
title: "8seg"
|
||||
external_links:
|
||||
- name: Sources and hardware design files
|
||||
url: "https://git.jaseg.de/8seg.git"
|
||||
- name: Enclosure 3D models
|
||||
url: "https://www.printables.com/model/695260-parametric-flexible-aluminium-profile-case"
|
||||
- name: Technical overview blog post
|
||||
url: "/blog/8seg"
|
||||
summary: >
|
||||
8seg is an experimental textual display. It is made from a 45m by 1.5m large lacework banner that can be put up in a
|
||||
variety of spaces, conforming to the space's size and shape through bending and folding.
|
||||
---
|
||||
|
||||
8seg
|
||||
====
|
||||
|
||||
8seg is an experimental textual display. It is made from a 45m by 1.5m large lacework banner that can be put up in a
|
||||
variety of spaces, conforming to the space's size and shape through bending and folding.
|
||||
|
||||
8seg was last set up at 37C3 in the entrance hall, on the second-floor railing opposite Hall X.
|
||||
|
||||
By popular request, you can find 3D models for the printable parts of the power supply enclosures `here
|
||||
<https://www.printables.com/model/695260-parametric-flexible-aluminium-profile-case>`__
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
title: Projects
|
||||
hide_date: true
|
||||
---
|
||||
I maintain a number of open-source projects. Most of these I started out of some personal need or interest.
|
||||
I strive to keep all of them up to date and maintained, so if you notice an issue with one of them, please
|
||||
open an issue on the project's issue tracker.
|
||||
|
|
@ -1,700 +0,0 @@
|
|||
Gerbolyze renders SVG vector and PNG/JPG raster images into existing gerber PCB manufacturing files.
|
||||
Vector data from SVG files is rendered losslessly *without* an intermediate rasterization/revectorization step.
|
||||
Still, gerbolyze supports (almost) the full SVG 1.1 spec including complex, self-intersecting paths with holes,
|
||||
patterns, dashes and transformations.
|
||||
|
||||
Raster images can either be vectorized through contour tracing (like gerbolyze v1.0 did) or they can be embedded using
|
||||
high-resolution grayscale emulation while (mostly) guaranteeing trace/space design rules.
|
||||
|
||||
Try gerbolyze online at https://dyna.kokoroyukuma.de/gerboweb
|
||||
|
||||
.. figure:: pics/pcbway_sample_02_small.jpg
|
||||
:width: 800px
|
||||
|
||||
Drawing by `トーコ Toko <https://twitter.com/fluffy2038/status/1317231121269104640>`__ converted using Gerbolyze and printed at PCBWay.
|
||||
|
||||
|
||||
Tooling for PCB art is quite limited in both open source and closed source ecosystems. Something as simple as putting a
|
||||
pretty picture on a PCB can be an extremely tedious task. Depending on the PCB tool used, various arcane incantations
|
||||
may be necessary and even modestly complex images will slow down most PCB tools to a crawl.
|
||||
|
||||
Gerbolyze solves this problem in a toolchain-agnostic way by directly vectorizing SVG vector and PNG or JPG bitmap files
|
||||
onto existing gerber layers. Gerbolyze processes any spec-compliant SVG and "gerbolyzes" SVG vector data into a Gerber
|
||||
spec-compliant form. Gerbolyze has been tested against both the leading open-source KiCAD toolchain and the
|
||||
industry-standard Altium Designer. Gerbolyze is written with performance in mind and will happily vectorize tens of
|
||||
thousands of primitives, generating tens of megabytes of gerber code without crapping itself. With gerbolyze you can
|
||||
finally be confident that your PCB fab's toolchain will fall over before yours does if you overdo it with the high-poly
|
||||
anime silkscreen.
|
||||
|
||||
Gerbolyze is based on gerbonara_.
|
||||
|
||||
.. image:: pics/process-overview.png
|
||||
:width: 800px
|
||||
|
||||
.. contents::
|
||||
|
||||
Tl;dr: Produce high-quality artistic PCBs in three easy steps!
|
||||
--------------------------------------------------------------
|
||||
|
||||
Gerbolyze works in three steps.
|
||||
|
||||
1. Generate a scale-accurate template of the finished PCB from your CAD tool's gerber output:
|
||||
|
||||
.. code::
|
||||
|
||||
$ gerbolyze template --top template_top.svg [--bottom template_bottom.svg] my_gerber_dir
|
||||
|
||||
2. Load the resulting template image Inkscape_ or another SVG editing program. Put your artwork on the appropriate SVG
|
||||
layer. Dark colors become filled gerber primitives, bright colors become unfilled primitives. You can directly put
|
||||
raster images (PNG/JPG) into this SVG as well, just position and scale them like everything else. SVG clips work for
|
||||
images, too. Masks are not supported.
|
||||
|
||||
3. Vectorize the edited SVG template image drectly into the PCB's gerber files:
|
||||
|
||||
.. code::
|
||||
|
||||
$ gerbolyze paste --top template_top_edited.svg [--bottom ...] my_gerber_dir output_gerber_dir
|
||||
|
||||
Quick Start Installation (Any Platform)
|
||||
---------------------------------------
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip install --user gerbolyze
|
||||
|
||||
To uninstall, run
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip uninstall gerbolyze gerbonara resvg-wasi svg-flatten-wasi
|
||||
|
||||
To update, run
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip install --user --upgrade --upgrade-strategy eager gerbolyze
|
||||
|
||||
Speeding up gerbolyze using natively-built binaries
|
||||
---------------------------------------------------
|
||||
|
||||
This will install gerbolyze's binary dependency resvg and gerbolyze's svg-flatten utility as pre-built cross-platform
|
||||
WASM binaries. When you first run gerbolyze, it will take some time (~30s) to link these binaries for your system. The
|
||||
output is cached, so any future run is going to be fast.
|
||||
|
||||
WASM is slower than natively-built binaries. To speed up gerbolyze, you can natively build its two binary dependencies:
|
||||
|
||||
1. Install resvg natively using rust's cargo package manager: ``cargo install resvg``
|
||||
2. Install gerbolyze's svg-flatten utility natively. You can get pre-built binaries from gerbolyze's gitlab CI jobs `at
|
||||
this link <https://gitlab.com/gerbolyze/gerbolyze/-/pipelines?scope=tags&page=1>`__ by clicking the three dots on the
|
||||
right next to the version you want. These pre-built binaries should work on any x86_64 linux since they are
|
||||
statically linked. You can also build svg-flatten yourself by running ``make`` inside the ``svg-flatten`` folder from
|
||||
a gerbolyze checkout.
|
||||
|
||||
Gerbolyze will pick up these binaries when installed in your ``$PATH``. resvg is also picked up when it is installed by
|
||||
cargo in your home's ``~/.cargo``, even if it's not in your ``$PATH``. You can override the resvg, usvg or svg-flatten
|
||||
binary that gerbolyze uses by giving it the absoulute path to a binary in the ``$RESVG``, ``$USVG`` and ``$SVG_FLATTEN``
|
||||
environment variables.
|
||||
|
||||
|
||||
Build from source (any distro)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: sh
|
||||
|
||||
git clone --recurse-submodules https://git.jaseg.de/gerbolyze.git
|
||||
cd gerbolyze
|
||||
|
||||
python3 -m venv
|
||||
source venv/bin/activate
|
||||
python3 setup.py install
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Input on the left, output on the right.
|
||||
|
||||
.. image:: pics/test_svg_readme_composited.png
|
||||
:width: 800px
|
||||
|
||||
* Almost full SVG 1.1 static spec coverage (!)
|
||||
|
||||
* Paths with beziers, self-intersections and holes
|
||||
* Strokes, even with dashes and markers
|
||||
* Pattern fills and strokes
|
||||
* Transformations and nested groups
|
||||
* Proper text rendering with support for complex text layout (e.g. Arabic)
|
||||
* <image> elements via either built-in vectorizer or built-in halftone processor
|
||||
* (some) CSS
|
||||
|
||||
* Writes Gerber, SVG or KiCAD S-Expression (``.kicad_mod``) formats
|
||||
* Can export from top/bottom SVGs to a whole gerber layer stack at once with filename autodetection
|
||||
* Can export SVGs to ``.kicad_mod`` files like svg2mod (but with full SVG support)
|
||||
* Beziers flattening with configurable tolerance using actual math!
|
||||
* Polygon intersection removal
|
||||
* Polygon hole removal (!)
|
||||
* Optionally vector-compositing of output: convert black/white/transparent image to black/transparent image
|
||||
* Renders SVG templates from input gerbers for accurate and easy scaling and positioning of artwork
|
||||
* layer masking with offset (e.g. all silk within 1mm of soldermask)
|
||||
* Can read gerbers from zip files
|
||||
* Limited SVG support for board outline layers (no fill/region support)
|
||||
* Dashed lines supported on board outline layers
|
||||
|
||||
Gerbolyze is the end-to-end "paste this svg into these gerbers" command that handles all layers on both board sides at
|
||||
once. The heavy-duty computer geometry logic of gerbolyze is handled by the svg-flatten utility (``svg-flatten``
|
||||
directory). svg-flatten reads an SVG file and renders it into a variety of output formats. svg-flatten can be used like
|
||||
a variant of the popular svg2mod that supports all of SVG and handles arbitrary input ``<path>`` elements.
|
||||
|
||||
Algorithm Overview
|
||||
------------------
|
||||
|
||||
This is the algorithm gerbolyze uses to process a stack of gerbers.
|
||||
|
||||
* Map input files to semantic layers by their filenames
|
||||
* For each layer:
|
||||
|
||||
* load input gerber
|
||||
* Pass mask layers through ``gerbv`` for conversion to SVG
|
||||
* Pass mask layers SVG through ``svg-flatten --dilate``
|
||||
* Pass input SVG through ``svg-flatten --only-groups [layer]``
|
||||
* Overlay input gerber, mask and input svg
|
||||
* Write result to output gerber
|
||||
|
||||
This is the algorithm svg-flatten uses to process an SVG.
|
||||
|
||||
* pass input SVG through usvg_
|
||||
* iterate depth-first through resulting SVG.
|
||||
|
||||
* for groups: apply transforms and clip and recurse
|
||||
* for images: Vectorize using selected vectorizer
|
||||
* for paths:
|
||||
|
||||
* flatten path using Cairo
|
||||
* remove self-intersections using Clipper
|
||||
* if stroke is set: process dash, then offset using Clipper
|
||||
* apply pattern fills
|
||||
* clip to clip-path
|
||||
* remove holes using Clipper
|
||||
|
||||
* for KiCAD S-Expression export: vector-composite results using CavalierContours: subtract each clear output primitive
|
||||
from all previous dark output primitives
|
||||
|
||||
Web interface
|
||||
-------------
|
||||
|
||||
You can try gerbolyze online at https://dyna.kokoroyukuma.de/gerboweb
|
||||
|
||||
The web interface does not expose all of gerbolyze's bells and whistles, but it allows you to simply paste a single SVG
|
||||
file on a board to try out gerbolyze. Upload your design on the web interface, then download the template for either the
|
||||
top or bottom side, and put your artwork on the appropriate layer of that template using Inkscape_. Finally, upload the
|
||||
modified template and let gerbolyze process your design.
|
||||
|
||||
Command-line usage
|
||||
------------------
|
||||
.. _command_line_usage:
|
||||
|
||||
Generate SVG template from Gerber files:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
gerbolyze template [options] [--top|--bottom] input_dir_or.zip output.svg
|
||||
|
||||
Render design from an SVG made with the template above into a set of gerber files:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
gerbolyze paste [options] artwork.svg input_dir_or.zip output_dir_or.zip
|
||||
|
||||
Use svg-flatten to convert an SVG file into Gerber or flattened SVG:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
svg-flatten [options] --format [gerber|svg] [input_file.svg] [output_file]
|
||||
|
||||
Use svg-flatten to convert an SVG file into the given layer of a KiCAD S-Expression (``.kicad_mod``) file:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
svg-flatten [options] --format kicad --sexp-layer F.SilkS --sexp-mod-name My_Module [input_file.svg] [output_file]
|
||||
|
||||
Use svg-flatten to convert an SVG file into a ``.kicad_mod`` with SVG layers fed into separate KiCAD layers based on
|
||||
their IDs like the popular ``svg2mod`` is doing:
|
||||
|
||||
Note:
|
||||
Right now, the input SVG's layers must have *ids* that match up KiCAD's s-exp layer names. Note that when you name
|
||||
a layer in Inkscape that only sets a ``name`` attribute, but does not change the ID. In order to change the ID in
|
||||
Inkscape, you have to use Inkscape's "object properties" context menu function.
|
||||
|
||||
Also note that svg-flatten expects the layer names KiCAD uses in their S-Expression format. These are *different* to
|
||||
the layer names KiCAD exposes in the UI (even though most of them match up!).
|
||||
|
||||
For your convenience, there is an SVG template with all the right layer names and IDs located next to this README.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
svg-flatten [options] --format kicad --sexp-mod-name My_Module [input_file.svg] [output_file]
|
||||
|
||||
``gerbolyze template``
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Usage: ``gerbolyze template [OPTIONS] INPUT``
|
||||
|
||||
Generate SVG template for gerbolyze paste from gerber files.
|
||||
|
||||
INPUT may be a gerber file, directory of gerber files or zip file with gerber files. The output file contains a preview
|
||||
image of the input gerbers to allow you to position your artwork, as well as prepared Inkscape layers corresponding to
|
||||
each gerber layer. Simply place your artwork in this SVG template using Inkscape. Starting in v3.0, gerbolyze
|
||||
automatically keeps track of which board side (top or bottom) is contained in an SVG template.
|
||||
|
||||
Options:
|
||||
********
|
||||
``--top | --bottom``
|
||||
Output top or bottom side template. This affects both the preview image and the prepared Inkscape layers.
|
||||
|
||||
``--vector | --raster``
|
||||
Embed preview renders into output file as SVG vector graphics instead of rendering them to PNG bitmaps. The
|
||||
resulting preview may slow down your SVG editor.
|
||||
|
||||
``--raster-dpi FLOAT``
|
||||
DPI for rastering preview
|
||||
|
||||
``--bbox TEXT``
|
||||
Output file bounding box. Format: "w,h" to force [w] mm by [h] mm output canvas OR "x,y,w,h" to force [w] mm by [h]
|
||||
mm output canvas with its bottom left corner at the given input gerber coördinates.
|
||||
|
||||
|
||||
``gerbolyze paste``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
(see `below <vectorization_>`__)
|
||||
|
||||
Usage: ``gerbolyze paste [OPTIONS] INPUT_GERBERS OVERLAY_SVG OUTPUT_GERBERS``
|
||||
|
||||
Render vector data and raster images from SVG file into gerbers. The SVG input file can be generated using ``gerbolyze
|
||||
template`` and contains the name and board side of each layer. Note that for board outline layers, handling slightly
|
||||
differs from other layers as PCB fabs do not support filled Gerber regions on these layers.
|
||||
|
||||
Options:
|
||||
********
|
||||
|
||||
``--bbox TEXT``
|
||||
Output file bounding box. Format: "w,h" to force [w] mm by [h] mm output canvas OR "x,y,w,h" to force [w] mm by [h]
|
||||
mm output canvas with its bottom left corner at the given input gerber coördinates. This **must match the ``--bbox`` value given to
|
||||
template**!
|
||||
|
||||
``--subtract TEXT``
|
||||
Use user subtraction script from argument (see `below <subtraction_script_>`_)
|
||||
|
||||
``--no-subtract``
|
||||
Disable subtraction (see `below <subtraction_script_>`_)
|
||||
|
||||
``--dilate FLOAT``
|
||||
Default dilation for subtraction operations in mm (see `below <subtraction_script_>`_)
|
||||
|
||||
``--trace-space FLOAT``
|
||||
Passed through to svg-flatten, see `below <svg_flatten_>`__.
|
||||
|
||||
``--vectorizer TEXT``
|
||||
Passed through to svg-flatten, see `its description below <svg_flatten_>`__. Also have a look at `the examples below <vectorization_>`_.
|
||||
|
||||
``--vectorizer-map TEXT``
|
||||
Passed through to svg-flatten, see `below <svg_flatten_>`__.
|
||||
|
||||
``--exclude-groups TEXT``
|
||||
Passed through to svg-flatten, see `below <svg_flatten_>`__.
|
||||
|
||||
|
||||
.. _outline_layers:
|
||||
|
||||
Outline layers
|
||||
**************
|
||||
|
||||
Outline layers require special handling since PCB fabs do not support filled G36/G37 polygons on these layers. The main
|
||||
difference between normal layers and outline layers is how strokes are handled. On outline layers, strokes are
|
||||
translated to normal Gerber draw commands (D01, D02 etc.) with an aperture set to the stroke's width instead of tracing
|
||||
them to G36/G37 filled regions. This means that on outline layers, SVG end caps and line join types do not work: All
|
||||
lines are redered with round joins and end caps.
|
||||
|
||||
One exception from this are patterns, which work as expected for both fills and strokes with full support for joins and
|
||||
end caps.
|
||||
|
||||
Dashed strokes are supported on outline layers and can be used to make easy mouse bites.
|
||||
|
||||
.. _subtraction_script:
|
||||
|
||||
Subtraction scripts
|
||||
*******************
|
||||
|
||||
.. image:: pics/subtract_example.png
|
||||
:width: 800px
|
||||
|
||||
Subtraction scripts tell ``gerbolyze paste`` to remove an area around certain input layers to from an overlay layer.
|
||||
When a input layer is given in the subtraction script, gerbolyze will dilate (extend outwards) everything on this input
|
||||
layer and remove it from the target overlay layer. By default, Gerbolyze subtracts the mask layer from the silk layer to
|
||||
make sure there are no silk primitives that overlap bare copper, and subtracts each input layer from its corresponding
|
||||
overlay to make sure the two do not overlap. In the picture above you can see both at work: The overlay contains
|
||||
halftone primitives all over the place. The subtraction script has cut out an area around all pads (mask layer) and all
|
||||
existing silkscreen. You can turn off this behavior by passing ``--no-subtract`` or pass your own "script".
|
||||
|
||||
The syntax of these scripts is:
|
||||
|
||||
.. code-block::
|
||||
|
||||
{target layer} -= {source layer} {dilation} [; ...]
|
||||
|
||||
The target layer must be ``out.{layer name}`` and the source layer ``in.{layer name}``. The layer names are gerbolyze's
|
||||
internal layer names, i.e.: ``paste, silk, mask, copper, outline, drill``
|
||||
|
||||
The dilation value is optional, but can be a float with a leading ``+`` or ``-``. If given, before subtraction the
|
||||
source layer's features will be extended by that many mm. If not given, the dilation defaults to the value given by
|
||||
``--dilate`` if given or 0.1 mm otherwise. To disable dilation, simply pass ``+0`` here.
|
||||
|
||||
Multiple commands can be separated by semicolons ``;`` or line breaks.
|
||||
|
||||
The default subtraction script is:
|
||||
|
||||
.. code-block::
|
||||
|
||||
out.silk -= in.mask
|
||||
out.silk -= in.silk+0.5
|
||||
out.mask -= in.mask+0.5
|
||||
out.copper -= in.copper+0.5
|
||||
|
||||
.. _svg_flatten:
|
||||
|
||||
``svg-flatten``
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Usage: ``svg-flatten [OPTIONS]... [INPUT_FILE] [OUTPUT_FILE]``
|
||||
|
||||
Specify ``-`` for stdin/stdout.
|
||||
|
||||
Options:
|
||||
********
|
||||
|
||||
``-h, --help``
|
||||
Print help and exit
|
||||
|
||||
``-v, --version``
|
||||
Print version and exit
|
||||
|
||||
``-o, --format``
|
||||
Output format. Supported: gerber, gerber-outline (for board outline layers), svg, s-exp (KiCAD S-Expression)
|
||||
|
||||
``-p, --precision``
|
||||
Number of decimal places use for exported coordinates (gerber: 1-9, SVG: >=0). Note that not all gerber viewers are
|
||||
happy with too many digits. 5 or 6 is a reasonable choice.
|
||||
|
||||
``--clear-color``
|
||||
SVG color to use in SVG output for "clear" areas (default: white)
|
||||
|
||||
``--dark-color``
|
||||
SVG color to use in SVG output for "dark" areas (default: black)
|
||||
|
||||
``-f, --flip-gerber-polarity``
|
||||
Flip polarity of all output gerber primitives for --format gerber.
|
||||
|
||||
``-d, --trace-space``
|
||||
Minimum feature size of elements in vectorized graphics (trace/space) in mm. Default: 0.1mm.
|
||||
|
||||
``--no-header``
|
||||
Do not export output format header/footer, only export the primitives themselves
|
||||
|
||||
``--flatten``
|
||||
Flatten output so it only consists of non-overlapping white polygons. This perform composition at the vector level.
|
||||
Potentially slow. This defaults to on when using KiCAD S-Exp export because KiCAD does not know polarity or colors.
|
||||
|
||||
``--no-flatten``
|
||||
Disable automatic flattening for KiCAD S-Exp export
|
||||
|
||||
``--dilate``
|
||||
Dilate output gerber primitives by this amount in mm. Used for masking out other layers.
|
||||
|
||||
``-g, --only-groups``
|
||||
Comma-separated list of group IDs to export.
|
||||
|
||||
``-b, --vectorizer``
|
||||
Vectorizer to use for bitmap images. One of poisson-disc (default), hex-grid, square-grid, binary-contours,
|
||||
dev-null. Have a look at `the examples below <vectorization_>`_.
|
||||
|
||||
``--vectorizer-map``
|
||||
Map from image element id to vectorizer. Overrides --vectorizer. Format: id1=vectorizer,id2=vectorizer,...
|
||||
|
||||
You can use this to set a certain vectorizer for specific images, e.g. if you want to use both halftone
|
||||
vectorization and contour tracing in the same SVG. Note that you can set an ``<image>`` element's SVG ID from within
|
||||
Inkscape though the context menu's Object Properties tool.
|
||||
|
||||
``--force-svg``
|
||||
Force SVG input irrespective of file name
|
||||
|
||||
``--force-png``
|
||||
Force bitmap graphics input irrespective of file name
|
||||
|
||||
``-s, --size``
|
||||
Bitmap mode only: Physical size of output image in mm. Format: 12.34x56.78
|
||||
|
||||
``--sexp-mod-name``
|
||||
Module name for KiCAD S-Exp output. This is a mandatory argument if using S-Exp output.
|
||||
|
||||
``--sexp-layer``
|
||||
Layer for KiCAD S-Exp output. Defaults to auto-detect layers from SVG layer/top-level group IDs. If given, SVG
|
||||
groups and layers are completely ignored and everything is simply vectorized into this layer, though you cna still
|
||||
use ``-g`` for group selection.
|
||||
|
||||
``-a, --preserve-aspect-ratio``
|
||||
Bitmap mode only: Preserve aspect ratio of image. Allowed values are meet, slice. Can also parse full SVG
|
||||
preserveAspectRatio syntax.
|
||||
|
||||
``--no-usvg``
|
||||
Do not preprocess input using usvg (do not use unless you know *exactly* what you're doing)
|
||||
|
||||
``--usvg-dpi``
|
||||
Passed through to usvg's --dpi, in case the input file has different ideas of DPI than usvg has.
|
||||
|
||||
``--scale``
|
||||
Scale input svg lengths by this factor (-o gerber only).
|
||||
|
||||
``-e, --exclude-groups``
|
||||
Comma-separated list of group IDs to exclude from export. Takes precedence over --only-groups.
|
||||
|
||||
.. _vectorization:
|
||||
|
||||
Gerbolyze image vectorization
|
||||
-----------------------------
|
||||
|
||||
Gerbolyze has two built-in strategies to translate pixel images into vector images. One is its built-in halftone
|
||||
processor that tries to approximate grayscale. The other is its built-in binary vectorizer that traces contours in
|
||||
black-and-white images. Below are examples for the four options.
|
||||
|
||||
The vectorizers can be used in isolation through ``svg-flatten`` with either an SVG input that contains an image or a
|
||||
PNG/JPG input.
|
||||
|
||||
The vectorizer can be controlled globally using the ``--vectorizer`` flag in both ``gerbolyze`` and ``svg-flatten``. It
|
||||
can also be set on a per-image basis in both using ``--vectorizer-map [image svg id]=[option]["," ...]``.
|
||||
|
||||
.. for f in vec_*.png; convert -background white -gravity center $f -resize 500x500 -extent 500x500 (basename -s .png $f)-square.png; end
|
||||
.. for vec in hexgrid square poisson contours; convert vec_"$vec"_whole-square.png vec_"$vec"_detail-square.png -background transparent -splice 25x0+0+0 +append -chop 25x0+0+0 vec_"$vec"_composited.png; end
|
||||
|
||||
``--vectorizer poisson-disc`` (the default)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: pics/vec_poisson_composited.png
|
||||
:width: 800px
|
||||
|
||||
``--vectorizer hex-grid``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: pics/vec_hexgrid_composited.png
|
||||
:width: 800px
|
||||
|
||||
``--vectorizer square-grid``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: pics/vec_square_composited.png
|
||||
:width: 800px
|
||||
|
||||
``--vectorizer binary-contours``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: pics/vec_contours_composited.png
|
||||
:width: 800px
|
||||
|
||||
The binary contours vectorizer requires a black-and-white binary input image. As you can see, like every bitmap tracer
|
||||
it will produce some artifacts. For artistic input this is usually not too bad as long as the input data is
|
||||
high-resolution. Antialiased edges in the input image are not only OK, they may even help with an accurate
|
||||
vectorization.
|
||||
|
||||
GIMP halftone preprocessing guide
|
||||
---------------------------------
|
||||
|
||||
Gerbolyze has its own built-in halftone processor, but you can also use the high-quality "newsprint" filter built into
|
||||
GIMP_ instead if you like. This section will guide you through this. The PNG you get out of this can then be fed into
|
||||
gerbolyze using ``--vectorizer binary-contours``.
|
||||
|
||||
1 Import your desired artwork
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Though anime or manga pictures are highly recommended, you can use any image including photographs. Be careful to select
|
||||
a picture with comparatively low detail that remains recognizable at very low resolution. While working on a screen this
|
||||
is hard to vizualize, but the grain resulting from the low resolution of a PCB's silkscreen is quite coarse.
|
||||
|
||||
.. image:: screenshots/02import02.png
|
||||
:width: 800px
|
||||
|
||||
2 Convert the image to grayscale
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: screenshots/06grayscale.png
|
||||
:width: 800px
|
||||
|
||||
3 Fine-tune the image's contrast
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To look well on the PCB, contrast is critical. If your source image is in color, you may have lost some contrast during
|
||||
grayscale conversion. Now is the time to retouch that using the GIMP's color curve tool.
|
||||
|
||||
When using the GIMP's newsprint filter, bright grays close to white and dark grays close to black will cause very small
|
||||
dots that might be beyond your PCB manufacturer's maximum resolution. To control this case, add small steps at the ends
|
||||
of the grayscale value curve as shown (exaggerated) in the picture below. These steps saturate very bright grays to
|
||||
white and very dark grays to black while preserving the values in the middle.
|
||||
|
||||
.. image:: screenshots/08curve_cut.png
|
||||
:width: 800px
|
||||
|
||||
4 Retouch details
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Therer might be small details that don't look right yet, such as the image's background color or small highlights that
|
||||
merge into the background now. You can manually change the color of any detail now using the GIMP's flood-fill tool.
|
||||
|
||||
If you don't want the image's background to show up on the final PCB at all, just make it black.
|
||||
|
||||
Particularly on low-resolution source images it may make sense to apply a blur with a radius similar to the following
|
||||
newsprint filter's cell size (10px) to smooth out the dot pattern generated by the newsprint filter.
|
||||
|
||||
.. image:: screenshots/09retouch.png
|
||||
:width: 800px
|
||||
|
||||
In the following example, I retouched the highlights in the hair of the character in the picture to make them completely
|
||||
white instead of light-gray, so they still stand out nicely in the finished picture.
|
||||
|
||||
.. image:: screenshots/10retouched.png
|
||||
:width: 800px
|
||||
|
||||
5 Run the newsprint filter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now, run the GIMP's newsprint filter, under filters, distorts, newsprint.
|
||||
|
||||
The first important settings is the spot size, which should be larger than your PCB's minimum detail size (about 10px
|
||||
with ``gerbolyze render`` default settings for good-quality silkscreen). In general the cheap and fast standard option of chinese PCB houses will require a larger detail size, but when you order specialty options like large size, 4-layer or non-green color along with a longer turnaround time you'll get much better-quality silk screen.
|
||||
|
||||
The second important setting is oversampling, which should be set to four or slightly higher. This improves the result
|
||||
of the edge reconstruction of ``gerbolyze vectorize``.
|
||||
|
||||
.. image:: screenshots/11newsprint.png
|
||||
:width: 800px
|
||||
|
||||
The following are examples on the detail resulting from the newsprint filter.
|
||||
|
||||
.. image:: screenshots/12newsprint.png
|
||||
:width: 800px
|
||||
|
||||
6 Export the image for use with ``gerbolyze vectorize``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Simply export the image as a PNG file. Below are some pictures of the output ``gerbolyze vectorize`` produced for this
|
||||
example.
|
||||
|
||||
.. image:: screenshots/14result_cut.png
|
||||
:width: 800px
|
||||
|
||||
.. image:: screenshots/15result_cut.png
|
||||
:width: 800px
|
||||
|
||||
Manufacturing Considerations
|
||||
----------------------------
|
||||
|
||||
The main consideration when designing artwork for PCB processes is the processes' trace/space design rule. The two
|
||||
things you can do here is one, to be creative with graphical parts of the design and avoid extremely narrow lines,
|
||||
wedges or other thin features that will not come out well. Number two is to keep detail in raster images several times
|
||||
larger than the manufacturing processes native capability. For example, to target a trace/space design rule of 100 µm,
|
||||
the smallest detail in embedded raster graphics should not be much below 1mm.
|
||||
|
||||
Gerbolyze's halftone vectorizers have built-in support for trace/space design rules. While they can still produce small
|
||||
artifacts that violate these rules, their output should be close enough to satifsy board houses and close enough for the
|
||||
result to look good. The way gerbolyze does this is to clip the halftone cell's values to zero whenevery they get too
|
||||
small, and to forcefully split or merge two neighboring cells when they get too close. While this process introduces
|
||||
slight steps at the top and bottom of grayscale response, for most inputs these are not noticeable.
|
||||
|
||||
On the other hand, for SVG vector elements as well as for traced raster images, Gerbolyze cannot help with these design
|
||||
rules. There is no heuristic that would allow Gerbolyze to non-destructively "fix" a design here, so all that's on the
|
||||
roadmap here is to eventually include a gerber-level design rule checker.
|
||||
|
||||
As far as board houses go, I have made good experiences with the popular Chinese board houses. In my experience, JLC
|
||||
will just produce whatever you send them with little fucks being given about design rule adherence or validity of the
|
||||
input gerbers. This is great if you just want artistic circuit boards without much of a hassle, and you don't care if
|
||||
they come out exactly as you imagined. The worst I've had happen was when an older version of gerbolyze generated
|
||||
polygons with holes assuming standard fill-rule processing. The in the board house's online gerber viewer things looked
|
||||
fine, and neither did they complain during file review. However, the resulting boards looked completely wrong because
|
||||
all the dark halftones were missing.
|
||||
|
||||
PCBWay on the other hand has a much more rigurous file review process. They <em>will</em> complain when you throw
|
||||
illegal garbage gerbers at them, and they will helpfully guide you through your design rule violations. In this way you
|
||||
get much more of a professional service from them and for designs that have to be functional their higher level of
|
||||
scrutiny definitely is a good thing. For the design you saw in the first picture in this article, I ended up begging
|
||||
them to just plot my files if it doesn't physically break their machines and to their credit, while they seemed unhappy
|
||||
about it they did it and the result looks absolutely stunning.
|
||||
|
||||
PCBWay is a bit more expensive on their lowest-end offering than JLC, but I found that for anything else (large boards,
|
||||
multi-layer, gold plating etc.) their prices match. PCBWay offers a much broader range of manufacturing options such as
|
||||
flexible circuit boards, multi-layer boards, thick or thin substrates and high-temperature substrates.
|
||||
|
||||
When in doubt about how your design is going to come out on the board, do not hesitate to contact your board house. Most
|
||||
of the end customer-facing online PCB services have a number of different factories that do a number of different
|
||||
fabrication processes for them depending on order parameters. Places like PCBWay have exceptional quality control and
|
||||
good customer service, but that is mostly focused on the technical aspects of the PCB. If you rely on visual aspects
|
||||
like silkscreen uniformity or solder mask color that is a strong no concern to everyone else in the electronics
|
||||
industry, you may find significant variations between manufacturers or even between orders with the same manufacturer
|
||||
and you may encounter challenges communicating your requirements.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
SVG raster features
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Currently, SVG masks and filters are not supported. Though SVG is marketed as a "vector graphics format", these two
|
||||
features are really raster primitives that all SVG viewers perform at the pixel level after rasterization. Since
|
||||
supporting these would likely not end up looking like what you want, it is not a planned feature. If you need masks or
|
||||
filters, simply export the relevant parts of the SVG as a PNG then include that in your template.
|
||||
|
||||
Gerber pass-through
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since gerbolyze has to composite your input gerbers with its own output, it has to fully parse and re-serialize them.
|
||||
gerbolyze gerbonara_ for all its gerber parsing needs. Thus, gerbonara will interpret your gerbers and output will be in
|
||||
gerbonara's gerber "dialect". If you find a corner case where this does not work and the output looks wrong, please file
|
||||
a bug report with an example file on the gerbonara_ bug tracker. *Always* check the output files for errors before
|
||||
submitting them to production.
|
||||
|
||||
Gerbolyze is provided without any warranty, but still please open an issue or `send me an email
|
||||
<mailto:gerbolyze@jaseg.de>`__ if you find any errors or inconsistencies.
|
||||
|
||||
Trace/Space design rule adherence
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While the grayscale halftone vectorizers do a reasonable job adhering to a given trace/space design rule, they can still
|
||||
produce small parts of output that violate it. For the contour vectorizer as well as for all SVG primitives, you are
|
||||
responsible for adhering to design rules yourself as there is no algorithm that gerboyze could use to "fix" its input.
|
||||
|
||||
A design rule checker is planned as a future addition to gerbolyze, but is not yet part of it. If in doubt, talk to your
|
||||
fab and consider doing a test run of your design before ordering assembled boards ;)
|
||||
|
||||
Gallery
|
||||
-------
|
||||
|
||||
.. image:: pics/sample3.jpg
|
||||
:width: 400px
|
||||
|
||||
For a demonstration of ``gerbolyze convert``, check out the `Gerbolyze Protoboard Index`_, where you can download gerber
|
||||
files for over 7.000 SMD and THT protoboard layouts.
|
||||
|
||||
Licensing
|
||||
---------
|
||||
|
||||
This tool is licensed under the rather radical AGPLv3 license. Briefly, this means that you have to provide users of a
|
||||
webapp using this tool in the backend with this tool's source.
|
||||
|
||||
I get that some people have issues with the AGPL. In case this license prevents you from using this software, please
|
||||
send me `an email <mailto:agpl.sucks@jaseg.de>`__ and I can grant you an exception. I want this software to be useful to as
|
||||
many people as possible and I wouldn't want the license to be a hurdle to anyone. OTOH I see a danger of some cheap
|
||||
board house just integrating a fork into their webpage without providing their changes back upstream, and I want to
|
||||
avoid that so the default license is still AGPL.
|
||||
|
||||
.. _usvg: https://github.com/RazrFalcon/resvg
|
||||
.. _Inkscape: https://inkscape.org/
|
||||
.. _pcb-tools: https://github.com/curtacircuitos/pcb-tools
|
||||
.. _pcb-tools-extension: https://github.com/opiopan/pcb-tools-extension
|
||||
.. _GIMP: https://gimp.org/
|
||||
.. _gerbonara: https://gitlab.com/gerbolyze/gerbonara
|
||||
.. _`Gerbolyze Protoboard Index`: https://dyna.kokoroyukuma.de/protos/
|
||||
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
title: "Gerbolyze"
|
||||
external_links:
|
||||
- name: Sources
|
||||
url: "https://git.jaseg.de/gerbolyze.git"
|
||||
- name: Issues
|
||||
url: "https://github.com/jaseg/gerbolyze/issues"
|
||||
- name: Docs
|
||||
url: "https://gerbolyze.gitlab.io/gerbolyze"
|
||||
- name: PyPI
|
||||
url: "https://pypi.org/project/gerbolyze"
|
||||
summary: >
|
||||
Gerbolyze is a tool that allows the modification of Gerber PCB artwork with a vector graphics editor like Inkscape.
|
||||
Gerbolyze directly converts between SVG and Gerber, and accurately reproduces details that other tools can not.
|
||||
---
|
||||
|
||||
.. include:: content/projects/gerbolyze/README.rst
|
||||
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 442 KiB |
|
Before Width: | Height: | Size: 509 KiB |
|
Before Width: | Height: | Size: 729 KiB |
|
Before Width: | Height: | Size: 504 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 362 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 380 KiB |
|
Before Width: | Height: | Size: 365 KiB |
|
Before Width: | Height: | Size: 307 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 568 KiB |
|
Before Width: | Height: | Size: 503 KiB |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 114 KiB |