#!/usr/bin/env python3 """Writes the two illustrations on the home page (templates/illustrations/sweep.html and sky.html, inline SVG for Zola to include). They are drawings of the LoRa Scanner's Sweep view and the GNSS App's sky view on the Cardputer, not screenshots, and the page says so. Every colour is one of the device's own (RGB332). The shapes come from a deterministic generator, so running this again gives the same files: python3 site/tools/make_illustrations.py """ import math import os HERE = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(HERE, "..", "templates", "illustrations") BLACK, SLATE, DIM, CYAN, MAG, WHITE, LABEL, ONFILL = "#000000", "#494955", "#6d6d55", "#00dbff", "#ff00ff", "#ffffff", "#b6dbff", "#000055" ORANGE = "#ff9200" def notch(x, y, w, h, n): """A rectangle with its corners cut in two steps of n/2 (the site's .n-sm, .n-md and .n-lg).""" a, b = n, n // 2 pts = [(0, a), (b, a), (b, b), (a, b), (a, 0), (w - a, 0), (w - a, b), (w - b, b), (w - b, a), (w, a), (w, h - a), (w - b, h - a), (w - b, h - b), (w - a, h - b), (w - a, h), (a, h), (a, h - b), (b, h - b), (b, h - a), (0, h - a)] return " ".join(f"{x + px:g},{y + py:g}" for px, py in pts) def rnd(n): x = math.sin(n * 127.1 + 311.7) * 43758.5453 return x - math.floor(x) def jsround(v): return math.floor(v + 0.5) SOURCES = [(5, 0.72, 0.9), (13.5, 0.58, 1.2), (22, 0.95, 1.0), (27, 0.42, 0.8)] def strength(i, r): s = 0.05 + 0.1 * rnd(i * 7 + r * 13) for k, (c, a, w) in enumerate(SOURCES): if rnd(k * 10 + r) > 0.2: g = math.exp(-((i - c) ** 2) / (2 * w * w)) s = max(s, a * g * (0.6 + 0.4 * rnd(i * 3 + r * 5 + k))) return min(s, 1) def frame(parts, label): keys_y = 306 kw = (480 - 13 * 4) / 14 keys = [] for row in range(4): for col in range(14): x, y = 20 + col * (kw + 4), keys_y + row * 26 keys.append(f'') return ( f'' f'' f'' + "".join(parts) + "".join(keys) + "\n" ) def label(x, y, text, fill, anchor="start", weight=400, spacing=0.08): return (f'{text}') def sweep(): p = [] # Tabs and Status Bar letters p.append(label(40, 44, "SNIFFER", LABEL, weight=500)) p.append(f'') p.append(label(112, 44, "SWEEP", ONFILL, weight=500)) p.append(label(488, 44, "CAP", LABEL, "end", 500)) p.append(label(457, 44, "SW", CYAN, "end", 500)) p.append(label(434, 44, "L", CYAN, "end", 500)) # Bars with peak hold p.append(f'') colw = (448 - 31 * 2) / 32 for i in range(32): s = strength(i, 0) h = max(4, jsround(s * 100 / 4) * 4) gap = jsround(rnd(i * 11 + 3) * 3) * 4 gap = min(gap, 112 - h - 4 if 112 - h - 4 > 0 else 0) x = 36 + i * (colw + 2) p.append(f'') p.append(f'') # Waterfall, newest row on top p.append(f'') for r in range(6): for i in range(32): s = strength(i, r + 1) if s < 0.3: continue color = CYAN if s < 0.6 else MAG if s < 0.85 else WHITE p.append(f'') p.append(label(32, 276, "863", LABEL, spacing=0)) p.append(label(260, 276, "866.5", LABEL, "middle", spacing=0)) p.append(label(488, 276, "870 MHz", LABEL, "end", spacing=0)) return frame(p, "Illustration of the Cardputer showing the LoRa Scanner's Sweep view: bars of signal strength from 863 to 870 MHz with peak marks, and a waterfall under them") def sky(): p = [] p.append(label(32, 44, "GNSS", WHITE, weight=500)) p.append(f'') p.append(label(457.5, 44, "SKY", ONFILL, weight=500)) p.append(label(445.5, 44, "POSITION", LABEL, "end", 500)) p.append(f'') ox, oy, CELL, NC, CC = 48, 86, 5, 33, 16 for y in range(NC): for x in range(NC): dx, dy = x - CC, y - CC d = math.sqrt(dx * dx + dy * dy) if any(abs(d - R) <= 0.5 for R in (15, 10, 5)): color = DIM elif (dx == 0 or dy == 0) and d < 15 and abs(dx + dy) % 2 == 0 and d > 0: color = SLATE else: continue p.append(f'') cols = [CYAN, MAG, ORANGE, WHITE] sats = [(20, 70, 0.8, 0), (75, 40, 0.6, 1), (130, 25, 0.35, 2), (160, 60, 0.9, 0), (210, 15, 0.25, 3), (250, 50, 0.7, 1), (290, 30, 0.5, 0), (320, 75, 0.85, 2), (345, 35, 0.55, 1)] for az, el, snr, ci in sats: r = (90 - el) / 90 * 15 a = az * math.pi / 180 cx, cy = jsround(CC + r * math.sin(a)), jsround(CC - r * math.cos(a)) p.append(f'') p.append(f'') p.append(label(ox + 82.5, 80, "N", MAG, "middle", 500, 0)) p.append(label(ox + 82.5, 265, "S", LABEL, "middle", 500, 0)) p.append(label(ox - 14, 172, "W", LABEL, "start", 500, 0)) p.append(label(ox + 165 + 14, 172, "E", LABEL, "end", 500, 0)) # Signal strength of each satellite p.append(label(237, 107, "SNR", LABEL, weight=500)) bw = (235 - 8 * 6) / 9 for i, (az, el, snr, ci) in enumerate(sats): h = max(8, jsround(snr * 124 / 4) * 4) p.append(f'') return frame(p, "Illustration of the GNSS App's sky view: the satellites' positions in a circle, and the signal strength of each as a bar") if __name__ == "__main__": os.makedirs(OUT, exist_ok=True) for name, svg in (("sweep.html", sweep()), ("sky.html", sky())): with open(os.path.join(OUT, name), "w") as f: f.write(svg) print(name, len(svg), "bytes")