Initial scaffold: NixOS + SvelteKit dashboard + Python renderer
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
# Nix
|
||||
result
|
||||
result-*
|
||||
.direnv/
|
||||
|
||||
# Node / SvelteKit
|
||||
dashboard/node_modules/
|
||||
dashboard/.svelte-kit/
|
||||
dashboard/build/
|
||||
dashboard/.env
|
||||
dashboard/.env.*
|
||||
!dashboard/.env.example
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.so
|
||||
.venv/
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
|
||||
# Secrets (handled via sops-nix / LoadCredential, never plaintext in repo)
|
||||
*.secret
|
||||
secrets.local.*
|
||||
@@ -0,0 +1,116 @@
|
||||
# eink
|
||||
|
||||
NixOS-managed Raspberry Pi 4 driving a Waveshare 12.48" e-paper panel as a
|
||||
calendar / day-planner. CalDAV data comes from the household Stalwart at
|
||||
`mail.rubenhensen.nl`. The Pi pulls every commit on `main` via
|
||||
[comin](https://github.com/nlewo/comin) — never touch the SD card after the
|
||||
first flash.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Raspberry Pi 4 (NixOS aarch64, hostname: dayplanner) │
|
||||
│ │
|
||||
│ systemd: eink-dashboard.service systemd: eink-renderer.service │
|
||||
│ ┌──────────────────────────┐ ┌──────────────────────────────┐ │
|
||||
│ │ SvelteKit (Node adapter) │◄──────│ Playwright → Pillow dither │ │
|
||||
│ │ :3000 │ HTTP │ → Waveshare 12.48" driver │ │
|
||||
│ │ fetches CalDAV every load│ │ (every REFRESH_SECONDS) │ │
|
||||
│ └────────────┬─────────────┘ └───────────────┬──────────────┘ │
|
||||
│ │ │ SPI │
|
||||
│ ▼ ▼ │
|
||||
│ mail.rubenhensen.nl Waveshare 12.48" HAT │
|
||||
│ (Stalwart CalDAV) (1304×984, 4 sub-panels) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Deploy path: laptop → git push → Gitea → comin (60s poll) → Pi
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
eink/
|
||||
├── flake.nix # nixpkgs + comin → nixosConfigurations.dayplanner
|
||||
├── hosts/dayplanner/ # this Pi's NixOS config (users, comin, hardware)
|
||||
├── modules/ # eink-dashboard.nix + eink-renderer.nix
|
||||
├── dashboard/ # SvelteKit app rendered headlessly at 1304×984
|
||||
└── renderer/ # Python loop: screenshot → dither → push
|
||||
```
|
||||
|
||||
## One-time bootstrap
|
||||
|
||||
1. **Flash NixOS aarch64 SD image** onto a card and boot the Pi.
|
||||
- Image: <https://hydra.nixos.org/job/nixos/release-25.11/nixos.sd_image.aarch64-linux/latest>
|
||||
- On first boot, set a `nixos` user password via the serial console or
|
||||
monitor + keyboard. Enable SSH manually (`sudo systemctl start sshd`)
|
||||
and note the Pi's IP.
|
||||
2. **Add your SSH public key** to `hosts/dayplanner/default.nix` under
|
||||
`users.users.ruben.openssh.authorizedKeys.keys`.
|
||||
3. **Push this repo to a Git remote** the Pi can reach (e.g. your Gitea).
|
||||
Update `services.comin.remotes[0].url` in `hosts/dayplanner/default.nix`
|
||||
if the URL differs.
|
||||
4. **First synchronous deploy from your laptop:**
|
||||
```sh
|
||||
nix run nixpkgs#nixos-rebuild -- switch \
|
||||
--flake .#dayplanner \
|
||||
--target-host nixos@<pi-ip> \
|
||||
--build-host nixos@<pi-ip> \
|
||||
--use-remote-sudo
|
||||
```
|
||||
(Building on the Pi itself avoids needing a cross-compile setup on your
|
||||
Mac. Slow but one-time.)
|
||||
5. **Drop the CalDAV password** on the Pi:
|
||||
```sh
|
||||
ssh ruben@<pi-ip> 'sudo install -d -m 700 /var/lib/eink/secrets'
|
||||
ssh ruben@<pi-ip> 'sudo tee /var/lib/eink/secrets/caldav-password >/dev/null' \
|
||||
<<< 'your-caldav-app-password'
|
||||
ssh ruben@<pi-ip> 'sudo chmod 600 /var/lib/eink/secrets/caldav-password'
|
||||
```
|
||||
(Long-term replace this with [sops-nix](https://github.com/Mic92/sops-nix)
|
||||
so the secret lives encrypted in this repo.)
|
||||
6. **Vendor the Waveshare driver** (see `renderer/waveshare/README.md`).
|
||||
|
||||
After this, the steady-state workflow is just:
|
||||
|
||||
```sh
|
||||
git push origin main # comin picks it up within ~60s and switches the Pi
|
||||
```
|
||||
|
||||
## Iterating on the dashboard locally
|
||||
|
||||
You don't need the Pi to develop the UI — just open the SvelteKit dev
|
||||
server in a browser sized to 1304×984:
|
||||
|
||||
```sh
|
||||
cd dashboard
|
||||
npm install
|
||||
CALDAV_PASSWORD=... npm run dev -- --host
|
||||
# open http://localhost:5173 in a browser with viewport 1304×984
|
||||
```
|
||||
|
||||
Chrome devtools → "Toggle device toolbar" → set to 1304×984 to preview
|
||||
exactly what the panel will see.
|
||||
|
||||
## Refresh cadence & panel wear
|
||||
|
||||
`eink.renderer.refreshSeconds = 300` (5 min) is the default. The 12.48"
|
||||
panel has **no partial refresh** — every update is a full flash that
|
||||
takes ~8s and consumes some panel life. Below 180 s is abusive; 300-600
|
||||
s is comfortable. Consider scheduling a nightly invert to spread wear.
|
||||
|
||||
## Panel variant
|
||||
|
||||
Set `eink.renderer.panel = "bwr"` in `hosts/dayplanner/default.nix` if
|
||||
you have the **12.48inch e-Paper Module (B)** (B/W/Red) instead of the
|
||||
plain B/W version. The dashboard's red channel is whatever CSS color
|
||||
matches `rgb(>180, <100, <100)` — see `renderer/render.py:to_bwr`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Comin not pulling:** `journalctl -u comin.service -f` on the Pi.
|
||||
- **Renderer dry-running:** check `/tmp/eink-last.png` — if it exists,
|
||||
the screenshot+dither pipeline works and the Waveshare driver is the
|
||||
remaining piece (see `renderer/waveshare/README.md`).
|
||||
- **Dashboard 500:** `journalctl -u eink-dashboard.service -f`. Most
|
||||
common cause: empty `/var/lib/eink/secrets/caldav-password`.
|
||||
Generated
+1965
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "eink-dashboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.2.0",
|
||||
"@sveltejs/kit": "^2.8.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.5.0",
|
||||
"vite": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tsdav": "^2.1.0",
|
||||
"ical.js": "^2.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/* E-paper-friendly base styles.
|
||||
Constraints:
|
||||
- Hard 1-bit palette: pure black on pure white. No grays (they dither badly).
|
||||
- No thin lines below 2px — Floyd-Steinberg dither breaks them up.
|
||||
- No anti-aliased small fonts. Stick to bold weights at >= 18px.
|
||||
- No soft shadows; use crisp 2-4px solid borders for separation. */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 1304px;
|
||||
height: 984px;
|
||||
background: #ffffff;
|
||||
color: #000000;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
-webkit-font-smoothing: none;
|
||||
-moz-osx-font-smoothing: unset;
|
||||
text-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.border-thick {
|
||||
border: 3px solid #000000;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=1304, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
{@render children()}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createDAVClient } from 'tsdav';
|
||||
import ICAL from 'ical.js';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
const CALDAV_URL = process.env.CALDAV_URL ?? 'https://mail.rubenhensen.nl/dav/';
|
||||
const CALDAV_USER = process.env.CALDAV_USER ?? 'ruben@rubenhensen.nl';
|
||||
|
||||
// systemd LoadCredential drops the password at $CREDENTIALS_DIRECTORY/caldav-password.
|
||||
// Falls back to env for dev.
|
||||
function readCalDavPassword(): string {
|
||||
const dir = process.env.CREDENTIALS_DIRECTORY;
|
||||
if (dir) {
|
||||
try {
|
||||
return readFileSync(`${dir}/caldav-password`, 'utf-8').trim();
|
||||
} catch {}
|
||||
}
|
||||
return process.env.CALDAV_PASSWORD ?? '';
|
||||
}
|
||||
|
||||
interface Event {
|
||||
start: Date;
|
||||
end: Date;
|
||||
summary: string;
|
||||
allDay: boolean;
|
||||
}
|
||||
|
||||
async function fetchTodayEvents(): Promise<Event[]> {
|
||||
const password = readCalDavPassword();
|
||||
if (!password) return [];
|
||||
|
||||
const client = await createDAVClient({
|
||||
serverUrl: CALDAV_URL,
|
||||
credentials: { username: CALDAV_USER, password },
|
||||
authMethod: 'Basic',
|
||||
defaultAccountType: 'caldav'
|
||||
});
|
||||
|
||||
const calendars = await client.fetchCalendars();
|
||||
const start = new Date();
|
||||
start.setHours(0, 0, 0, 0);
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 1);
|
||||
|
||||
// Stalwart returns "207 Multi-Status / 404 No resources found" for empty
|
||||
// time-range queries, which tsdav rejects as an error. Fetch everything
|
||||
// and filter client-side — the calendar is small enough that this is fine.
|
||||
const windowStart = ICAL.Time.fromJSDate(start, true);
|
||||
const windowEnd = ICAL.Time.fromJSDate(end, true);
|
||||
|
||||
const events: Event[] = [];
|
||||
for (const cal of calendars) {
|
||||
const objects = await client.fetchCalendarObjects({ calendar: cal });
|
||||
for (const obj of objects) {
|
||||
if (!obj.data) continue;
|
||||
const jcal = ICAL.parse(obj.data);
|
||||
const comp = new ICAL.Component(jcal);
|
||||
|
||||
for (const vevent of comp.getAllSubcomponents('vevent')) {
|
||||
const ev = new ICAL.Event(vevent);
|
||||
if (ev.isRecurrenceException()) continue;
|
||||
|
||||
const push = (s: ICAL.Time, e: ICAL.Time) => {
|
||||
events.push({
|
||||
start: s.toJSDate(),
|
||||
end: e.toJSDate(),
|
||||
summary: ev.summary,
|
||||
allDay: s.isDate
|
||||
});
|
||||
};
|
||||
|
||||
if (!ev.isRecurring()) {
|
||||
if (ev.endDate.compare(windowStart) <= 0) continue;
|
||||
if (ev.startDate.compare(windowEnd) >= 0) continue;
|
||||
push(ev.startDate, ev.endDate);
|
||||
continue;
|
||||
}
|
||||
|
||||
const iter = ev.iterator();
|
||||
let next: ICAL.Time | null;
|
||||
while ((next = iter.next())) {
|
||||
if (next.compare(windowEnd) >= 0) break;
|
||||
const occ = ev.getOccurrenceDetails(next);
|
||||
if (occ.endDate.compare(windowStart) <= 0) continue;
|
||||
push(occ.startDate, occ.endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return events.sort((a, b) => a.start.getTime() - b.start.getTime());
|
||||
}
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
let events: Event[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
events = await fetchTodayEvents();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
error = e.message;
|
||||
} else if (typeof e === 'object' && e !== null) {
|
||||
try { error = JSON.stringify(e); } catch { error = String(e); }
|
||||
} else {
|
||||
error = String(e);
|
||||
}
|
||||
}
|
||||
return {
|
||||
events,
|
||||
error,
|
||||
renderedAt: new Date().toISOString()
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
<script lang="ts">
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
function fmtTime(d: Date | string): string {
|
||||
const date = typeof d === 'string' ? new Date(d) : d;
|
||||
return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function fmtDate(d: Date | string): string {
|
||||
const date = typeof d === 'string' ? new Date(d) : d;
|
||||
return date.toLocaleDateString('en-GB', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long'
|
||||
});
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
</script>
|
||||
|
||||
<main class="dashboard">
|
||||
<header class="header">
|
||||
<h1>{fmtDate(today)}</h1>
|
||||
<span class="rendered">refreshed {fmtTime(data.renderedAt)}</span>
|
||||
</header>
|
||||
|
||||
<section class="events">
|
||||
<h2>Today</h2>
|
||||
{#if data.error}
|
||||
<p class="error">CalDAV error: {data.error}</p>
|
||||
{:else if data.events.length === 0}
|
||||
<p class="empty">Nothing scheduled.</p>
|
||||
{:else}
|
||||
<ul>
|
||||
{#each data.events as ev (`${ev.start}-${ev.summary}`)}
|
||||
<li class="event">
|
||||
<span class="time">
|
||||
{#if ev.allDay}all day{:else}{fmtTime(ev.start)}–{fmtTime(ev.end)}{/if}
|
||||
</span>
|
||||
<span class="summary">{ev.summary}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.dashboard {
|
||||
width: 1304px;
|
||||
height: 984px;
|
||||
padding: 40px 56px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
border-bottom: 4px solid #000;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 56px;
|
||||
}
|
||||
|
||||
.rendered {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.events h2 {
|
||||
font-size: 36px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.events ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.event {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
gap: 24px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 2px solid #000;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.empty, .error {
|
||||
font-size: 28px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
export default {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
description = "E-paper dayplanner — NixOS config for Pi 4 driving Waveshare 12.48\" e-paper";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
|
||||
comin = {
|
||||
url = "github:nlewo/comin";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, comin, ... }: {
|
||||
nixosConfigurations.dayplanner = nixpkgs.lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
specialArgs = { inherit self; };
|
||||
modules = [
|
||||
comin.nixosModules.comin
|
||||
./hosts/dayplanner
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{ config, lib, pkgs, self, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./hardware.nix
|
||||
../../modules/eink-dashboard.nix
|
||||
../../modules/eink-renderer.nix
|
||||
];
|
||||
|
||||
networking.hostName = "dayplanner";
|
||||
networking.domain = "local";
|
||||
|
||||
# WiFi or wired. Default to DHCP — set static IP via your router if you want predictability.
|
||||
networking.networkmanager.enable = true;
|
||||
networking.firewall.enable = true;
|
||||
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||
|
||||
time.timeZone = "Europe/Amsterdam";
|
||||
|
||||
users.users.ruben = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" "gpio" "spi" "dialout" ];
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEiY+6AEuKQHyt4Mpg/IbyoIkhg76u3xFPdXGpTsmwxT ruben.hensen@protonmail.com"
|
||||
];
|
||||
};
|
||||
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings.PasswordAuthentication = false;
|
||||
settings.PermitRootLogin = "no";
|
||||
};
|
||||
|
||||
# Pull-deploy: every commit on `main` lands on the Pi within ~60 s.
|
||||
# The remote URL must be reachable from the Pi (push to GitHub/Gitea).
|
||||
services.comin = {
|
||||
enable = true;
|
||||
remotes = [{
|
||||
name = "origin";
|
||||
url = "https://git.rubenhensen.nl/ruben/eink.git";
|
||||
branches.main.name = "main";
|
||||
}];
|
||||
hostname = "dayplanner";
|
||||
};
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
options = "--delete-older-than 14d";
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
git
|
||||
htop
|
||||
vim
|
||||
];
|
||||
|
||||
eink.dashboard.enable = true;
|
||||
eink.dashboard.caldavUser = "ruben";
|
||||
eink.renderer.enable = true;
|
||||
eink.renderer.dashboardUrl = "http://127.0.0.1:3000";
|
||||
|
||||
system.stateVersion = "25.11";
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
# Pi 4 aarch64. Boot from the official NixOS sd-image; we don't generate
|
||||
# the image from this flake — flash the upstream aarch64 sd-image once,
|
||||
# then `nixos-rebuild switch --flake .#dayplanner --target-host` swaps
|
||||
# the running system over and comin takes over from there.
|
||||
imports = [
|
||||
"${modulesPath}/installer/sd-card/sd-image-aarch64.nix"
|
||||
];
|
||||
|
||||
# Drop the default channel; we deploy from the flake.
|
||||
sdImage.compressImage = false;
|
||||
|
||||
boot.loader.grub.enable = false;
|
||||
boot.loader.generic-extlinux-compatible.enable = true;
|
||||
|
||||
# The Waveshare 12.48" HAT is driven entirely from userspace Python via
|
||||
# /dev/spidev*. We only need SPI enabled in the device tree and the
|
||||
# gpio/spi groups available to the renderer user.
|
||||
boot.kernelParams = [ "console=serial0,115200" "console=tty1" ];
|
||||
|
||||
# Enable SPI0 (CE0/CE1) and the extra GPIOs the HAT needs.
|
||||
# See: https://www.waveshare.com/wiki/12.48inch_e-Paper_Module
|
||||
hardware.deviceTree = {
|
||||
enable = true;
|
||||
overlays = [
|
||||
{
|
||||
name = "spi-on";
|
||||
dtsText = ''
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
/ {
|
||||
compatible = "brcm,bcm2711";
|
||||
fragment@0 {
|
||||
target = <&spi0>;
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.spi = {};
|
||||
users.groups.gpio = {};
|
||||
|
||||
services.udev.extraRules = ''
|
||||
SUBSYSTEM=="spidev", GROUP="spi", MODE="0660"
|
||||
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
{ config, lib, pkgs, self, ... }:
|
||||
|
||||
let
|
||||
cfg = config.eink.dashboard;
|
||||
|
||||
dashboardSrc = self + "/dashboard";
|
||||
|
||||
dashboardBuild = pkgs.buildNpmPackage {
|
||||
pname = "eink-dashboard";
|
||||
version = "0.1.0";
|
||||
src = dashboardSrc;
|
||||
|
||||
# Re-generate this hash with:
|
||||
# nix run nixpkgs#prefetch-npm-deps -- dashboard/package-lock.json
|
||||
npmDepsHash = "sha256-UticV62t7/CYZ1nVZPNxUZn38WhQ4mHmYT3V90D01OU=";
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
npm run build
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
cp -r build package.json $out/
|
||||
cp -r node_modules $out/
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.eink.dashboard = {
|
||||
enable = lib.mkEnableOption "SvelteKit e-paper dashboard";
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3000;
|
||||
};
|
||||
|
||||
caldavUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://mail.rubenhensen.nl/dav/cal/";
|
||||
description = "Base URL of the CalDAV server. Stalwart serves CalDAV at /dav/cal/, distinct from CardDAV at /dav/card/.";
|
||||
};
|
||||
|
||||
caldavUser = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ruben@rubenhensen.nl";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.eink-dashboard = {
|
||||
description = "E-paper dashboard (SvelteKit)";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment = {
|
||||
HOST = "127.0.0.1";
|
||||
PORT = toString cfg.port;
|
||||
CALDAV_URL = cfg.caldavUrl;
|
||||
CALDAV_USER = cfg.caldavUser;
|
||||
NODE_ENV = "production";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
# CalDAV password loaded as a systemd credential. Provision the
|
||||
# encrypted file under /var/lib/eink/secrets/caldav-password
|
||||
# (or use sops-nix). See README.
|
||||
LoadCredential = [ "caldav-password:/var/lib/eink/secrets/caldav-password" ];
|
||||
|
||||
DynamicUser = true;
|
||||
StateDirectory = "eink-dashboard";
|
||||
ExecStart = "${pkgs.nodejs}/bin/node ${dashboardBuild}/build/index.js";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{ config, lib, pkgs, self, ... }:
|
||||
|
||||
let
|
||||
cfg = config.eink.renderer;
|
||||
|
||||
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||
pillow
|
||||
numpy
|
||||
playwright
|
||||
requests
|
||||
# The Waveshare 12.48" lib is not packaged in nixpkgs; we vendor it
|
||||
# under renderer/waveshare/ and import it as a local module.
|
||||
spidev
|
||||
rpi-gpio
|
||||
]);
|
||||
in
|
||||
{
|
||||
options.eink.renderer = {
|
||||
enable = lib.mkEnableOption "E-paper renderer (Playwright + Pillow + Waveshare driver)";
|
||||
|
||||
dashboardUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://127.0.0.1:3000";
|
||||
};
|
||||
|
||||
refreshSeconds = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 300;
|
||||
description = "Seconds between full panel refreshes. Below 180 will wear the panel.";
|
||||
};
|
||||
|
||||
panel = lib.mkOption {
|
||||
type = lib.types.enum [ "bw" "bwr" ];
|
||||
default = "bw";
|
||||
description = "Panel variant: bw = 12.48\" B/W, bwr = 12.48\" Module (B) B/W/Red.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# Playwright needs a Chromium and its system deps. Use the nixpkgs one
|
||||
# so the binary path is stable across rebuilds (not the npm-downloaded
|
||||
# browser, which won't find its sandbox libs on NixOS).
|
||||
systemd.services.eink-renderer = {
|
||||
description = "E-paper renderer loop";
|
||||
after = [ "eink-dashboard.service" "network-online.target" ];
|
||||
wants = [ "eink-dashboard.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment = {
|
||||
DASHBOARD_URL = cfg.dashboardUrl;
|
||||
REFRESH_SECONDS = toString cfg.refreshSeconds;
|
||||
PANEL_VARIANT = cfg.panel;
|
||||
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1";
|
||||
PYTHONPATH = "${self}/renderer";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pythonEnv}/bin/python ${self}/renderer/render.py";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
|
||||
# The Waveshare lib needs raw SPI + GPIO. Run as a dedicated user
|
||||
# in the spi/gpio groups rather than root.
|
||||
User = "eink";
|
||||
Group = "eink";
|
||||
SupplementaryGroups = [ "spi" "gpio" ];
|
||||
};
|
||||
};
|
||||
|
||||
users.users.eink = {
|
||||
isSystemUser = true;
|
||||
group = "eink";
|
||||
extraGroups = [ "spi" "gpio" ];
|
||||
};
|
||||
users.groups.eink = {};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "eink-renderer"
|
||||
version = "0.1.0"
|
||||
description = "Screenshot the dashboard, dither it, push it to the Waveshare 12.48\" panel."
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"playwright>=1.48",
|
||||
"pillow>=10.4",
|
||||
"numpy>=2.1",
|
||||
"requests>=2.32",
|
||||
# Hardware-only — installed by Nix, not pip on dev machines.
|
||||
# "spidev>=3.6",
|
||||
# "RPi.GPIO>=0.7",
|
||||
]
|
||||
@@ -0,0 +1,144 @@
|
||||
"""E-paper renderer loop.
|
||||
|
||||
Pipeline:
|
||||
Playwright screenshot of $DASHBOARD_URL at 1304x984
|
||||
-> Pillow 1-bit dither (Floyd-Steinberg)
|
||||
-> Waveshare 12.48" driver push
|
||||
|
||||
Tunables come from env: DASHBOARD_URL, REFRESH_SECONDS, PANEL_VARIANT.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
log = logging.getLogger("eink.renderer")
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
|
||||
WIDTH, HEIGHT = 1304, 984
|
||||
DASHBOARD_URL = os.environ.get("DASHBOARD_URL", "http://127.0.0.1:3000")
|
||||
REFRESH_SECONDS = int(os.environ.get("REFRESH_SECONDS", "300"))
|
||||
PANEL_VARIANT = os.environ.get("PANEL_VARIANT", "bw") # bw | bwr
|
||||
|
||||
|
||||
def screenshot(url: str) -> Image.Image:
|
||||
"""Headless Chromium screenshot at the panel's native resolution."""
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True, args=["--no-sandbox"])
|
||||
ctx = browser.new_context(
|
||||
viewport={"width": WIDTH, "height": HEIGHT},
|
||||
device_scale_factor=1,
|
||||
)
|
||||
page = ctx.new_page()
|
||||
page.goto(url, wait_until="networkidle", timeout=30_000)
|
||||
png = page.screenshot(full_page=False, type="png", omit_background=False)
|
||||
browser.close()
|
||||
return Image.open(io.BytesIO(png)).convert("RGB")
|
||||
|
||||
|
||||
def to_bw(img: Image.Image) -> Image.Image:
|
||||
"""Floyd-Steinberg dither to 1-bit. Pillow's built-in is C-accelerated."""
|
||||
return img.convert("L").convert("1", dither=Image.Dither.FLOYDSTEINBERG)
|
||||
|
||||
|
||||
def to_bwr(img: Image.Image) -> tuple[Image.Image, Image.Image]:
|
||||
"""Separate red plane from B/W plane for the Module (B) variant.
|
||||
|
||||
Naive thresholding: pixels with R > 180 and G,B < 100 -> red plane.
|
||||
Everything else -> dithered B/W.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
arr = np.array(img)
|
||||
r, g, b = arr[..., 0], arr[..., 1], arr[..., 2]
|
||||
red_mask = (r > 180) & (g < 100) & (b < 100)
|
||||
|
||||
bw_arr = arr.copy()
|
||||
bw_arr[red_mask] = 255 # remove red from BW plane
|
||||
bw_img = to_bw(Image.fromarray(bw_arr))
|
||||
|
||||
red_img = Image.fromarray((red_mask * 255).astype("uint8"), mode="L").convert("1")
|
||||
return bw_img, red_img
|
||||
|
||||
|
||||
def push_to_panel_bw(bw: Image.Image) -> None:
|
||||
"""Push a 1-bit image to the B/W 12.48\" panel.
|
||||
|
||||
NOTE: requires the Waveshare 12.48\" Python driver vendored under
|
||||
renderer/waveshare/. See waveshare/README.md for install steps.
|
||||
"""
|
||||
try:
|
||||
from waveshare import epd12in48 # type: ignore
|
||||
except ImportError:
|
||||
log.warning("Waveshare driver not vendored — dry-run mode (no panel push).")
|
||||
bw.save("/tmp/eink-last.png")
|
||||
return
|
||||
|
||||
epd = epd12in48.EPD()
|
||||
epd.Init()
|
||||
epd.display(epd.getbuffer(bw))
|
||||
epd.EPD_Sleep()
|
||||
|
||||
|
||||
def push_to_panel_bwr(bw: Image.Image, red: Image.Image) -> None:
|
||||
try:
|
||||
from waveshare import epd12in48b # type: ignore
|
||||
except ImportError:
|
||||
log.warning("Waveshare (B) driver not vendored — dry-run mode (no panel push).")
|
||||
bw.save("/tmp/eink-last-bw.png")
|
||||
red.save("/tmp/eink-last-red.png")
|
||||
return
|
||||
|
||||
epd = epd12in48b.EPD()
|
||||
epd.Init()
|
||||
epd.display(epd.getbuffer(bw), epd.getbuffer(red))
|
||||
epd.EPD_Sleep()
|
||||
|
||||
|
||||
def render_once() -> None:
|
||||
log.info("Screenshot %s", DASHBOARD_URL)
|
||||
img = screenshot(DASHBOARD_URL)
|
||||
if PANEL_VARIANT == "bwr":
|
||||
bw, red = to_bwr(img)
|
||||
push_to_panel_bwr(bw, red)
|
||||
else:
|
||||
push_to_panel_bw(to_bw(img))
|
||||
log.info("Frame pushed to panel.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
stop = False
|
||||
|
||||
def handle(*_):
|
||||
nonlocal stop
|
||||
stop = True
|
||||
log.info("Shutdown signal — exiting after current frame.")
|
||||
|
||||
signal.signal(signal.SIGTERM, handle)
|
||||
signal.signal(signal.SIGINT, handle)
|
||||
|
||||
while not stop:
|
||||
started = time.monotonic()
|
||||
try:
|
||||
render_once()
|
||||
except Exception:
|
||||
log.exception("Render cycle failed; will retry next interval.")
|
||||
elapsed = time.monotonic() - started
|
||||
sleep_for = max(0, REFRESH_SECONDS - elapsed)
|
||||
log.info("Sleeping %.1fs", sleep_for)
|
||||
for _ in range(int(sleep_for)):
|
||||
if stop:
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
# Waveshare 12.48" driver
|
||||
|
||||
The 12.48" driver lives in a **separate repo** from the main Waveshare
|
||||
e-Paper one — it's at [`waveshareteam/12.48inch-e-paper`](https://github.com/waveshareteam/12.48inch-e-paper).
|
||||
The big `waveshareteam/e-Paper` repo does **not** contain it.
|
||||
|
||||
## Install
|
||||
|
||||
Run this from the project root (`~/Repos/eink/`):
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/waveshareteam/12.48inch-e-paper.git /tmp/waveshare-12in48
|
||||
cp /tmp/waveshare-12in48/RaspberryPi/python/lib/epd12in48.py renderer/waveshare/
|
||||
cp /tmp/waveshare-12in48/RaspberryPi/python/lib/epd12in48b.py renderer/waveshare/
|
||||
cp /tmp/waveshare-12in48/RaspberryPi/python/lib/epd12in48b_V2.py renderer/waveshare/
|
||||
cp /tmp/waveshare-12in48/RaspberryPi/python/lib/epdconfig.py renderer/waveshare/
|
||||
rm -rf /tmp/waveshare-12in48
|
||||
```
|
||||
|
||||
After copying, `renderer/waveshare/` should look like:
|
||||
|
||||
```
|
||||
renderer/waveshare/
|
||||
├── __init__.py # (already present)
|
||||
├── README.md # (already present, this file)
|
||||
├── epd12in48.py # B/W variant driver
|
||||
├── epd12in48b.py # B/W/Red (Module B) original driver
|
||||
├── epd12in48b_V2.py # B/W/Red (Module B) v2 driver — newer revision
|
||||
└── epdconfig.py # SPI + GPIO abstraction (uses spidev + RPi.GPIO)
|
||||
```
|
||||
|
||||
The renderer (`renderer/render.py`) imports `from waveshare import epd12in48`
|
||||
(B/W) or `from waveshare import epd12in48b` (B/W/Red). For the v2 panel
|
||||
revision, edit `render.py` to import `epd12in48b_V2` instead.
|
||||
|
||||
## Why not vendor it from the start?
|
||||
|
||||
It's vendor code with its own license headers — kept out of git until
|
||||
the user opts in to keep the repo tidy and the license boundary clear.
|
||||
Add it to `.gitignore` if you'd rather pull it fresh on each deploy.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Waveshare 12.48" driver — vendored from
|
||||
# https://github.com/waveshareteam/e-Paper. See README.md in this dir.
|
||||
@@ -0,0 +1,398 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : epd12in48.py
|
||||
# * | Author : Waveshare electrices
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2019-11-01
|
||||
# * | Info :
|
||||
# ******************************************************************************/
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
import time
|
||||
import epdconfig
|
||||
|
||||
EPD_WIDTH = 1304
|
||||
EPD_HEIGHT = 984
|
||||
|
||||
class EPD(object):
|
||||
def __init__(self):
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
|
||||
self.EPD_M1_CS_PIN = epdconfig.EPD_M1_CS_PIN
|
||||
self.EPD_S1_CS_PIN = epdconfig.EPD_S1_CS_PIN
|
||||
self.EPD_M2_CS_PIN = epdconfig.EPD_M2_CS_PIN
|
||||
self.EPD_S2_CS_PIN = epdconfig.EPD_S2_CS_PIN
|
||||
|
||||
self.EPD_M1S1_DC_PIN = epdconfig.EPD_M1S1_DC_PIN
|
||||
self.EPD_M2S2_DC_PIN = epdconfig.EPD_M2S2_DC_PIN
|
||||
|
||||
self.EPD_M1S1_RST_PIN = epdconfig.EPD_M1S1_RST_PIN
|
||||
self.EPD_M2S2_RST_PIN = epdconfig.EPD_M2S2_RST_PIN
|
||||
|
||||
self.EPD_M1_BUSY_PIN = epdconfig.EPD_M1_BUSY_PIN
|
||||
self.EPD_S1_BUSY_PIN = epdconfig.EPD_S1_BUSY_PIN
|
||||
self.EPD_M2_BUSY_PIN = epdconfig.EPD_M2_BUSY_PIN
|
||||
self.EPD_S2_BUSY_PIN = epdconfig.EPD_S2_BUSY_PIN
|
||||
|
||||
def Init(self):
|
||||
print("EPD init...")
|
||||
epdconfig.module_init()
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
self.Reset()
|
||||
|
||||
#panel setting
|
||||
self.M1_SendCommand(0x00)
|
||||
self.M1_SendData(0x1f) #KW-3f KWR-2F BWROTP 0f BWOTP 1f
|
||||
self.S1_SendCommand(0x00)
|
||||
self.S1_SendData(0x1f)
|
||||
self.M2_SendCommand(0x00)
|
||||
self.M2_SendData(0x13)
|
||||
self.S2_SendCommand(0x00)
|
||||
self.S2_SendData(0x13)
|
||||
|
||||
# booster soft start
|
||||
self.M1_SendCommand(0x06)
|
||||
self.M1_SendData(0x17) #A
|
||||
self.M1_SendData(0x17) #B
|
||||
self.M1_SendData(0x39) #C
|
||||
self.M1_SendData(0x17)
|
||||
self.M2_SendCommand(0x06)
|
||||
self.M2_SendData(0x17)
|
||||
self.M2_SendData(0x17)
|
||||
self.M2_SendData(0x39)
|
||||
self.M2_SendData(0x17)
|
||||
|
||||
#resolution setting
|
||||
self.M1_SendCommand(0x61)
|
||||
self.M1_SendData(0x02)
|
||||
self.M1_SendData(0x88) #source 648
|
||||
self.M1_SendData(0x01) #gate 492
|
||||
self.M1_SendData(0xEC)
|
||||
self.S1_SendCommand(0x61)
|
||||
self.S1_SendData(0x02)
|
||||
self.S1_SendData(0x90) #source 656
|
||||
self.S1_SendData(0x01) #gate 492
|
||||
self.S1_SendData(0xEC)
|
||||
self.M2_SendCommand(0x61)
|
||||
self.M2_SendData(0x02)
|
||||
self.M2_SendData(0x90) #source 656
|
||||
self.M2_SendData(0x01) #gate 492
|
||||
self.M2_SendData(0xEC)
|
||||
self.S2_SendCommand(0x61)
|
||||
self.S2_SendData(0x02)
|
||||
self.S2_SendData(0x88) #source 648
|
||||
self.S2_SendData(0x01) #gate 492
|
||||
self.S2_SendData(0xEC)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x15) #DUSPI
|
||||
self.M1S1M2S2_SendData(0x20)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x50) #Vcom and data interval setting
|
||||
self.M1S1M2S2_SendData(0x21) #Border KW
|
||||
self.M1S1M2S2_SendData(0x07)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x60) #TCON
|
||||
self.M1S1M2S2_SendData(0x22)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0xE3)
|
||||
self.M1S1M2S2_SendData(0x00)
|
||||
|
||||
#temperature
|
||||
temp = self.M1_ReadTemperature()
|
||||
|
||||
self.M1S1M2S2_SendCommand(0xe0) #Cascade setting
|
||||
self.M1S1M2S2_SendData(0x03)
|
||||
self.M1S1M2S2_SendCommand(0xe5) #Force temperature
|
||||
self.M1S1M2S2_SendData(temp)
|
||||
|
||||
def display(self, Image):
|
||||
start = time.clock()
|
||||
buf = [0x00] * int(self.width * self.height / 8)
|
||||
image_monocolor = Image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
temp=0;
|
||||
for y in range(0, imheight):
|
||||
for x in range(0, imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] < 127: # black
|
||||
buf[int((x + y*self.width)/8)] &= ~(0x80>>temp)
|
||||
else: # white
|
||||
buf[int((x + y*self.width)/8)] |= (0x80>>temp)
|
||||
temp=temp+1
|
||||
if(temp==8):
|
||||
temp=0
|
||||
|
||||
#M1 part 648*492
|
||||
self.M1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(buf[y*163 + x])
|
||||
|
||||
#S1 part 656*492
|
||||
self.S1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(buf[y*163 + x])
|
||||
|
||||
#M2 part 656*492
|
||||
self.M2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(buf[y*163 + x])
|
||||
|
||||
#S2 part 648*492
|
||||
self.S2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(buf[y*163 + x])
|
||||
|
||||
end = time.clock()
|
||||
print("use time:%f"%(end - start))
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def clear(self):
|
||||
"""Clear contents of image buffer"""
|
||||
start = time.clock()
|
||||
self.M1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(0xff)
|
||||
|
||||
self.S1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(0xff)
|
||||
|
||||
self.M2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(0xff)
|
||||
|
||||
self.S2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(0xff)
|
||||
end = time.clock()
|
||||
print("use time:%f"%(end - start))
|
||||
self.TurnOnDisplay()
|
||||
|
||||
""" M1S1M2S2 Write register address and data """
|
||||
def M1S1M2S2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
def M1S1M2S2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
""" M1M2 Write register address and data """
|
||||
def M1M2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
def M1S1M2S2_Senddata(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
""" S2 Write register address and data """
|
||||
def S2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
|
||||
def S2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
""" M2 Write register address and data """
|
||||
def M2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
def M2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
""" S1 Write register address and data """
|
||||
def S1_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
|
||||
def S1_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
|
||||
""" M1 Write register address and data """
|
||||
def M1_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
|
||||
def M1_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
|
||||
def Reset(self):
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 1)
|
||||
time.sleep(0.2)
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 0)
|
||||
time.sleep(0.01)
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 1)
|
||||
time.sleep(0.2)
|
||||
|
||||
def EPD_Sleep(self):
|
||||
self.M1S1M2S2_SendCommand(0X02)
|
||||
time.sleep(0.3)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0X07)
|
||||
self.M1S1M2S2_SendData(0xA5)
|
||||
time.sleep(0.3)
|
||||
print("module_exit")
|
||||
epdconfig.module_exit()
|
||||
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.M1M2_SendCommand(0x04)
|
||||
time.sleep(0.3)
|
||||
self.M1S1M2S2_SendCommand(0x12)
|
||||
self.M1_ReadBusy()
|
||||
self.S1_ReadBusy()
|
||||
self.M2_ReadBusy()
|
||||
self.S2_ReadBusy()
|
||||
|
||||
#Busy
|
||||
def M1_ReadBusy(self):
|
||||
self.M1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
print("M1_ReadBusy")
|
||||
while(busy):
|
||||
self.M1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
|
||||
def M2_ReadBusy(self):
|
||||
self.M2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
print("M2_ReadBusy")
|
||||
while(busy):
|
||||
self.M2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M2_BUSY_PIN)
|
||||
busy =not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
|
||||
def S1_ReadBusy(self):
|
||||
self.S1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
print("s1_ReadBusy")
|
||||
while(busy):
|
||||
self.S1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
|
||||
def S2_ReadBusy(self):
|
||||
self.S2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
print("S2_ReadBusy")
|
||||
while(busy):
|
||||
self.S2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
|
||||
def M1_ReadTemperature(self):
|
||||
self.M1_SendCommand(0x40)
|
||||
self.M1_ReadBusy()
|
||||
time.sleep(0.3)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
time.sleep(0.01)
|
||||
|
||||
# temp = epdconfig.spi_readbyte(0x00)
|
||||
temp = 25
|
||||
print("Read Temperature Reg:%d"%temp)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
# temp =0x29
|
||||
return temp
|
||||
@@ -0,0 +1,525 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : epd12in48.py
|
||||
# * | Author : Waveshare electrices
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2019-11-01
|
||||
# * | Info :
|
||||
# ******************************************************************************/
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
import time
|
||||
import epdconfig
|
||||
|
||||
EPD_WIDTH = 1304
|
||||
EPD_HEIGHT = 984
|
||||
|
||||
class EPD(object):
|
||||
def __init__(self):
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
|
||||
self.EPD_M1_CS_PIN = epdconfig.EPD_M1_CS_PIN
|
||||
self.EPD_S1_CS_PIN = epdconfig.EPD_S1_CS_PIN
|
||||
self.EPD_M2_CS_PIN = epdconfig.EPD_M2_CS_PIN
|
||||
self.EPD_S2_CS_PIN = epdconfig.EPD_S2_CS_PIN
|
||||
|
||||
self.EPD_M1S1_DC_PIN = epdconfig.EPD_M1S1_DC_PIN
|
||||
self.EPD_M2S2_DC_PIN = epdconfig.EPD_M2S2_DC_PIN
|
||||
|
||||
self.EPD_M1S1_RST_PIN = epdconfig.EPD_M1S1_RST_PIN
|
||||
self.EPD_M2S2_RST_PIN = epdconfig.EPD_M2S2_RST_PIN
|
||||
|
||||
self.EPD_M1_BUSY_PIN = epdconfig.EPD_M1_BUSY_PIN
|
||||
self.EPD_S1_BUSY_PIN = epdconfig.EPD_S1_BUSY_PIN
|
||||
self.EPD_M2_BUSY_PIN = epdconfig.EPD_M2_BUSY_PIN
|
||||
self.EPD_S2_BUSY_PIN = epdconfig.EPD_S2_BUSY_PIN
|
||||
|
||||
def Init(self):
|
||||
print("EPD init...")
|
||||
epdconfig.module_init()
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
self.Reset()
|
||||
|
||||
#panel setting
|
||||
self.M1_SendCommand(0x00)
|
||||
self.M1_SendData(0x2f) #KW-3f KWR-2F BWROTP 0f BWOTP 1f
|
||||
self.S1_SendCommand(0x00)
|
||||
self.S1_SendData(0x2f)
|
||||
self.M2_SendCommand(0x00)
|
||||
self.M2_SendData(0x23)
|
||||
self.S2_SendCommand(0x00)
|
||||
self.S2_SendData(0x23)
|
||||
|
||||
# POWER SETTING
|
||||
self.M1_SendCommand(0x01)
|
||||
self.M1_SendData(0x07)
|
||||
self.M1_SendData(0x17) # VGH=20V,VGL=-20V
|
||||
self.M1_SendData(0x3F) # VDH=15V
|
||||
self.M1_SendData(0x3F) # VDL=-15V
|
||||
self.M1_SendData(0x0d)
|
||||
self.M2_SendCommand(0x01)
|
||||
self.M2_SendData(0x07)
|
||||
self.M2_SendData(0x17) # VGH=20V,VGL=-20V
|
||||
self.M2_SendData(0x3F) # VDH=15V
|
||||
self.M2_SendData(0x3F) # VDL=-15V
|
||||
self.M2_SendData(0x0d)
|
||||
|
||||
# booster soft start
|
||||
self.M1_SendCommand(0x06)
|
||||
self.M1_SendData(0x17) #A
|
||||
self.M1_SendData(0x17) #B
|
||||
self.M1_SendData(0x39) #C
|
||||
self.M1_SendData(0x17)
|
||||
self.M2_SendCommand(0x06)
|
||||
self.M2_SendData(0x17)
|
||||
self.M2_SendData(0x17)
|
||||
self.M2_SendData(0x39)
|
||||
self.M2_SendData(0x17)
|
||||
|
||||
#resolution setting
|
||||
self.M1_SendCommand(0x61)
|
||||
self.M1_SendData(0x02)
|
||||
self.M1_SendData(0x88) #source 648
|
||||
self.M1_SendData(0x01) #gate 492
|
||||
self.M1_SendData(0xEC)
|
||||
self.S1_SendCommand(0x61)
|
||||
self.S1_SendData(0x02)
|
||||
self.S1_SendData(0x90) #source 656
|
||||
self.S1_SendData(0x01) #gate 492
|
||||
self.S1_SendData(0xEC)
|
||||
self.M2_SendCommand(0x61)
|
||||
self.M2_SendData(0x02)
|
||||
self.M2_SendData(0x90) #source 656
|
||||
self.M2_SendData(0x01) #gate 492
|
||||
self.M2_SendData(0xEC)
|
||||
self.S2_SendCommand(0x61)
|
||||
self.S2_SendData(0x02)
|
||||
self.S2_SendData(0x88) #source 648
|
||||
self.S2_SendData(0x01) #gate 492
|
||||
self.S2_SendData(0xEC)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x15) #DUSPI
|
||||
self.M1S1M2S2_SendData(0x20)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x30) # PLL
|
||||
self.M1S1M2S2_SendData(0x08)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x50) #Vcom and data interval setting
|
||||
self.M1S1M2S2_SendData(0x31)
|
||||
self.M1S1M2S2_SendData(0x07)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x60)#TCON
|
||||
self.M1S1M2S2_SendData(0x22)
|
||||
|
||||
self.M1_SendCommand(0xE0) #POWER SETTING
|
||||
self.M1_SendData(0x01)
|
||||
self.M2_SendCommand(0xE0) #POWER SETTING
|
||||
self.M2_SendData(0x01)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0xE3)
|
||||
self.M1S1M2S2_SendData(0x00)
|
||||
|
||||
self.M1_SendCommand(0x82)
|
||||
self.M1_SendData(0x1c)
|
||||
self.M2_SendCommand(0x82)
|
||||
self.M2_SendData(0x1c)
|
||||
|
||||
self.SetLut()
|
||||
|
||||
def display(self, BlackImage, RedImage):
|
||||
start = time.clock()
|
||||
|
||||
Blackbuf = [0x00] * int(self.width * self.height / 8)
|
||||
blackconvert = BlackImage.convert('1')
|
||||
bimwidth, bimheight = blackconvert.size
|
||||
Blackpixles = blackconvert.load()
|
||||
temp=0;
|
||||
for y in range(0, bimheight):
|
||||
for x in range(0, bimwidth):
|
||||
if Blackpixles[x, y] < 127: # black
|
||||
Blackbuf[int((x + y*self.width)/8)] &= ~(0x80>>temp)
|
||||
else: # white
|
||||
Blackbuf[int((x + y*self.width)/8)] |= (0x80>>temp)
|
||||
temp=temp+1
|
||||
if(temp==8):
|
||||
temp=0
|
||||
|
||||
Redbuf = [0x00] * int(self.width * self.height / 8)
|
||||
redconvert = RedImage.convert('1')
|
||||
rimwidth, rimheight = redconvert.size
|
||||
Redpixles = redconvert.load()
|
||||
temp=0;
|
||||
for y in range(0, rimheight):
|
||||
for x in range(0, rimwidth):
|
||||
if Redpixles[x, y] < 127: # black
|
||||
Redbuf[int((x + y*self.width)/8)] &= ~(0x80>>temp)
|
||||
else: # white
|
||||
Redbuf[int((x + y*self.width)/8)] |= (0x80>>temp)
|
||||
temp=temp+1
|
||||
if(temp==8):
|
||||
temp=0
|
||||
|
||||
#S2 part 648*492
|
||||
self.S2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(Blackbuf[y*163 + x])
|
||||
self.S2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
#M2 part 656*492
|
||||
self.M2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(Blackbuf[y*163 + x])
|
||||
self.M2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
#M1 part 648*492
|
||||
self.M1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(Blackbuf[y*163 + x])
|
||||
self.M1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
#S1 part 656*492
|
||||
self.S1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(Blackbuf[y*163 + x])
|
||||
self.S1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
end = time.clock()
|
||||
print("use time: %f"%(end - start))
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def clear(self):
|
||||
"""Clear contents of image buffer"""
|
||||
start = time.clock()
|
||||
|
||||
self.S2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(0xff)
|
||||
self.S2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(0x00)
|
||||
|
||||
self.M2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(0xff)
|
||||
self.M2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(0x00)
|
||||
|
||||
self.M1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(0xff)
|
||||
self.M1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(0x00)
|
||||
|
||||
self.S1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(0xff)
|
||||
self.S1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(0x00)
|
||||
|
||||
end = time.clock()
|
||||
print (end)
|
||||
print (start)
|
||||
print("use time: %f" %(end - start))
|
||||
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Reset(self):
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 1)
|
||||
time.sleep(0.2)
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 0)
|
||||
time.sleep(0.01)
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 1)
|
||||
time.sleep(0.2)
|
||||
|
||||
def EPD_Sleep(self):
|
||||
self.M1S1M2S2_SendCommand(0X02)
|
||||
time.sleep(0.3)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0X07)
|
||||
self.M1S1M2S2_SendData(0xA5)
|
||||
time.sleep(0.3)
|
||||
print("module_exit")
|
||||
epdconfig.module_exit()
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.M1M2_SendCommand(0x04)
|
||||
time.sleep(0.3)
|
||||
self.M1S1M2S2_SendCommand(0x12)
|
||||
self.M1_ReadBusy()
|
||||
self.S1_ReadBusy()
|
||||
self.M2_ReadBusy()
|
||||
self.S2_ReadBusy()
|
||||
|
||||
""" M1S1M2S2 Write register address and data """
|
||||
def M1S1M2S2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
def M1S1M2S2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
""" M1M2 Write register address and data """
|
||||
def M1M2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
def M1M2_Sendata(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
""" S2 Write register address and data """
|
||||
def S2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
def S2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
""" M2 Write register address and data """
|
||||
def M2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
def M2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
""" S1 Write register address and data """
|
||||
def S1_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
def S1_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
|
||||
""" M1 Write register address and data """
|
||||
def M1_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
def M1_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
|
||||
#Busy
|
||||
def M1_ReadBusy(self):
|
||||
self.M1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
while(busy):
|
||||
self.M1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
def M2_ReadBusy(self):
|
||||
self.M2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
self.M2_SendCommand(0x71)
|
||||
while(busy):
|
||||
self.M2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M2_BUSY_PIN)
|
||||
busy =not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
def S1_ReadBusy(self):
|
||||
self.S1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
while(busy):
|
||||
self.S1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
def S2_ReadBusy(self):
|
||||
self.S2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
while(busy):
|
||||
self.S2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
|
||||
lut_vcom1 = [
|
||||
0x00, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x00, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x00, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x00, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_ww1 = [
|
||||
0x91, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x04, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x80, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x08, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_bw1 = [
|
||||
0xA8, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x84, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x86, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x8C, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x8C, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0xF0, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_wb1 = [
|
||||
0x91, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x04, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x80, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x08, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_bb1 = [
|
||||
0x92, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x80, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x04, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x01, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
|
||||
def SetLut(self):
|
||||
self.M1S1M2S2_SendCommand(0x20) #vcom
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_vcom1[count])
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x21) #red not use
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_ww1[count])
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x22) #bw r
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_bw1[count]) # bw=r
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x23) #wb w
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_wb1[count]) # wb=w
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x24) #bb b
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_bb1[count]) # bb=b
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x25) #bb b
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_ww1[count]) # bb=b
|
||||
@@ -0,0 +1,529 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : epd12in48b_V2.py
|
||||
# * | Author : Waveshare electrices
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2022-09-14
|
||||
# * | Info :
|
||||
# ******************************************************************************/
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
import time
|
||||
import epdconfig
|
||||
|
||||
EPD_WIDTH = 1304
|
||||
EPD_HEIGHT = 984
|
||||
|
||||
class EPD(object):
|
||||
def __init__(self):
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
|
||||
self.EPD_M1_CS_PIN = epdconfig.EPD_M1_CS_PIN
|
||||
self.EPD_S1_CS_PIN = epdconfig.EPD_S1_CS_PIN
|
||||
self.EPD_M2_CS_PIN = epdconfig.EPD_M2_CS_PIN
|
||||
self.EPD_S2_CS_PIN = epdconfig.EPD_S2_CS_PIN
|
||||
|
||||
self.EPD_M1S1_DC_PIN = epdconfig.EPD_M1S1_DC_PIN
|
||||
self.EPD_M2S2_DC_PIN = epdconfig.EPD_M2S2_DC_PIN
|
||||
|
||||
self.EPD_M1S1_RST_PIN = epdconfig.EPD_M1S1_RST_PIN
|
||||
self.EPD_M2S2_RST_PIN = epdconfig.EPD_M2S2_RST_PIN
|
||||
|
||||
self.EPD_M1_BUSY_PIN = epdconfig.EPD_M1_BUSY_PIN
|
||||
self.EPD_S1_BUSY_PIN = epdconfig.EPD_S1_BUSY_PIN
|
||||
self.EPD_M2_BUSY_PIN = epdconfig.EPD_M2_BUSY_PIN
|
||||
self.EPD_S2_BUSY_PIN = epdconfig.EPD_S2_BUSY_PIN
|
||||
|
||||
def Init(self):
|
||||
print("EPD init...")
|
||||
epdconfig.module_init()
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
self.Reset()
|
||||
|
||||
# panel setting for Clear
|
||||
# self.M1_SendCommand(0x00)
|
||||
# self.M1_SendData(0x07) #KW-3f KWR-2F BWROTP 0f BWOTP 1f
|
||||
# self.S1_SendCommand(0x00)
|
||||
# self.S1_SendData(0x07)
|
||||
# self.M2_SendCommand(0x00)
|
||||
# self.M2_SendData(0x07)
|
||||
# self.S2_SendCommand(0x00)
|
||||
# self.S2_SendData(0x07)
|
||||
|
||||
# panel setting for Display
|
||||
self.M1_SendCommand(0x00)
|
||||
self.M1_SendData(0x0f) #KW-3f KWR-2F BWROTP 0f BWOTP 1f
|
||||
self.S1_SendCommand(0x00)
|
||||
self.S1_SendData(0x0f)
|
||||
self.M2_SendCommand(0x00)
|
||||
self.M2_SendData(0x03)
|
||||
self.S2_SendCommand(0x00)
|
||||
self.S2_SendData(0x03)
|
||||
|
||||
# booster soft start
|
||||
self.M1_SendCommand(0x06)
|
||||
self.M1_SendData(0x17) #A
|
||||
self.M1_SendData(0x17) #B
|
||||
self.M1_SendData(0x39) #C
|
||||
self.M1_SendData(0x17)
|
||||
self.M2_SendCommand(0x06)
|
||||
self.M2_SendData(0x17)
|
||||
self.M2_SendData(0x17)
|
||||
self.M2_SendData(0x39)
|
||||
self.M2_SendData(0x17)
|
||||
|
||||
#resolution setting
|
||||
self.M1_SendCommand(0x61)
|
||||
self.M1_SendData(0x02)
|
||||
self.M1_SendData(0x88) #source 648
|
||||
self.M1_SendData(0x01) #gate 492
|
||||
self.M1_SendData(0xEC)
|
||||
self.S1_SendCommand(0x61)
|
||||
self.S1_SendData(0x02)
|
||||
self.S1_SendData(0x90) #source 656
|
||||
self.S1_SendData(0x01) #gate 492
|
||||
self.S1_SendData(0xEC)
|
||||
self.M2_SendCommand(0x61)
|
||||
self.M2_SendData(0x02)
|
||||
self.M2_SendData(0x90) #source 656
|
||||
self.M2_SendData(0x01) #gate 492
|
||||
self.M2_SendData(0xEC)
|
||||
self.S2_SendCommand(0x61)
|
||||
self.S2_SendData(0x02)
|
||||
self.S2_SendData(0x88) #source 648
|
||||
self.S2_SendData(0x01) #gate 492
|
||||
self.S2_SendData(0xEC)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x15) #DUSPI
|
||||
self.M1S1M2S2_SendData(0x20)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x50) #Vcom and data interval setting
|
||||
self.M1S1M2S2_SendData(0x11)
|
||||
self.M1S1M2S2_SendData(0x07)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x60)#TCON
|
||||
self.M1S1M2S2_SendData(0x22)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0xE3)
|
||||
self.M1S1M2S2_SendData(0x00)
|
||||
|
||||
self.M1_ReadTemperature()
|
||||
|
||||
def display(self, BlackImage, RedImage):
|
||||
start = time.perf_counter()
|
||||
|
||||
Blackbuf = [0x00] * int(self.width * self.height / 8)
|
||||
blackconvert = BlackImage.convert('1')
|
||||
bimwidth, bimheight = blackconvert.size
|
||||
Blackpixles = blackconvert.load()
|
||||
temp=0
|
||||
for y in range(0, bimheight):
|
||||
for x in range(0, bimwidth):
|
||||
if Blackpixles[x, y] < 127: # black
|
||||
Blackbuf[int((x + y*self.width)/8)] &= ~(0x80>>temp)
|
||||
else: # white
|
||||
Blackbuf[int((x + y*self.width)/8)] |= (0x80>>temp)
|
||||
temp=temp+1
|
||||
if(temp==8):
|
||||
temp=0
|
||||
|
||||
Redbuf = [0x00] * int(self.width * self.height / 8)
|
||||
redconvert = RedImage.convert('1')
|
||||
rimwidth, rimheight = redconvert.size
|
||||
Redpixles = redconvert.load()
|
||||
temp=0
|
||||
for y in range(0, rimheight):
|
||||
for x in range(0, rimwidth):
|
||||
if Redpixles[x, y] < 127: # black
|
||||
Redbuf[int((x + y*self.width)/8)] &= ~(0x80>>temp)
|
||||
else: # white
|
||||
Redbuf[int((x + y*self.width)/8)] |= (0x80>>temp)
|
||||
temp=temp+1
|
||||
if(temp==8):
|
||||
temp=0
|
||||
|
||||
#S2 part 648*492
|
||||
self.S2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(Blackbuf[y*163 + x])
|
||||
self.S2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
#M2 part 656*492
|
||||
self.M2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(Blackbuf[y*163 + x])
|
||||
self.M2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
#M1 part 648*492
|
||||
self.M1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(Blackbuf[y*163 + x])
|
||||
self.M1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
#S1 part 656*492
|
||||
self.S1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(Blackbuf[y*163 + x])
|
||||
self.S1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(~Redbuf[y*163 + x])
|
||||
|
||||
end = time.perf_counter()
|
||||
print("use time: %f"%(end - start))
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def clear(self):
|
||||
"""Clear contents of image buffer"""
|
||||
start = time.perf_counter()
|
||||
|
||||
self.S2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(0xff)
|
||||
self.S2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(0, 81):
|
||||
self.S2_SendData(0x00)
|
||||
|
||||
self.M2_SendCommand(0x10)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(0xff)
|
||||
self.M2_SendCommand(0x13)
|
||||
for y in range(0, 492):
|
||||
for x in range(81, 163):
|
||||
self.M2_SendData(0x00)
|
||||
|
||||
self.M1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(0xff)
|
||||
self.M1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(0, 81):
|
||||
self.M1_SendData(0x00)
|
||||
|
||||
self.S1_SendCommand(0x10)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(0xff)
|
||||
self.S1_SendCommand(0x13)
|
||||
for y in range(492, 984):
|
||||
for x in range(81, 163):
|
||||
self.S1_SendData(0x00)
|
||||
|
||||
end = time.perf_counter()
|
||||
print (end)
|
||||
print (start)
|
||||
print("use time: %f" %(end - start))
|
||||
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Reset(self):
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 1)
|
||||
time.sleep(0.2)
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 0)
|
||||
time.sleep(0.01)
|
||||
epdconfig.digital_write(self.EPD_M1S1_RST_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_RST_PIN, 1)
|
||||
time.sleep(0.2)
|
||||
|
||||
def EPD_Sleep(self):
|
||||
self.M1S1M2S2_SendCommand(0X02)
|
||||
time.sleep(0.3)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0X07)
|
||||
self.M1S1M2S2_SendData(0xA5)
|
||||
time.sleep(0.3)
|
||||
print("module_exit")
|
||||
epdconfig.module_exit()
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.M1M2_SendCommand(0x04)
|
||||
time.sleep(0.3)
|
||||
self.M1S1M2S2_SendCommand(0x12)
|
||||
self.M1_ReadBusy()
|
||||
self.S1_ReadBusy()
|
||||
self.M2_ReadBusy()
|
||||
self.S2_ReadBusy()
|
||||
|
||||
""" M1S1M2S2 Write register address and data """
|
||||
def M1S1M2S2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
def M1S1M2S2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
""" M1M2 Write register address and data """
|
||||
def M1M2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
def M1M2_Sendata(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
""" S2 Write register address and data """
|
||||
def S2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
def S2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
""" M2 Write register address and data """
|
||||
def M2_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
def M2_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M2S2_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
|
||||
""" S1 Write register address and data """
|
||||
def S1_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
def S1_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
|
||||
""" M1 Write register address and data """
|
||||
def M1_SendCommand(self, cmd):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(cmd)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
def M1_SendData(self, val):
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.spi_writebyte(val)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
|
||||
#Busy
|
||||
def M1_ReadBusy(self):
|
||||
self.M1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
while(busy):
|
||||
self.M1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
def M2_ReadBusy(self):
|
||||
self.M2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
self.M2_SendCommand(0x71)
|
||||
while(busy):
|
||||
self.M2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_M2_BUSY_PIN)
|
||||
busy =not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
def S1_ReadBusy(self):
|
||||
self.S1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
while(busy):
|
||||
self.S1_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S1_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
def S2_ReadBusy(self):
|
||||
self.S2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
while(busy):
|
||||
self.S2_SendCommand(0x71)
|
||||
busy = epdconfig.digital_read(self.EPD_S2_BUSY_PIN)
|
||||
busy = not(busy & 0x01)
|
||||
time.sleep(0.2)
|
||||
|
||||
lut_vcom1 = [
|
||||
0x00, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x00, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x00, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x00, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_ww1 = [
|
||||
0x91, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x04, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x80, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x08, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_bw1 = [
|
||||
0xA8, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x84, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x86, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x8C, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x8C, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0xF0, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_wb1 = [
|
||||
0x91, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x04, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x80, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x08, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
lut_bb1 = [
|
||||
0x92, 0x10, 0x10, 0x01, 0x08, 0x01,
|
||||
0x80, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x84, 0x08, 0x01, 0x08, 0x01, 0x06,
|
||||
0x04, 0x06, 0x01, 0x06, 0x01, 0x05,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x06,
|
||||
0x00, 0x05, 0x01, 0x1E, 0x0F, 0x01,
|
||||
0x01, 0x04, 0x05, 0x08, 0x08, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
|
||||
def SetLut(self):
|
||||
self.M1S1M2S2_SendCommand(0x20) #vcom
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_vcom1[count])
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x21) #red not use
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_ww1[count])
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x22) #bw r
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_bw1[count]) # bw=r
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x23) #wb w
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_wb1[count]) # wb=w
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x24) #bb b
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_bb1[count]) # bb=b
|
||||
|
||||
self.M1S1M2S2_SendCommand(0x25) #bb b
|
||||
for count in range(0, 60):
|
||||
self.M1S1M2S2_SendData(self.lut_ww1[count]) # bb=b
|
||||
|
||||
def M1_ReadTemperature(self):
|
||||
self.M1_SendCommand(0x40)
|
||||
self.M1_ReadBusy()
|
||||
time.sleep(0.3)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 0)
|
||||
epdconfig.digital_write(self.EPD_S1_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_M2_CS_PIN, 1)
|
||||
epdconfig.digital_write(self.EPD_S2_CS_PIN, 1)
|
||||
|
||||
epdconfig.digital_write(self.EPD_M1S1_DC_PIN, 1)
|
||||
time.sleep(0.05)
|
||||
|
||||
temp = epdconfig.spi_readbyte(0x00)
|
||||
epdconfig.digital_write(self.EPD_M1_CS_PIN, 1)
|
||||
|
||||
self.M1S1M2S2_SendCommand(0xE0)
|
||||
self.M1S1M2S2_SendData(0x03)
|
||||
self.M1S1M2S2_SendCommand(0xE5)
|
||||
self.M1S1M2S2_SendData(temp)
|
||||
@@ -0,0 +1,99 @@
|
||||
# /*****************************************************************************
|
||||
# * | File : epdconfig.py
|
||||
# * | Author : Waveshare electrices
|
||||
# * | Function : Hardware underlying interface
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2019-11-01
|
||||
# * | Info :
|
||||
# ******************************************************************************/
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
import time
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from ctypes import *
|
||||
|
||||
EPD_SCK_PIN =11
|
||||
EPD_MOSI_PIN =10
|
||||
|
||||
EPD_M1_CS_PIN =8
|
||||
EPD_S1_CS_PIN =7
|
||||
EPD_M2_CS_PIN =17
|
||||
EPD_S2_CS_PIN =18
|
||||
|
||||
EPD_M1S1_DC_PIN =13
|
||||
EPD_M2S2_DC_PIN =22
|
||||
|
||||
EPD_M1S1_RST_PIN =6
|
||||
EPD_M2S2_RST_PIN =23
|
||||
|
||||
EPD_M1_BUSY_PIN =5
|
||||
EPD_S1_BUSY_PIN =19
|
||||
EPD_M2_BUSY_PIN =27
|
||||
EPD_S2_BUSY_PIN =24
|
||||
|
||||
find_dirs = [
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'/usr/local/lib',
|
||||
'/usr/lib',
|
||||
]
|
||||
spi = None
|
||||
for find_dir in find_dirs:
|
||||
val = int(os.popen('getconf LONG_BIT').read())
|
||||
logging.debug("System is %d bit"%val)
|
||||
if val == 64:
|
||||
so_filename = os.path.join(find_dir, 'DEV_Config_64.so')
|
||||
else:
|
||||
so_filename = os.path.join(find_dir, 'DEV_Config_32.so')
|
||||
if os.path.exists(so_filename):
|
||||
spi = CDLL(so_filename)
|
||||
break
|
||||
if spi is None:
|
||||
RuntimeError('Cannot find DEV_Config.so')
|
||||
|
||||
def digital_write(pin, value):
|
||||
spi.DEV_Digital_Write(pin, value)
|
||||
|
||||
def digital_read(pin):
|
||||
return spi.DEV_Digital_Read(pin)
|
||||
|
||||
def spi_writebyte(value):
|
||||
spi.DEV_SPI_WriteByte(value)
|
||||
|
||||
def delay_ms(delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def module_init():
|
||||
spi.DEV_ModuleInit()
|
||||
|
||||
def module_exit():
|
||||
spi.DEV_ModuleExit()
|
||||
|
||||
|
||||
def spi_readbyte(Reg):
|
||||
return spi.DEV_SPI_ReadByte(Reg)
|
||||
|
||||
def delay_ms(delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user