Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: CI
# Run this workflow whenever the build may be affected
on:
push:
branches: [ main ]
branches: [ main, "voidsec-hub/**" ]
paths-ignore:
- 'doc/**'
- '**.md'
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@

</div>

## // CYBERPUNK EDITION

This fork reworks InfiniTime into a neon, hacker-con-ready build for the PineTime.

| NetRunner | Glitch | Matrix | Badge |
|:-:|:-:|:-:|:-:|
| ![NetRunner](doc/cyberpunk/netrunner.png) | ![Glitch](doc/cyberpunk/netrunner-glitch.png) | ![Matrix](doc/cyberpunk/matrix.png) | ![Badge](doc/cyberpunk/badge.png) |
| **Launcher** | **Settings** | **Terminal** | **Intrusions** |
| ![Launcher](doc/cyberpunk/launcher.png) | ![Settings](doc/cyberpunk/settings.png) | ![Terminal](doc/cyberpunk/terminal.png) | ![Intrusions](doc/cyberpunk/intrusions.png) |

What's different from upstream:

- **NetRunner watch face** (new default): chromatic-aberration time with random glitch bursts plus a guaranteed glitch every minute, HUD frame lines, seconds sweep bar, link/power status, HR + steps, weather, and the Unix epoch in hex. The handle in the bottom-left corner comes from `src/Identity.h`.
- **Matrix app**: digital rain built from ASCII and Cyrillic glyphs. Tap to cycle green, cyan, and magenta palettes.
- **Badge app** (skull icon): your handle and a scannable QR code linking to your GitHub. The screen stays on while it is open. To point it somewhere else, run `python3 tools/badge/generate_badge_qr.py <url>` (needs `pip install segno`).
- **Intrusion log** (shield icon): every Bluetooth connection that isn't your bonded phone is logged with time, address, duration and what it tried (pairing, failed pairing). If you have a bonded phone, an unknown connection vibrates and pops up an `UNKNOWN LINK` alert. Toggle alerts on/off or wipe the log from the app. Your phone counts as trusted only when it is paired with the PIN (bonded), so pair it that way.
- **Bluetooth name** is your handle (`VOIDSEC`), set in `src/Identity.h`. Gadgetbridge only auto-detects watches whose name starts with `InfiniTime`, so pair it before flashing this firmware, or temporarily change the name back to pair.
- **Heart rate in the background**: measurement starts at boot and runs every 5 minutes by default, so you never have to open the heart rate app. If no pulse is found for 30 seconds (the watch is off your wrist or on the charger), it backs off and retries every 5 minutes, so even *Cont* mode doesn't run the sensor all day on the nightstand. Change the interval in *Settings → Heart rate*.
- **Neon theme**: cyan and magenta outlined buttons, sharp corners, dark purple surfaces, and recolored launcher tiles, lists, sliders, and switches.
- **Terminal face** recolored as a root shell.
- **Trimmed build**: Paint, Paddle, 2048, Dice, and Metronome apps plus the Analog, PineTimeStyle, Infineat, Casio, and Pride Flag faces are no longer built by default, which frees about 49 KB of flash. Re-enable any of them in `src/displayapp/apps/CMakeLists.txt`.

### Installing with Gadgetbridge

1. Grab `pinetime-mcuboot-app-dfu-<version>.zip` (and `infinitime-resources-<version>.zip` for the navigation icons) from the CI artifacts of this branch.
2. On the watch, go to *Settings → Over-the-air* and pick *Till reboot*. Updates and file uploads are refused while it is *Disabled*, and *Till reboot* shuts the door again after the update.
3. In Gadgetbridge, open the zip with *File installer* (or share it to Gadgetbridge) and install. Then install the resources zip the same way.
4. After the watch reboots, go to *Settings → Firmware* and tap *Validate*, otherwise the old firmware comes back on the next reboot.

> If your watch already has saved settings, pick NetRunner in *Settings → Watch face*.

## New to InfiniTime?

- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
Expand Down
Binary file added doc/cyberpunk/badge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/cyberpunk/intrusions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/cyberpunk/launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/cyberpunk/matrix.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/cyberpunk/netrunner-glitch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/cyberpunk/netrunner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/cyberpunk/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/cyberpunk/terminal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,9 @@ list(APPEND SOURCE_FILES
displayapp/screens/Steps.cpp
displayapp/screens/Timer.cpp
displayapp/screens/Dice.cpp
displayapp/screens/Matrix.cpp
displayapp/screens/Badge.cpp
displayapp/screens/IntrusionLog.cpp
displayapp/screens/PassKey.cpp
displayapp/screens/Error.cpp
displayapp/screens/Alarm.cpp
Expand Down Expand Up @@ -430,6 +433,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/WatchFacePineTimeStyle.cpp
displayapp/screens/WatchFaceCasioStyleG7710.cpp
displayapp/screens/WatchFacePrideFlag.cpp
displayapp/screens/WatchFaceNetRunner.cpp

##

Expand Down Expand Up @@ -463,6 +467,7 @@ list(APPEND SOURCE_FILES
components/ble/BatteryInformationService.cpp
components/ble/FSService.cpp
components/ble/ImmediateAlertService.cpp
components/ble/IntrusionLog.cpp
components/ble/ServiceDiscovery.cpp
components/ble/HeartRateService.cpp
components/ble/MotionService.cpp
Expand Down Expand Up @@ -533,6 +538,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/ble/BatteryInformationService.cpp
components/ble/FSService.cpp
components/ble/ImmediateAlertService.cpp
components/ble/IntrusionLog.cpp
components/ble/ServiceDiscovery.cpp
components/ble/NavigationService.cpp
components/ble/HeartRateService.cpp
Expand Down Expand Up @@ -591,6 +597,7 @@ set(INCLUDE_FILES

${CMAKE_CURRENT_BINARY_DIR}/src/Version.h
BootloaderVersion.h
Identity.h
logging/Logger.h
logging/NrfLogger.h
displayapp/DisplayApp.h
Expand Down Expand Up @@ -618,6 +625,10 @@ set(INCLUDE_FILES
displayapp/screens/Motion.h
displayapp/screens/Timer.h
displayapp/screens/Dice.h
displayapp/screens/Matrix.h
displayapp/screens/Badge.h
displayapp/screens/BadgeQr.h
displayapp/screens/IntrusionLog.h
displayapp/screens/Alarm.h
displayapp/Colors.h
displayapp/widgets/Counter.h
Expand Down Expand Up @@ -653,6 +664,7 @@ set(INCLUDE_FILES
components/ble/BatteryInformationService.h
components/ble/FSService.h
components/ble/ImmediateAlertService.h
components/ble/IntrusionLog.h
components/ble/ServiceDiscovery.h
components/ble/BleClient.h
components/ble/HeartRateService.h
Expand Down
9 changes: 9 additions & 0 deletions src/Identity.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

namespace Pinetime {
namespace Identity {
// Your handle. Shown on the NetRunner watch face and the Badge app, and used as the Bluetooth name.
// Note: companion apps such as Gadgetbridge look for a name starting with "InfiniTime" when pairing a new watch.
static constexpr const char* handle = "VOIDSEC";
}
}
202 changes: 202 additions & 0 deletions src/components/ble/IntrusionLog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#include "components/ble/IntrusionLog.h"

#include <algorithm>
#include <cstring>
#include "components/fs/FS.h"

using namespace Pinetime::Controllers;

IntrusionLog::IntrusionLog(FS& fs) : fs {fs} {
}

void IntrusionLog::Load() {
FileData data;
lfs_file_t file;
if (fs.FileOpen(&file, fileName, LFS_O_RDONLY) != LFS_ERR_OK) {
return;
}
const int read = fs.FileRead(&file, reinterpret_cast<uint8_t*>(&data), sizeof(data));
fs.FileClose(&file);
if (read != sizeof(data) || data.magic != magic || data.version != fileVersion || data.count > MaxEntries || data.head >= MaxEntries) {
return;
}
enabled = data.enabled != 0;
count = data.count;
head = data.head;
entries = data.entries;
}

void IntrusionLog::Save() {
FileData data;
taskENTER_CRITICAL();
data.magic = magic;
data.version = fileVersion;
data.enabled = enabled ? 1 : 0;
data.count = count;
data.head = head;
data.entries = entries;
dirty = false;
taskEXIT_CRITICAL();

lfs_file_t file;
if (fs.FileOpen(&file, fileName, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) != LFS_ERR_OK) {
dirty = true;
return;
}
fs.FileWrite(&file, reinterpret_cast<const uint8_t*>(&data), sizeof(data));
fs.FileClose(&file);
}

void IntrusionLog::SetEnabled(bool enable) {
enabled = enable;
dirty = true;
}

void IntrusionLog::Clear() {
taskENTER_CRITICAL();
count = 0;
committed = false;
dirty = true;
taskEXIT_CRITICAL();
}

IntrusionLog::Entry IntrusionLog::Get(size_t index) const {
Entry entry {};
taskENTER_CRITICAL();
if (index < count) {
entry = entries[(head + MaxEntries - index) % MaxEntries];
}
taskEXIT_CRITICAL();
return entry;
}

void IntrusionLog::FormatAddress(const Entry& entry, char* buffer) {
static constexpr char hex[] = "0123456789ABCDEF";
// NimBLE stores addresses little-endian, print them the usual way round
for (int i = 0; i < 6; i++) {
const uint8_t byte = entry.address[5 - i];
*buffer++ = hex[byte >> 4];
*buffer++ = hex[byte & 0x0F];
*buffer++ = i < 5 ? ':' : '\0';
}
}

const char* IntrusionLog::Describe(const Entry& entry) {
if ((entry.flags & Trusted) != 0) {
return "BONDED";
}
if ((entry.flags & EncryptFailed) != 0) {
return "PAIR FAILED";
}
if ((entry.flags & PairAttempt) != 0) {
return "PAIR ATTEMPT";
}
if ((entry.flags & NoBond) != 0) {
return "UNVERIFIED";
}
return "UNKNOWN";
}

void IntrusionLog::Commit() {
taskENTER_CRITICAL();
head = (head + 1) % MaxEntries;
count = std::min<uint8_t>(count + 1, MaxEntries);
entries[head] = current;
committed = true;
dirty = true;
taskEXIT_CRITICAL();
}

void IntrusionLog::OnConnect(const uint8_t* address, uint8_t addressType, bool knownBond, uint32_t time) {
// Nothing to log when disabled, or when the bonded phone reconnects with its identity address
connected = enabled && !knownBond;
pending = connected;
committed = false;
if (!connected) {
return;
}
current.time = time;
current.duration = 0;
std::memcpy(current.address, address, sizeof(current.address));
current.addressType = addressType;
current.flags = 0;
connectTick = xTaskGetTickCount();
}

bool IntrusionLog::OnPairingRequest() {
if (connected) {
Active().flags |= PairAttempt;
}
return false;
}

bool IntrusionLog::OnEncryptionChange(bool success, bool bonded) {
if (!connected) {
return false;
}
if (success && bonded) {
if (pending) {
// Our own phone: forget about it
connected = false;
pending = false;
} else {
// Encrypted after it was already reported, keep the record but mark it
Active().flags |= Trusted;
dirty = true;
}
return false;
}
if (!success) {
Active().flags |= EncryptFailed;
if (pending) {
pending = false;
current.flags |= Alerted;
Commit();
return true;
}
dirty = true;
}
return false;
}

bool IntrusionLog::ClassifyPending(bool hasBond) {
if (!pending) {
return false;
}
pending = false;
if (!hasBond) {
current.flags |= NoBond;
}
const bool alert = hasBond || (current.flags & (EncryptFailed | PairAttempt)) != 0;
if (alert) {
current.flags |= Alerted;
}
Commit();
return alert;
}

bool IntrusionLog::Classify(bool hasBond) {
// Give someone typing the passkey on their phone time to finish pairing
if (pending && (current.flags & PairAttempt) != 0) {
return false;
}
return ClassifyPending(hasBond);
}

bool IntrusionLog::OnDisconnect(bool hasBond) {
if (!connected) {
return false;
}
const uint32_t seconds = (xTaskGetTickCount() - connectTick) / configTICK_RATE_HZ;
const uint16_t duration = static_cast<uint16_t>(std::min<uint32_t>(std::max<uint32_t>(seconds, 1), UINT16_MAX));
current.duration = duration;
const bool alert = ClassifyPending(hasBond);
if (committed) {
taskENTER_CRITICAL();
entries[head].duration = duration;
taskEXIT_CRITICAL();
dirty = true;
}
connected = false;
return alert;
}
Loading
Loading