feat: use AprilTag instead of QR code

This commit is contained in:
Schuwi
2025-09-14 18:52:24 +02:00
parent 788ad54724
commit 589c9964aa
600 changed files with 12814 additions and 122 deletions

View File

@@ -7,13 +7,14 @@ defmodule ComponentsElixir.Inventory.StorageLocation do
"""
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias ComponentsElixir.Inventory.{StorageLocation, Component}
schema "storage_locations" do
field :name, :string
field :description, :string
field :qr_code, :string
field :apriltag_id, :integer
field :is_active, :boolean, default: true
# Computed/virtual fields - not stored in database
@@ -31,13 +32,14 @@ defmodule ComponentsElixir.Inventory.StorageLocation do
@doc false
def changeset(storage_location, attrs) do
storage_location
|> cast(attrs, [:name, :description, :parent_id, :is_active])
|> cast(attrs, [:name, :description, :parent_id, :is_active, :apriltag_id])
|> validate_required([:name])
|> validate_length(:name, min: 1, max: 100)
|> validate_length(:description, max: 500)
|> validate_apriltag_id()
|> foreign_key_constraint(:parent_id)
|> validate_no_circular_reference()
|> put_qr_code()
|> put_apriltag_id()
end
# Prevent circular references (location being its own ancestor)
@@ -79,19 +81,24 @@ defmodule ComponentsElixir.Inventory.StorageLocation do
end
@doc """
Returns the QR code format for this storage location.
Format: SL:{level}:{qr_code}:{parent_qr_or_ROOT}
Returns the AprilTag format for this storage location.
Returns the AprilTag ID that corresponds to this location.
"""
def qr_format(storage_location, parent \\ nil) do
parent_code = if parent, do: parent.qr_code, else: "ROOT"
"SL:#{storage_location.level}:#{storage_location.qr_code}:#{parent_code}"
def apriltag_format(storage_location) do
storage_location.apriltag_id
end
# Private functions for changeset processing
defp put_qr_code(changeset) do
case get_field(changeset, :qr_code) do
nil -> put_change(changeset, :qr_code, generate_qr_code())
defp validate_apriltag_id(changeset) do
changeset
|> validate_number(:apriltag_id, greater_than_or_equal_to: 0, less_than_or_equal_to: 586)
|> unique_constraint(:apriltag_id, message: "AprilTag ID is already in use")
end
defp put_apriltag_id(changeset) do
case get_field(changeset, :apriltag_id) do
nil -> put_change(changeset, :apriltag_id, get_next_available_apriltag_id())
_ -> changeset
end
end
@@ -118,16 +125,22 @@ defmodule ComponentsElixir.Inventory.StorageLocation do
"#{compute_path(parent)}/#{name}"
end
defp generate_qr_code do
# Generate a unique 6-character alphanumeric code
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
defp get_next_available_apriltag_id do
# Get all used AprilTag IDs
used_ids = ComponentsElixir.Repo.all(
from sl in ComponentsElixir.Inventory.StorageLocation,
where: not is_nil(sl.apriltag_id),
select: sl.apriltag_id
)
1..6
|> Enum.map(fn _ ->
chars
|> String.graphemes()
|> Enum.random()
end)
|> Enum.join()
# Find the first available ID (0-586)
0..586
|> Enum.find(&(&1 not in used_ids))
|> case do
nil ->
# All IDs are used - this should be handled at the application level
raise "All AprilTag IDs are in use"
id -> id
end
end
end