npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

gan-i3-356-bluetooth

v0.0.5

Published

Library for connecting to and interacting with Bluetooth-enabled Rubik's cubes (GAN)

Readme

Rubik's Cube Bluetooth Library

This library provides an interface for connecting to and interacting with Bluetooth-enabled Rubik's cubes, specifically the GAN 356 i3 3x3 Bluetooth Smart Cube. It allows you to track moves, monitor cube state, and receive gyroscope data. The package is fully minified for optimal performance and minimal size.

[!NOTE] Credit to CSTimer for the original code that this project is based on. It has been refactored and made more modular for easier use.

Installation

npm install gan-i3-356-bluetooth

Getting Started

Using the BTCube class

import { BTCube } from 'gan-i3-356-bluetooth';

// Create a new BTCube instance
const btCube = new BTCube();

// Initialize the connection to the cube
btCube.init()
  .then(() => console.log('Connected to cube'))
  .catch(err => console.error('Failed to connect:', err));

// Set a callback for cube state changes
btCube.setCallback((state, moves, timestamps, deviceName) => {
  console.log('Cube state:', state);
  console.log('Moves:', moves);
  console.log('Device:', deviceName);
});

// Disconnect from the cube when done
btCube.stop();

Direct access to GanCube

import { GanCube } from 'gan-i3-356-bluetooth';

// Create a new GanCube instance
const ganCube = new GanCube();

// The GanCube class is typically used through BTCube,
// but can be accessed directly for advanced usage

TODO

  • [x] Stop using window events and instead use a custom event emitter
  • [x] Setup proper types for the events
  • [x] add a cube "state" number
  • [ ] Fix the glitchy Gyro data
  • [ ] Web interface needs to grab the battery data sooner
  • [ ] EsLint + prettier need to be configured here
  • [ ] Convert mathlib over to typescript + use a class instead

How to spin up the example

Simply run pnpm i and pnpm dev to start the example app (chrome does not like localhost having bluetooth permissions, so I dev with a HTTPS CloudFlare tunnel, using cloudflared tunnel --url http://localhost:3000)

Class-Based Event System

The library uses an object-oriented event system where each cube instance emits its own events. This replaces the previous window-based events with a more encapsulated approach.

Using the Event System

import { BTCube } from 'gan-i3-356-bluetooth';

// Create a cube instance
const btCube = new BTCube();

// Listen for cube state changes
btCube.on('cubeStateChanged', (data) => {
  const { facelet, move, corners, edges, timestamp } = data;
  
  // facelet: String representation of the cube state (e.g., "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB")
  // move: The move that was just performed (e.g., "R'")
  // corners: Array representing the corner pieces
  // edges: Array representing the edge pieces
  // timestamp: Time when the move was made
  
  console.log('Cube state changed:', facelet);
  console.log('Move performed:', move);
});

// Listen for gyroscope data
btCube.on('gyroData', (data) => {
  const { x, y, z, timestamp } = data;
  
  // x, y, z: Gyroscope readings for each axis
  // timestamp: Time when the data was captured
  
  console.log('Gyroscope data:', { x, y, z });
});

// Listen for move events
btCube.on('move', (data) => {
  const { move, time } = data;
  
  // move: The move that was performed (e.g., "R'")
  // time: Time taken to perform the move
  
  console.log('Move detected:', move, 'Time:', time);
});

// Listen for solved state
btCube.on('cubeSolved', () => {
  console.log('Cube solved!');
  // Trigger celebration or next steps
});

// Listen for unsolved state
btCube.on('unSolved', () => {
  console.log('Cube is not solved');
});

// Remove event listeners when done
btCube.off('cubeStateChanged', yourListenerFunction);

// Or clear all listeners
btCube.clearAllListeners();

Useful Methods

Logging Cube State

You can manually log the current state of the cube:

// Import the GanCube module if needed
import { btCube } from './code.js';

// Get the cube instance
const cube = btCube.getCube();

// Log the current state
const state = cube.logCubeState();
console.log(state);

Disconnecting from the Cube

To properly disconnect from the cube:

btCube.stop();

Example Usage in React

import React, { useEffect, useState, useRef } from 'react';
import { BTCube } from 'gan-i3-356-bluetooth';

function CubeApp() {
  const btCubeRef = useRef(null);
  const [btCube, setBtCube] = useState(null);
  const [cubeState, setCubeState] = useState('');
  const [lastMove, setLastMove] = useState('');
  const [isSolved, setIsSolved] = useState(false);
  
  // Initialize the cube instance
  useEffect(() => {
    if (!btCubeRef.current) {
      btCubeRef.current = new BTCube();
      setBtCube(btCubeRef.current);
    }
    
    return () => {
      // Disconnect from cube when component unmounts
      if (btCubeRef.current) {
        btCubeRef.current.stop();
      }
    };
  }, []);
  
  // Set up event listeners
  useEffect(() => {
    if (!btCube) return;
    
    // Listen for cube state changes
    const handleStateChange = (data) => {
      setCubeState(data.facelet);
      if (data.move?.length <= 2) {
        setLastMove(data.move);
      }
    };
    
    // Listen for solved state
    const handleSolved = () => {
      setIsSolved(true);
    };
    
    // Listen for unsolved state
    const handleUnsolved = () => {
      setIsSolved(false);
    };
    
    // Register event listeners on the BTCube instance
    btCube.on('cubeStateChanged', handleStateChange);
    btCube.on('cubeSolved', handleSolved);
    btCube.on('unSolved', handleUnsolved);
    
    return () => {
      // Remove event listeners when component unmounts
      btCube.off('cubeStateChanged', handleStateChange);
      btCube.off('cubeSolved', handleSolved);
      btCube.off('unSolved', handleUnsolved);
    };
  }, [btCube]);
  
  const connectCube = () => {
    if (!btCube) return;
    
    btCube.init()
      .then(() => console.log('Connected to cube'))
      .catch(err => console.error('Failed to connect:', err));
  };
  
  return (
    <div>
      <button onClick={connectCube}>Connect to Cube</button>
      <div>Cube State: {cubeState}</div>
      <div>Last Move: {lastMove}</div>
      <div>Solved: {isSolved ? 'Yes' : 'No'}</div>
    </div>
  );
}

Technical Notes

  • The cube communicates using Bluetooth Low Energy (BLE)
  • This has only been tested on the "GAN V2 protocol"
  • Move notation follows standard Rubik's cube notation (U, R, F, D, L, B with ' for counterclockwise)
  • The facelet representation uses the following convention:
    • U: Up face (yellow)
    • R: Right face (red)
    • F: Front face (blue)
    • D: Down face (white)
    • L: Left face (orange)
    • B: Back face (green)

Publishing

If you're maintaining this package, here's how to publish a new version:

  1. Update the version in package.json
  2. Build the library:
    pnpm run build:lib
  3. Publish to npm:
    npm publish

License

This project is licensed under the MIT License.