Skip to main content

CrowdTalkie Goes Offline: LoRa Integration, Command Center, and Twin Cities Rapid Response

10 min read By Craig Merry
CrowdTalkie LoRa Meshtastic MeshCore Incident Command Command Center OCR Mesh Networking Privacy Offline-First Protest Tools

When we built CrowdTalkie, we assumed internet connectivity would be available most of the time. Web Bluetooth was our fallback for when networks failed. But real-world deployments taught us that Bluetooth’s 30-meter range creates fragile meshes that break when crowds disperse or when people move between buildings.

The solution: LoRa radio integration. LoRa (Long Range) radios can transmit kilometers with minimal power, forming resilient meshes that work through walls, across neighborhoods, and during complete infrastructure blackouts.

The LoRa Bridge Architecture

Rather than pick a single LoRa protocol, CrowdTalkie implements a unified bridge that supports both major mesh ecosystems:

Meshtastic Integration

Meshtastic is the established player - open-source firmware for common LoRa boards like the LilyGo T-Beam and Heltec V3. It uses Protocol Buffers for message encoding and supports up to 8 encrypted channels per device.

CrowdTalkie connects to Meshtastic devices via Web Bluetooth:

// Connecting to a Meshtastic radio
const device = await connectMeshtastic()
// device: { name: "T-Beam", region: "US (915 MHz)", batteryLevel: 87 }

Messages route through Meshtastic’s PRIVATE_APP portnum, keeping CrowdTalkie traffic separate from regular Meshtastic chat.

MeshCore Integration

MeshCore takes a different approach - purpose-built for high-throughput mesh networking with native binary protocols. It’s newer but offers better performance for real-time coordination.

Both backends implement the same unified interface:

type LoRaBackend = 'meshtastic' | 'meshcore'

interface LoRaDevice {
  id: string
  name: string
  backend: LoRaBackend
  firmwareVersion: string
  batteryLevel: number
  hasGps: boolean
  region: string  // "United States (915 MHz)", "Europe 868 MHz", etc.
  isConnected: boolean
}

The bridge handles backend differences transparently. Send a message, and it encodes appropriately for whichever radio is connected.

Twin Cities Rapid Response Features

The impetus for deep LoRa integration came from community organizers in the Twin Cities requesting features for rapid response scenarios. When a vehicle is spotted multiple times across a demonstration, that information needs to propagate instantly - even when cell networks are overloaded.

Vehicle Sighting Reports

A dedicated vehicle sighting form captures structured data:

  • License plate with confidence level (full/partial/none)
  • Vehicle type: sedan, SUV, van, truck, unmarked, other
  • Color from predefined palette
  • Direction of travel: 8-point compass + stationary
  • Following status: flag if the reporter is trailing the vehicle
  • Photos with automatic processing
interface VehicleSighting {
  plate?: string
  plateConfidence: 'full' | 'partial' | 'none'
  vehicleType?: 'sedan' | 'suv' | 'van' | 'truck' | 'unmarked' | 'other'
  vehicleColor?: string
  direction?: 'N' | 'NE' | 'E' | 'SE' | 'S' | 'SW' | 'W' | 'NW' | 'stationary'
  isFollowing?: boolean
  photos?: SightingPhoto[]
  location: GeoLocation
}

Photo Capture with OCR

The killer feature: automatic license plate detection. When you snap a photo, CrowdTalkie runs Tesseract.js client-side to extract plate characters.

const result = await readPlateWithPreprocess(imageBlob)
// result: { text: "ABC1234", confidence: 0.87, boundingBox: {...} }

The preprocessing pipeline:

  1. Scale images to 1200px max for faster processing
  2. Convert to grayscale
  3. Boost contrast (1.5x) to separate plate from background
  4. Run Tesseract with English character recognition
  5. Post-process to extract plate-like patterns (2-10 alphanumeric chars)

OCR runs entirely in-browser - no server ever sees the image.

Photo Geo-Tagging

Photos often contain GPS coordinates in EXIF metadata. CrowdTalkie extracts them automatically using exifr:

const metadata = await extractPhotoMetadata(imageFile)
// metadata: { location: { lat: 44.9778, lng: -93.2650, accuracy: 5 },
//             timestamp: Date, make: "Apple", model: "iPhone 15" }

If the photo lacks GPS data (screenshot, edited image, older camera), the app falls back to the device’s current location with user permission.

LoRa Message Protocol for Vehicles

Vehicle sightings need to traverse the LoRa mesh efficiently. We defined compact binary message types:

type LoRaMessageType =
  | 'text'           // Regular chat
  | 'poi'            // Point of interest
  | 'vehicle'        // Vehicle sighting (0x10)
  | 'plate_lookup'   // Cross-zone plate query (0x11)
  | 'plate_response' // Query response (0x12)
  | 'handoff_req'    // Zone handoff request (0x13)
  | 'handoff_ack'    // Handoff acknowledgment (0x14)

Vehicle sightings encode to a compact format for LoRa’s limited bandwidth:

interface LoRaVehicleSighting {
  p?: string      // Plate
  v?: number      // Vehicle type (0-5 enum)
  c?: number      // Color index
  d?: number      // Direction (0-8 enum)
  f?: boolean     // Is following
  la: number      // Latitude (6 decimals)
  lo: number      // Longitude (6 decimals)
  de?: string     // Description (max 50 chars)
}

Zone Handoff Protocol

Large demonstrations span multiple neighborhoods. Each area may have its own CrowdTalkie room. When a vehicle leaves one zone and enters another, the zone handoff protocol ensures continuity:

  1. Follower in Zone A spots vehicle heading east toward Zone B
  2. Sends handoff_req message with vehicle details and boundary street
  3. Zone B room receives the request, displays alert with vehicle info
  4. Zone B coordinator sends handoff_ack to confirm receipt
  5. Zone A follower sees confirmation - handoff complete
interface LoRaHandoffRequest {
  r: string              // Request ID
  v: LoRaVehicleSighting // Vehicle details
  z: string              // Source zone name
  st?: string            // Street name at boundary
}

This works over LoRa or regular P2P, whichever path is available.

Multi-Language Support

The Twin Cities community requested Spanish language support. CrowdTalkie now includes full i18n using react-i18next:

  • English and Spanish translations for all UI elements
  • Vehicle types, colors, and directions translated
  • Accessibility labels translated
  • Language selector in settings

The interface respects the user’s browser language preference but allows manual override.

Alert Sounds

When high-priority messages arrive - vehicle sightings, blocked routes, emergency reports - an audio alert plays. This uses the Web Audio API with user-configurable volume and the ability to disable entirely:

// Priority messages trigger alert
if (message.type === 'vehicle_sighting' || message.priority === 'high') {
  playAlertSound()
}

The sound is a brief synthesized tone, not a jarring alarm. It gets attention without causing panic.

The Command Center: Incident Command for Crowds

The most significant architectural addition is the Command Center - a hierarchical coordination layer inspired by the Incident Command System (ICS) used by emergency responders.

How It Works

Large actions often span multiple neighborhoods, each with their own coordination needs. The Command Center lets organizers create a parent “command room” that oversees multiple “sector rooms”:

Command Room (Citywide Coordination)
├── Sector: Uptown
├── Sector: Powderhorn
├── Sector: Longfellow
└── Sector: Downtown

The commander sees a unified dashboard with:

  • Map visualization of all sectors with boundaries
  • Participant counts per sector
  • Aggregated POI reports from all zones
  • Pending escalations requiring attention

Remote Commands

Commanders can issue authenticated remote commands to any sector:

type RemoteCommandPayload =
  | { type: 'pause_room' }           // Freeze comms temporarily
  | { type: 'resume_room' }          // Resume operations
  | { type: 'kick_participant'; targetPeerId: string }
  | { type: 'update_mode'; mode: 'mic' | 'chat'; value: string }
  | { type: 'update_boundary'; centerLat; centerLng; radiusMeters }
  | { type: 'set_alert_status'; status: 'active' | 'alert' | 'moving' }
  | { type: 'clear_all_pois' }

Every command is cryptographically signed with Ed25519. Sector rooms verify the commander’s public key before executing. No impersonation possible.

Escalation Protocol

When a sector coordinator spots something that needs citywide attention, they can escalate:

  1. Coordinator tags a message/POI with priority level (routine/urgent/critical)
  2. Escalation appears in Command Center dashboard
  3. Commander acknowledges or broadcasts response to all sectors
  4. Sector receives feedback that the escalation was seen

This mirrors how ICS handles information flow: ground teams report upward, commanders broadcast directives downward.

Broadcast Messages

The Command Center can send broadcasts to all sectors or targeted subsets:

  • Priority levels: Routine (green), Urgent (yellow), Critical (red)
  • Target selection: All sectors, or pick specific zones
  • Audio alerts: Critical broadcasts trigger sound notifications in receiving sectors

Deputy Sharing

Commanders can share their Command Center with deputies:

  • Generate a “deputy link” that grants command access
  • Deputies see the same dashboard and can issue commands
  • All actions are signed with the deputy’s key (attribution preserved)
  • Link can be revoked by generating a new one

Sector Management

From the Command Center, organizers can:

  • Create new sectors with name, location, and radius
  • Adjust sector boundaries as the situation evolves
  • Assign sector leads from active participants
  • Monitor sector health via participant count and activity

The system suggests boundary adjustments when participant density shifts - if most people in a sector cluster near one edge, it may recommend expanding that direction.

Why This Matters

Traditional protest coordination often breaks down at scale. With 50 people, shouting works. With 5,000 people spread across a city, you need structure.

The Command Center provides that structure without sacrificing CrowdTalkie’s decentralized privacy model:

  • No central server - command/sector relationships are P2P
  • Cryptographic authority - only verified commanders can issue orders
  • Resilient hierarchy - if command goes offline, sectors continue independently
  • LoRa compatibility - commands traverse the LoRa mesh when internet fails

It’s Incident Command without the bureaucracy.

What This Enables

With these features, CrowdTalkie supports scenarios that were previously impossible:

Complete infrastructure failure: LoRa radios form a mesh covering square kilometers. Even if cell towers go down and WiFi dies, coordinated communication continues.

Vehicle tracking across zones: A suspicious vehicle reported in Uptown appears on dashboards in Powderhorn and Longfellow before it arrives. Each zone knows what to look for.

Photo evidence with provenance: Every photo has GPS coordinates (EXIF or device), timestamp, and cryptographic signature from the sender. The chain of custody is clear.

Bandwidth-constrained operation: LoRa moves kilobits per second. The compact binary protocol ensures vehicle alerts traverse the mesh in seconds, not minutes.

Hardware Requirements

To use LoRa features, you need a compatible radio:

Meshtastic-compatible devices:

  • LilyGo T-Beam (recommended - built-in GPS)
  • Heltec LoRa 32 V3
  • RAK WisBlock

MeshCore-compatible devices:

  • MeshCore boards with BLE support

Devices cost $25-60. One radio per room is sufficient - it bridges the room’s P2P mesh to the LoRa mesh.

Privacy Preserved

The LoRa integration maintains CrowdTalkie’s core privacy guarantees:

  • End-to-end encryption: Messages encrypt before leaving the device with room-derived AES-256-GCM keys
  • Per-room identity: Your LoRa peerId in Room A differs from Room B
  • No persistent identity: When the session ends, the identity vanishes
  • Local processing: OCR and geo-tagging happen client-side; no cloud services

LoRa relay nodes (other Meshtastic/MeshCore devices in range) see only encrypted blobs.

Try It

CrowdTalkie with LoRa integration is live at crowdtalkie.com. The Field Manual documents the new features.

If you’re organizing in an area with infrastructure concerns, consider deploying a few LoRa radios. They’re inexpensive, solar-chargeable, and extend your coordination range from 30 meters (Bluetooth) to several kilometers.

The mesh just got a lot more resilient.


CrowdTalkie is open source and part of the Antifascist Fun Brigade toolkit. Contributions welcome.