Skip to content

Commit

Permalink
add OLED Display modules (#22)
Browse files Browse the repository at this point in the history
* add OLED Display modules

* add test

* add size and resolution

* typo

* change name size to display_width

* fix format
  • Loading branch information
Anshgrover23 authored Jan 6, 2025
1 parent e9e9df8 commit a3f1f9c
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 0 deletions.
Binary file modified bun.lockb
Binary file not shown.
79 changes: 79 additions & 0 deletions lib/db/derivedtables/oled_display.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { DerivedTableSpec } from "./types"
import type { KyselyDatabaseInstance } from "../kysely-types"
import { extractMinQPrice } from "lib/util/extract-min-quantity-price"

interface OLEDDisplay {
lcsc: number
mfr: string
description: string
stock: number
price1: number | null
in_stock: boolean
package?: string
protocol?: string
display_width?: string
pixel_resolution?: string
}

export const oledDisplayTableSpec: DerivedTableSpec<OLEDDisplay> = {
tableName: "oled_display",
extraColumns: [
{ name: "package", type: "text" },
{ name: "protocol", type: "text" },
{ name: "display_width", type: "text" },
{ name: "pixel_resolution", type: "text" },
],

listCandidateComponents(db: KyselyDatabaseInstance) {
return db
.selectFrom("components")
.innerJoin("categories", "components.category_id", "categories.id")
.selectAll()
.where((eb) => eb("description", "like", "%OLED Display%"))
},

mapToTable(components) {
return components.map((c) => {
try {
const extraData = c.extra ? JSON.parse(c.extra) : {}
const attrs = extraData.attributes || {}
// Extract protocol from description or interface attribute
let protocol
if (c.description.includes("I2C")) {
protocol = "I2C"
} else if (attrs.Interface) {
protocol = attrs.Interface
}
// Extract display_width and resolution from description
let display_width = undefined
let pixel_resolution = undefined
const description = c.description || ""
// Extract resolution (e.g., "128x64")
const resMatch = description.match(/(\d+x\d+)/)
if (resMatch) {
pixel_resolution = resMatch[1]
}
// Extract display_width (e.g., "0.96")
const widthMatch = description.match(/\s(\d+\.\d+)\s/)
if (widthMatch) {
display_width = widthMatch[1]
}

return {
lcsc: Number(c.lcsc),
mfr: String(c.mfr || ""),
description: String(description),
stock: Number(c.stock || 0),
price1: extractMinQPrice(c.price),
in_stock: Boolean((c.stock || 0) > 0),
package: String(c.package || ""),
protocol: protocol || undefined,
display_width,
pixel_resolution,
}
} catch (e) {
return null
}
})
},
}
1 change: 1 addition & 0 deletions routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default withWinterSpec({
<a href="/led_drivers/list">LED Drivers</a>
<a href="/mosfets/list">Mosfets</a>
<a href="/led_with_ic/list">LED with ICs</a>
<a href="/oled_display/list"> OLED Displays Modules</a>
<a href="/led_segment_display/list">LED Segment Display Modules</a>
</div>
</div>,
Expand Down
2 changes: 2 additions & 0 deletions routes/oled_display/list.json.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import list from "./list"
export default list
230 changes: 230 additions & 0 deletions routes/oled_display/list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import { Table } from "lib/ui/Table"
import { withWinterSpec } from "lib/with-winter-spec"
import { z } from "zod"
import { formatPrice } from "lib/util/format-price"

export default withWinterSpec({
auth: "none",
methods: ["GET", "POST"],
commonParams: z.object({
json: z.boolean().optional(),
package: z.string().optional(),
protocol: z.string().optional(),
display_width: z.string().optional(),
pixel_resolution: z.string().optional(),
mfr: z.string().optional(),
description: z.string().optional(),
}),
jsonResponse: z.object({
oled_displays: z.array(
z.object({
lcsc: z.number(),
mfr: z.string(),
package: z.string(),
description: z.string(),
stock: z.number(),
price1: z.number(),
protocol: z.string().optional(),
display_width: z.string().optional(),
pixel_resolution: z.string().optional(),
}),
),
}),
} as const)(async (req, ctx) => {
const params = req.commonParams
const limit = 100
const search = "OLED Display"
const searchPattern = `%${search}%`

let query = ctx.db
.selectFrom("oled_display")
.select([
"lcsc",
"mfr",
"package",
"description",
"stock",
"price1",
"protocol",
"display_width",
"pixel_resolution",
] as const)
.limit(limit)
.orderBy("stock", "desc")
.where("stock", ">", 0)
.where((eb) =>
eb("description", "like", searchPattern)
.or("mfr", "like", searchPattern)
.or(
search.match(/^\d+$/)
? eb("lcsc", "=", parseInt(search))
: eb("description", "like", searchPattern),
),
)

// Add filters for each column
if (params.package) {
query = query.where("package", "=", params.package)
}
if (params.protocol) {
query = query.where("protocol", "=", params.protocol)
}
if (params.display_width) {
query = query.where("display_width", "=", params.display_width)
}
if (params.pixel_resolution) {
query = query.where("pixel_resolution", "=", params.pixel_resolution)
}
if (params.mfr) {
query = query.where("mfr", "like", `%${params.mfr}%`)
}
if (params.description) {
query = query.where("description", "like", `%${params.description}%`)
}

const fullComponents = await query.execute()
const components = fullComponents.map((c) => ({
lcsc: c.lcsc,
mfr: c.mfr,
package: c.package,
description: c.description,
stock: c.stock,
price: c.price1,
protocol: c.protocol,
display_width: c.display_width,
pixel_resolution: c.pixel_resolution,
}))

if (ctx.isApiRequest) {
return ctx.json({
oled_displays: fullComponents
.map((c) => ({
lcsc: c.lcsc ?? 0,
mfr: c.mfr ?? "",
package: c.package ?? "",
description: c.description ?? "",
stock: c.stock ?? 0,
price1: c.price1 ?? 0,
protocol: c.protocol ?? undefined,
display_width: c.display_width ?? undefined,
pixel_resolution: c.pixel_resolution ?? undefined,
}))
.filter((c) => c.lcsc !== 0 && c.package !== ""),
})
}

const protocols = await ctx.db
.selectFrom("oled_display")
.select("protocol")
.distinct()
.orderBy("protocol")
.execute()

const packages = await ctx.db
.selectFrom("oled_display")
.select("package")
.distinct()
.orderBy("package")
.execute()

const widths = await ctx.db
.selectFrom("oled_display")
.select("display_width")
.distinct()
.orderBy("display_width")
.execute()

const resolutions = await ctx.db
.selectFrom("oled_display")
.select("pixel_resolution")
.distinct()
.orderBy("pixel_resolution")
.execute()

return ctx.react(
<div>
<h2>OLED Displays</h2>
<form method="GET" className="flex flex-row gap-4">
<div>
<label>Package: </label>
<select name="package" className="border px-2 py-1 rounded">
<option value="">All</option>
{packages.map((p) => (
<option
key={p.package}
value={p.package ?? ""}
selected={p.package === params.package}
>
{p.package || "-"}
</option>
))}
</select>
</div>
<div>
<label>Protocol: </label>
<select name="protocol" className="border px-2 py-1 rounded">
<option value="">All</option>
{protocols.map((p) => (
<option
key={p.protocol}
value={p.protocol ?? ""}
selected={p.protocol === params.protocol}
>
{p.protocol || "N/A"}
</option>
))}
</select>
</div>
<div>
<label>Display Width: </label>
<select name="display_width" className="border px-2 py-1 rounded">
<option value="">All</option>
{widths.map((w) => (
<option
key={w.display_width}
value={w.display_width ?? ""}
selected={w.display_width === params.display_width}
>
{w.display_width || "N/A"}
</option>
))}
</select>
</div>
<div>
<label>Resolution: </label>
<select name="pixel_resolution" className="border px-2 py-1 rounded">
<option value="">All</option>
{resolutions.map((r) => (
<option
key={r.pixel_resolution}
value={r.pixel_resolution ?? ""}
selected={r.pixel_resolution === params.pixel_resolution}
>
{r.pixel_resolution || "N/A"}
</option>
))}
</select>
</div>
<button
type="submit"
className="bg-blue-500 text-white px-4 py-1 rounded hover:bg-blue-600"
>
Filter
</button>
</form>
<Table
rows={components.map((c) => ({
lcsc: c.lcsc,
mfr: c.mfr,
package: c.package || "-",
description: c.description,
display_width: c.display_width ?? "N/A",
pixel_resolution: c.pixel_resolution ?? "N/A",
stock: <span className="tabular-nums">{c.stock}</span>,
price: <span className="tabular-nums">{formatPrice(c.price)}</span>,
protocol: c.protocol ?? "N/A",
}))}
/>
</div>,
)
})
2 changes: 2 additions & 0 deletions scripts/setup-derived-tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { DerivedTableSpec } from "lib/db/derivedtables/types"
import type { KyselyDatabaseInstance } from "lib/db/kysely-types"
import { mosfetTableSpec } from "lib/db/derivedtables/mosfet"
import { ledWithICTableSpec } from "lib/db/derivedtables/led_with_ic"
import { oledDisplayTableSpec } from "lib/db/derivedtables/oled_display"
import { ledSegmentDisplayTableSpec } from "lib/db/derivedtables/led_segment_display"

const resetArg = process.argv.indexOf("--reset")
Expand All @@ -39,6 +40,7 @@ const DERIVED_TABLES: DerivedTableSpec<any>[] = [
ledDriverTableSpec,
mosfetTableSpec,
ledWithICTableSpec,
oledDisplayTableSpec,
ledSegmentDisplayTableSpec,
]

Expand Down
61 changes: 61 additions & 0 deletions tests/oled_display/list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { test, expect } from "bun:test"
import { getTestServer } from "../fixtures/get-test-server"

test("GET /oled_display/list.json with json param returns OLED Display data", async () => {
const { axios } = await getTestServer()

const res = await axios.get("/oled_display/list.json?json=true")

expect(res.data).toHaveProperty("oled_displays")
expect(Array.isArray(res.data.oled_displays)).toBe(true)

// check structure of first OLED Display if array not empty
if (res.data.oled_displays.length > 0) {
const oledDisplay = res.data.oled_displays[0]
expect(oledDisplay).toHaveProperty("lcsc")
expect(oledDisplay).toHaveProperty("mfr")
expect(oledDisplay).toHaveProperty("package")
expect(oledDisplay).toHaveProperty("description")
expect(oledDisplay).toHaveProperty("stock")
expect(oledDisplay).toHaveProperty("price1")
expect(oledDisplay).toHaveProperty("protocol")

expect(typeof oledDisplay.lcsc).toBe("number")
expect(typeof oledDisplay.mfr).toBe("string")
expect(typeof oledDisplay.package).toBe("string")
expect(typeof oledDisplay.description).toBe("string")
expect(typeof oledDisplay.stock).toBe("number")
expect(typeof oledDisplay.price1).toBe("number")
if (oledDisplay.protocol) {
expect(typeof oledDisplay.protocol).toBe("string")
}
}
})

test("GET /oled_display/list.json with filters returns filtered data", async () => {
const { axios } = await getTestServer()

// Test with package filter
const res = await axios.get("/oled_display/list.json?json=true&package=QFN32")

expect(res.data).toHaveProperty("oled_displays")
expect(Array.isArray(res.data.oled_displays)).toBe(true)

// Verify all returned OLED Displays have the specified package
for (const oledDisplay of res.data.oled_displays) {
expect(oledDisplay.package).toBe("QFN32")
}

// Test with protocol filter
const protocolRes = await axios.get(
"/oled_display/list.json?json=true&protocol=I2C",
)

expect(protocolRes.data).toHaveProperty("oled_displays")
expect(Array.isArray(protocolRes.data.oled_displays)).toBe(true)

// Verify all returned OLED Displays have the specified protocol
for (const oledDisplay of protocolRes.data.oled_displays) {
expect(oledDisplay.protocol).toBe("I2C")
}
})

0 comments on commit a3f1f9c

Please sign in to comment.