refactor(elixir): remove unused qr_code
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
# AprilTag Migration Summary
|
||||
|
||||
## Completed Changes
|
||||
|
||||
### 1. Database Migration ✅
|
||||
- Migrated from `qr_code` string field to `apriltag_id` integer field
|
||||
- Added constraint to ensure valid AprilTag IDs (0-586)
|
||||
- Created unique index for apriltag_id
|
||||
- Preserved old qr_code data as qr_code_old for rollback safety
|
||||
|
||||
### 2. Schema Updates ✅
|
||||
- Updated `StorageLocation` schema to use `apriltag_id` instead of `qr_code`
|
||||
- Added validation for AprilTag ID range (0-586)
|
||||
- Implemented auto-assignment of next available ID
|
||||
- Added unique constraint validation
|
||||
|
||||
### 3. Business Logic Refactoring ✅
|
||||
- Replaced `ComponentsElixir.QRCode` module with `ComponentsElixir.AprilTag` module
|
||||
- Updated inventory functions to use AprilTag IDs instead of QR code strings
|
||||
- Implemented AprilTag ID availability checking
|
||||
- Added bulk SVG generation functionality
|
||||
|
||||
### 4. UI/UX Improvements ✅
|
||||
- Replaced dropdown with 587 options with better UX:
|
||||
- Radio buttons for "Auto-assign" vs "Manual selection"
|
||||
- Number input for specific ID selection when manual mode selected
|
||||
- Shows available ID count and examples
|
||||
- Different interface for add vs edit forms
|
||||
- Updated templates to show AprilTag information instead of QR codes
|
||||
- Added download functionality for AprilTag SVGs
|
||||
|
||||
### 5. AprilTag Generation ✅
|
||||
- Created `ComponentsElixir.AprilTag` module for managing tag36h11 family
|
||||
- Generated all 587 placeholder SVG files with human-readable IDs
|
||||
- Added Mix task `mix apriltag.generate_all` for batch generation
|
||||
- SVG files served statically at `/apriltags/tag36h11_id_XXX.svg`
|
||||
|
||||
### 6. Event Handling ✅
|
||||
- Updated LiveView event handlers for AprilTag scanning/assignment
|
||||
- Added mode switching for manual vs automatic assignment
|
||||
- Implemented proper form state management for different modes
|
||||
|
||||
## Benefits Achieved
|
||||
|
||||
1. **Better UX**: No more 587-option dropdown menu
|
||||
2. **Future-Ready**: AprilTags designed for multi-tag detection scenarios
|
||||
3. **Robust**: 587 unique IDs provide ample space without conflicts
|
||||
4. **Maintainable**: Simpler integer ID system vs complex string encoding
|
||||
5. **Industry Standard**: AprilTags widely used in robotics/AR applications
|
||||
|
||||
## Current State
|
||||
|
||||
- ✅ Database schema updated
|
||||
- ✅ All 587 placeholder SVG files generated
|
||||
- ✅ UI forms updated with better UX
|
||||
- ✅ Business logic migrated to AprilTag system
|
||||
- ⏳ **Next**: Real AprilTag pattern generation (future enhancement)
|
||||
- ⏳ **Next**: Camera detection integration (future enhancement)
|
||||
|
||||
## Usage
|
||||
|
||||
### Generate AprilTag SVGs
|
||||
```bash
|
||||
mix apriltag.generate_all # Generate missing files
|
||||
mix apriltag.generate_all --force # Regenerate all files
|
||||
```
|
||||
|
||||
### Available AprilTag IDs
|
||||
- Range: 0-586 (tag36h11 family)
|
||||
- Auto-assignment picks next available ID
|
||||
- Manual assignment allows specific ID selection
|
||||
- Unique constraint prevents conflicts
|
||||
|
||||
### File Locations
|
||||
- SVG files: `priv/static/apriltags/tag36h11_id_XXX.svg`
|
||||
- URL pattern: `/apriltags/tag36h11_id_XXX.svg`
|
||||
- Placeholder pattern includes human-readable ID label
|
||||
|
||||
The system is now ready for use with AprilTags instead of QR codes! The placeholder SVGs will work perfectly for testing and development until we implement actual AprilTag pattern generation.
|
||||
@@ -1,233 +0,0 @@
|
||||
defmodule ComponentsElixir.QRCode do
|
||||
@moduledoc """
|
||||
QR Code generation and parsing for storage locations.
|
||||
|
||||
Provides functionality to generate QR codes for storage locations
|
||||
and parse them back to retrieve location information.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Generates a QR code data string for a storage location.
|
||||
|
||||
Format: SL:{level}:{qr_code}:{parent_qr_or_ROOT}
|
||||
|
||||
## Examples
|
||||
|
||||
iex> location = %StorageLocation{level: 1, qr_code: "ABC123", parent: nil}
|
||||
iex> ComponentsElixir.QRCode.generate_qr_data(location)
|
||||
"SL:1:ABC123:ROOT"
|
||||
|
||||
iex> parent = %StorageLocation{qr_code: "SHELF1"}
|
||||
iex> drawer = %StorageLocation{level: 2, qr_code: "DRAW01", parent: parent}
|
||||
iex> ComponentsElixir.QRCode.generate_qr_data(drawer)
|
||||
"SL:2:DRAW01:SHELF1"
|
||||
"""
|
||||
def generate_qr_data(storage_location) do
|
||||
parent_code =
|
||||
case storage_location.parent do
|
||||
nil -> "ROOT"
|
||||
parent -> parent.qr_code
|
||||
end
|
||||
|
||||
"SL:#{storage_location.level}:#{storage_location.qr_code}:#{parent_code}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses a QR code string and extracts components.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> ComponentsElixir.QRCode.parse_qr_data("SL:1:ABC123:ROOT")
|
||||
{:ok, %{level: 1, code: "ABC123", parent: "ROOT"}}
|
||||
|
||||
iex> ComponentsElixir.QRCode.parse_qr_data("invalid")
|
||||
{:error, :invalid_format}
|
||||
"""
|
||||
def parse_qr_data(qr_string) do
|
||||
case String.split(qr_string, ":") do
|
||||
["SL", level_str, code, parent] ->
|
||||
case Integer.parse(level_str) do
|
||||
{level, ""} ->
|
||||
{:ok, %{level: level, code: code, parent: parent}}
|
||||
_ ->
|
||||
{:error, :invalid_level}
|
||||
end
|
||||
_ ->
|
||||
{:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates if a string looks like a storage location QR code.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> ComponentsElixir.QRCode.valid_storage_qr?("SL:1:ABC123:ROOT")
|
||||
true
|
||||
|
||||
iex> ComponentsElixir.QRCode.valid_storage_qr?("COMP:12345")
|
||||
false
|
||||
"""
|
||||
def valid_storage_qr?(qr_string) do
|
||||
case parse_qr_data(qr_string) do
|
||||
{:ok, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a printable label data structure for a storage location.
|
||||
|
||||
This could be used to generate PDF labels or send to a label printer.
|
||||
"""
|
||||
def generate_label_data(storage_location) do
|
||||
qr_data = generate_qr_data(storage_location)
|
||||
|
||||
%{
|
||||
qr_code: qr_data,
|
||||
name: storage_location.name,
|
||||
path: storage_location.path,
|
||||
level: storage_location.level,
|
||||
description: storage_location.description
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates multiple QR codes for disambiguation testing.
|
||||
|
||||
This is useful for testing multi-QR detection scenarios.
|
||||
"""
|
||||
def generate_test_codes(storage_locations) when is_list(storage_locations) do
|
||||
Enum.map(storage_locations, &generate_qr_data/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a QR code image (PNG) for a storage location.
|
||||
|
||||
Returns the binary PNG data that can be saved to disk or served directly.
|
||||
|
||||
## Options
|
||||
|
||||
- `:size` - The size of the QR code image in pixels (default: 200)
|
||||
- `:background` - Background color as `{r, g, b}` tuple (default: white)
|
||||
- `:foreground` - Foreground color as `{r, g, b}` tuple (default: black)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> location = %StorageLocation{level: 1, qr_code: "ABC123"}
|
||||
iex> {:ok, png_data} = ComponentsElixir.QRCode.generate_qr_image(location)
|
||||
iex> File.write!("/tmp/qr_code.png", png_data)
|
||||
|
||||
"""
|
||||
def generate_qr_image(storage_location, _opts \\ []) do
|
||||
qr_data = generate_qr_data(storage_location)
|
||||
|
||||
qr_data
|
||||
|> QRCode.create()
|
||||
|> QRCode.render(:png)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates and saves a QR code image to the specified file path.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> location = %StorageLocation{level: 1, qr_code: "ABC123"}
|
||||
iex> ComponentsElixir.QRCode.save_qr_image(location, "/tmp/qr_code.png")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def save_qr_image(storage_location, file_path, opts \\ []) do
|
||||
case generate_qr_image(storage_location, opts) do
|
||||
{:ok, png_data} ->
|
||||
# Ensure directory exists
|
||||
file_path
|
||||
|> Path.dirname()
|
||||
|> File.mkdir_p!()
|
||||
|
||||
File.write!(file_path, png_data)
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a QR code image URL for serving via Phoenix static files.
|
||||
|
||||
This function generates the QR code image and saves it to the static directory,
|
||||
returning a URL that can be used in templates.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> location = %StorageLocation{id: 123, qr_code: "ABC123"}
|
||||
iex> ComponentsElixir.QRCode.get_qr_image_url(location)
|
||||
"/qr_codes/storage_location_123.png"
|
||||
|
||||
"""
|
||||
def get_qr_image_url(storage_location, opts \\ []) do
|
||||
filename = "storage_location_#{storage_location.id}.png"
|
||||
file_path = Path.join([Application.app_dir(:components_elixir, "priv/static/user_generated/qr_codes"), filename])
|
||||
|
||||
# Generate and save the image if it doesn't exist or if regeneration is forced
|
||||
force_regenerate = Keyword.get(opts, :force_regenerate, false)
|
||||
|
||||
if force_regenerate || !File.exists?(file_path) do
|
||||
case save_qr_image(storage_location, file_path, opts) do
|
||||
:ok -> "/user_generated/qr_codes/#{filename}"
|
||||
{:error, _reason} -> nil
|
||||
end
|
||||
else
|
||||
"/user_generated/qr_codes/#{filename}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates QR code images for multiple storage locations (bulk generation).
|
||||
|
||||
Returns a list of results indicating success or failure for each location.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> locations = [location1, location2, location3]
|
||||
iex> ComponentsElixir.QRCode.bulk_generate_images(locations)
|
||||
[
|
||||
{:ok, "/qr_codes/storage_location_1.png"},
|
||||
{:ok, "/qr_codes/storage_location_2.png"},
|
||||
{:error, "Failed to generate for location 3"}
|
||||
]
|
||||
|
||||
"""
|
||||
def bulk_generate_images(storage_locations, opts \\ []) do
|
||||
# Use Task.async_stream for concurrent generation with back-pressure
|
||||
storage_locations
|
||||
|> Task.async_stream(
|
||||
fn location ->
|
||||
case get_qr_image_url(location, Keyword.put(opts, :force_regenerate, true)) do
|
||||
nil -> {:error, "Failed to generate QR code for location #{location.id}"}
|
||||
url -> {:ok, url}
|
||||
end
|
||||
end,
|
||||
timeout: :infinity,
|
||||
max_concurrency: System.schedulers_online() * 2
|
||||
)
|
||||
|> Enum.map(fn {:ok, result} -> result end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cleans up QR code images for deleted storage locations.
|
||||
|
||||
Should be called when storage locations are deleted to prevent orphaned files.
|
||||
"""
|
||||
def cleanup_qr_image(storage_location_id) do
|
||||
filename = "storage_location_#{storage_location_id}.png"
|
||||
file_path = Path.join([Application.app_dir(:components_elixir, "priv/static/user_generated/qr_codes"), filename])
|
||||
|
||||
if File.exists?(file_path) do
|
||||
File.rm(file_path)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user