mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-09-11 18:30:14 -07:00
Compare commits
6
Commits
dev
..
clang-format
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6f37c80df | ||
|
|
07e7ed2abd | ||
|
|
3b0d14759b | ||
|
|
9248223a39 | ||
|
|
1e79e8bd43 | ||
|
|
e0790f677b |
@@ -0,0 +1,5 @@
|
||||
BreakBeforeBinaryOperators: All
|
||||
ColumnLimit: 120
|
||||
BasedOnStyle: Google
|
||||
BreakBeforeBraces: Stroustrup
|
||||
IndentWidth: 4
|
||||
@@ -1,309 +0,0 @@
|
||||
const FEATURE_FREEZE_LABEL = "feature freeze";
|
||||
const CLOSE_SOON_LABEL = "close soon";
|
||||
const INACTIVITY_MARKER = "<!-- chameleon-ultra-draft-inactivity:";
|
||||
|
||||
const LABELS = {
|
||||
[FEATURE_FREEZE_LABEL]: {
|
||||
color: "1d76db",
|
||||
description: "Included in the current review and release batch",
|
||||
},
|
||||
[CLOSE_SOON_LABEL]: {
|
||||
color: "d73a4a",
|
||||
description: "Will be closed unless development resumes",
|
||||
},
|
||||
};
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
module.exports = async ({ github, context, core, mode }) => {
|
||||
const { owner, repo } = context.repo;
|
||||
let knownLabels;
|
||||
|
||||
const daysSince = (timestamp) =>
|
||||
(Date.now() - new Date(timestamp).getTime()) / DAY;
|
||||
|
||||
const hasLabel = (pull, name) =>
|
||||
pull.labels.some((label) => label.name.toLowerCase() === name.toLowerCase());
|
||||
|
||||
async function ensureLabel(name) {
|
||||
if (!knownLabels) {
|
||||
const labels = await github.paginate(github.rest.issues.listLabelsForRepo, {
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100,
|
||||
});
|
||||
knownLabels = new Set(labels.map((label) => label.name.toLowerCase()));
|
||||
}
|
||||
|
||||
if (knownLabels.has(name.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await github.rest.issues.createLabel({ owner, repo, name, ...LABELS[name] });
|
||||
knownLabels.add(name.toLowerCase());
|
||||
core.info(`Created the "${name}" label`);
|
||||
} catch (error) {
|
||||
// Two simultaneous runs may both notice that a label is absent.
|
||||
if (error.status !== 422) throw error;
|
||||
knownLabels.add(name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
async function addLabel(pullNumber, name) {
|
||||
await ensureLabel(name);
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pullNumber,
|
||||
labels: [name],
|
||||
});
|
||||
}
|
||||
|
||||
async function removeLabel(pull, name) {
|
||||
if (!hasLabel(pull, name)) return;
|
||||
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pull.number,
|
||||
name,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function inactivityComments(pullNumber) {
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pullNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
return comments.filter(
|
||||
(comment) =>
|
||||
comment.user?.login === "github-actions[bot]" &&
|
||||
comment.body?.includes(INACTIVITY_MARKER),
|
||||
);
|
||||
}
|
||||
|
||||
async function clearDraftCountdown(pull) {
|
||||
const hadCloseSoon = hasLabel(pull, CLOSE_SOON_LABEL);
|
||||
await removeLabel(pull, CLOSE_SOON_LABEL);
|
||||
const comments = await inactivityComments(pull.number);
|
||||
|
||||
for (const comment of comments) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (comments.length || hadCloseSoon) {
|
||||
core.info(`Reset the inactivity countdown for #${pull.number}`);
|
||||
}
|
||||
}
|
||||
|
||||
function warningBody(warningDays, responseDays) {
|
||||
return [
|
||||
`${INACTIVITY_MARKER}warning -->`,
|
||||
"This draft pull request has not had activity for a while. Are you still interested in finishing it?",
|
||||
"",
|
||||
`Any new commit, PR edit, or conversation comment will reset this timer. If there is no activity for another ${responseDays} days, the \`${CLOSE_SOON_LABEL}\` label will be added.`,
|
||||
"",
|
||||
"If you are no longer interested, reply with `no`, `no longer interested`, `please close`, or `/close-soon`.",
|
||||
"",
|
||||
`<sub>This reminder was posted after ${warningDays} days of inactivity.</sub>`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function closeSoonBody(closeDays) {
|
||||
return [
|
||||
`${INACTIVITY_MARKER}close-soon -->`,
|
||||
`This draft pull request is now marked \`${CLOSE_SOON_LABEL}\` because no continuing interest was confirmed.`,
|
||||
"",
|
||||
`It will be closed after ${closeDays} more days without activity. A new commit, PR edit, or conversation comment will cancel the countdown.`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function markCloseSoon(pull, comment, closeDays) {
|
||||
await addLabel(pull.number, CLOSE_SOON_LABEL);
|
||||
const body = closeSoonBody(closeDays);
|
||||
|
||||
if (comment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pull.number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
core.info(`Marked draft #${pull.number} as "${CLOSE_SOON_LABEL}"`);
|
||||
}
|
||||
|
||||
if (mode === "start-feature-freeze") {
|
||||
await ensureLabel(FEATURE_FREEZE_LABEL);
|
||||
await ensureLabel(CLOSE_SOON_LABEL);
|
||||
|
||||
const pulls = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
per_page: 100,
|
||||
});
|
||||
const readyPulls = pulls.filter((pull) => !pull.draft);
|
||||
|
||||
for (const pull of readyPulls) {
|
||||
await addLabel(pull.number, FEATURE_FREEZE_LABEL);
|
||||
await removeLabel(pull, CLOSE_SOON_LABEL);
|
||||
}
|
||||
|
||||
core.summary
|
||||
.addHeading("Feature-freeze batch started")
|
||||
.addRaw(
|
||||
readyPulls.length
|
||||
? `Labeled ${readyPulls.length} open, non-draft pull request(s): ${readyPulls.map((pull) => `#${pull.number}`).join(", ")}.`
|
||||
: "There were no open, non-draft pull requests to label.",
|
||||
);
|
||||
await core.summary.write();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === "pull-activity") {
|
||||
const pull = context.payload.pull_request;
|
||||
const action = context.payload.action;
|
||||
|
||||
// A converted draft has left the current batch. A reopened PR waits for the next batch rather than silently rejoining its old one.
|
||||
if (action === "converted_to_draft" || action === "reopened") {
|
||||
await removeLabel(pull, FEATURE_FREEZE_LABEL);
|
||||
}
|
||||
|
||||
if (pull.draft || action === "ready_for_review" || action === "reopened") {
|
||||
await clearDraftCountdown(pull);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === "discussion-activity") {
|
||||
if (context.payload.issue && !context.payload.issue.pull_request) return;
|
||||
|
||||
const pullNumber = context.payload.issue?.number ?? context.payload.pull_request?.number;
|
||||
if (!pullNumber || context.actor.endsWith("[bot]")) return;
|
||||
|
||||
const { data: pull } = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullNumber,
|
||||
});
|
||||
if (!pull.draft) return;
|
||||
|
||||
const comments = await inactivityComments(pull.number);
|
||||
const activeComment = comments.at(-1);
|
||||
if (!activeComment) return;
|
||||
|
||||
const reply = context.payload.comment?.body?.trim() ?? "";
|
||||
const isAuthorReply = context.actor === pull.user.login;
|
||||
const isNegativeReply = /^(?:no|no thanks|not anymore|no longer interested|please close(?: this)?|close (?:it|this|this pr)|\/close-soon)[\s.!]*$/i.test(reply);
|
||||
|
||||
if (isAuthorReply && isNegativeReply) {
|
||||
const closeDays = Number(process.env.DRAFT_CLOSE_DAYS);
|
||||
await markCloseSoon(pull, activeComment, closeDays);
|
||||
return;
|
||||
}
|
||||
|
||||
await clearDraftCountdown(pull);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === "scan-drafts") {
|
||||
const warningDays = Number(process.env.DRAFT_WARNING_DAYS);
|
||||
const responseDays = Number(process.env.DRAFT_RESPONSE_DAYS);
|
||||
const closeDays = Number(process.env.DRAFT_CLOSE_DAYS);
|
||||
|
||||
if (![warningDays, responseDays, closeDays].every(Number.isFinite)) {
|
||||
core.setFailed("Draft inactivity periods must all be numbers");
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureLabel(CLOSE_SOON_LABEL);
|
||||
const pulls = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
for (const pull of pulls.filter((candidate) => candidate.draft)) {
|
||||
const comments = await inactivityComments(pull.number);
|
||||
const activeComment = comments.at(-1);
|
||||
const phase = activeComment?.body?.includes(`${INACTIVITY_MARKER}close-soon`)
|
||||
? "close-soon"
|
||||
: activeComment?.body?.includes(`${INACTIVITY_MARKER}warning`)
|
||||
? "warning"
|
||||
: undefined;
|
||||
|
||||
// Honor a close-soon label applied manually by starting the final timer.
|
||||
if (hasLabel(pull, CLOSE_SOON_LABEL) && phase !== "close-soon") {
|
||||
await markCloseSoon(pull, activeComment, closeDays);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Removing the label manually cancels the final countdown.
|
||||
if (phase === "close-soon" && !hasLabel(pull, CLOSE_SOON_LABEL)) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: activeComment.id,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!activeComment && daysSince(pull.updated_at) >= warningDays) {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pull.number,
|
||||
body: warningBody(warningDays, responseDays),
|
||||
});
|
||||
core.info(`Warned inactive draft #${pull.number}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phase === "warning" && daysSince(activeComment.created_at) >= responseDays) {
|
||||
await markCloseSoon(pull, activeComment, closeDays);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phase === "close-soon" && daysSince(activeComment.updated_at) >= closeDays) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: activeComment.id,
|
||||
body: [
|
||||
`${INACTIVITY_MARKER}closed -->`,
|
||||
`Closing this draft pull request after the \`${CLOSE_SOON_LABEL}\` waiting period expired. It can be reopened if development resumes.`,
|
||||
].join("\n"),
|
||||
});
|
||||
await github.rest.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pull.number,
|
||||
state: "closed",
|
||||
});
|
||||
core.info(`Closed inactive draft #${pull.number}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -11,8 +11,6 @@ on:
|
||||
jobs:
|
||||
build_client:
|
||||
name: Build client
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -37,12 +35,11 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }}
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v7
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install PyInstaller and client dependencies
|
||||
@@ -64,14 +61,14 @@ jobs:
|
||||
cd software
|
||||
pyinstaller pyinstaller.spec
|
||||
- name: Upload built client
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: client-${{ matrix.name }}
|
||||
path: software/dist/*
|
||||
- name: Zip up client for release
|
||||
run: ${{ matrix.bundle_command }}
|
||||
- name: Upload release artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-artifacts-${{ matrix.name }}
|
||||
path: client-${{ matrix.name }}.zip
|
||||
|
||||
@@ -6,44 +6,38 @@ on:
|
||||
checkout-sha:
|
||||
required: false
|
||||
type: string
|
||||
publish-builder:
|
||||
description: Build and publish the firmware builder image
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build_fw_builder:
|
||||
name: Build fw-builder Docker image
|
||||
runs-on: ubuntu-latest # Inherits permissions from caller
|
||||
name: Build and push fw-builder Docker image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
outputs:
|
||||
image_ref: ${{ steps.published-image.outputs.image_ref || steps.local-image.outputs.image_ref }}
|
||||
image_hash: ${{ steps.push.outputs.digest }}
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: ghcr.io login
|
||||
if: ${{ inputs.publish-builder }}
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract Docker metadata
|
||||
if: ${{ inputs.publish-builder }}
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}-fw-builder
|
||||
- name: Build and push Docker images
|
||||
if: ${{ inputs.publish-builder }}
|
||||
id: push
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: firmware
|
||||
push: true
|
||||
@@ -51,63 +45,26 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Build Docker image for this workflow
|
||||
if: ${{ !inputs.publish-builder }}
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: firmware
|
||||
tags: chameleonultra-fw-builder:pr
|
||||
outputs: type=docker,dest=${{ runner.temp }}/fw-builder.tar
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Upload Docker image
|
||||
if: ${{ !inputs.publish-builder }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: firmware-builder-image
|
||||
path: ${{ runner.temp }}/fw-builder.tar
|
||||
retention-days: 1
|
||||
- name: Use local builder image
|
||||
if: ${{ !inputs.publish-builder }}
|
||||
id: local-image
|
||||
run: echo "image_ref=chameleonultra-fw-builder:pr" >> "$GITHUB_OUTPUT"
|
||||
- name: Use published builder image
|
||||
if: ${{ inputs.publish-builder }}
|
||||
id: published-image
|
||||
env:
|
||||
IMAGE: ghcr.io/${{ github.repository }}-fw-builder
|
||||
DIGEST: ${{ steps.push.outputs.digest }}
|
||||
run: echo "image_ref=${IMAGE,,}@${DIGEST}" >> "$GITHUB_OUTPUT"
|
||||
build_fw:
|
||||
name: Build firmware
|
||||
runs-on: ubuntu-latest
|
||||
needs: build_fw_builder
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
device_type: [ultra, lite]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Download Docker image
|
||||
if: ${{ !inputs.publish-builder }}
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: firmware-builder-image
|
||||
path: ${{ runner.temp }}
|
||||
- name: Load Docker image
|
||||
if: ${{ !inputs.publish-builder }}
|
||||
run: docker load --input "${{ runner.temp }}/fw-builder.tar"
|
||||
- name: Build firmware
|
||||
env:
|
||||
repo: ${{ github.repository }}
|
||||
run: |
|
||||
docker run --rm -v ${PWD}:/workdir -e CURRENT_DEVICE_TYPE=${{ matrix.device_type }} "${{ needs.build_fw_builder.outputs.image_ref }}" firmware/build.sh
|
||||
docker run --rm -v ${PWD}:/workdir -e CURRENT_DEVICE_TYPE=${{ matrix.device_type }} ghcr.io/${repo,,}-fw-builder@${{ needs.build_fw_builder.outputs.image_hash }} firmware/build.sh
|
||||
- name: Upload built binaries
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.device_type }}-firmware
|
||||
path: firmware/objects/*.hex
|
||||
@@ -119,17 +76,17 @@ jobs:
|
||||
unzip firmware/objects/${{ matrix.device_type }}-dfu-app.zip -d firmware/objects/${{ matrix.device_type }}-dfu-app
|
||||
unzip firmware/objects/${{ matrix.device_type }}-dfu-full.zip -d firmware/objects/${{ matrix.device_type }}-dfu-full
|
||||
- name: Upload dfu app image
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.device_type }}-dfu-app
|
||||
path: firmware/objects/${{ matrix.device_type }}-dfu-app/*
|
||||
- name: Upload dfu full image
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.device_type }}-dfu-full
|
||||
path: firmware/objects/${{ matrix.device_type }}-dfu-full/*
|
||||
- name: Upload release artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-artifacts-${{ matrix.device_type }}
|
||||
path: firmware/objects/*.zip
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: clang-format Check
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- '**.c'
|
||||
- '**.h'
|
||||
- '**.cpp'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install clang-format
|
||||
run: sudo apt install -y clang-format
|
||||
|
||||
- name: Get files and check format
|
||||
run: |
|
||||
exit_code=0
|
||||
while IFS= read -r -d '' f; do
|
||||
if ! diff -u "$f" <(clang-format "$f"); then
|
||||
echo "Formatting issue in $f"
|
||||
exit_code=1
|
||||
fi
|
||||
done < <(find . -type f \( -name '*.c' -o -name '*.h' \) ! -path './firmware/nrf52_sdk/*' ! -path './firmware/nrf52_sdk/**' -print0)
|
||||
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo "Clang-format check failed."
|
||||
exit 1
|
||||
else
|
||||
echo "All files are properly formatted."
|
||||
fi
|
||||
@@ -1,48 +1,15 @@
|
||||
on: pull_request_target
|
||||
name: Changelog Reminder
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
remind:
|
||||
name: Changelog Reminder
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Changelog Reminder
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const message = 'You are welcome to add an entry to the CHANGELOG.md as well';
|
||||
const pullNumber = context.payload.pull_request.number;
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pullNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
if (files.some((file) => file.filename === 'CHANGELOG.md')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
const alreadyReminded = comments.some((comment) =>
|
||||
comment.user?.login === 'github-actions[bot]' && comment.body === message
|
||||
);
|
||||
|
||||
if (!alreadyReminded) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullNumber,
|
||||
body: message,
|
||||
});
|
||||
}
|
||||
- uses: actions/checkout@master
|
||||
- name: Changelog Reminder
|
||||
uses: peterjgrainger/action-changelog-reminder@v1.2.0
|
||||
with:
|
||||
changelog_regex: 'CHANGELOG.md'
|
||||
customPrMessage: 'You are welcome to add an entry to the CHANGELOG.md as well'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -5,7 +5,11 @@ on:
|
||||
branches: ["main"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
@@ -17,23 +21,20 @@ jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
persist-credentials: false
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Build with Jekyll
|
||||
uses: actions/jekyll-build-pages@v1
|
||||
with:
|
||||
source: ./
|
||||
destination: ./_site
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
@@ -42,10 +43,7 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
name: PR handler
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: pr-build-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
firmware_pipeline:
|
||||
name: Build Firmware
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
checkout-sha: "${{ github.event.pull_request.head.sha }}"
|
||||
publish-builder: false
|
||||
client_pipeline:
|
||||
name: Build Firmware
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build_client.yml
|
||||
with:
|
||||
checkout-sha: "${{ github.event.pull_request.head.sha }}"
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
name: Comment on PR
|
||||
needs:
|
||||
- firmware_pipeline
|
||||
- client_pipeline
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
message: |
|
||||
# Built artifacts for commit ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
## Firmware
|
||||
|
||||
- [Ultra APP DFU Package](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/ultra-dfu-app.zip)
|
||||
- [Ultra binaries](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/ultra-firmware.zip)
|
||||
- [Lite APP DFU Package](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/lite-dfu-app.zip)
|
||||
- [Lite binaries](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/lite-firmware.zip)
|
||||
|
||||
## Client
|
||||
|
||||
- [Linux](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/client-linux.zip)
|
||||
- [macOS](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/client-macos.zip)
|
||||
- [Windows](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/client-windows.zip)
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
name: PR build artifact comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["PR handler"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
name: Comment on PR build result
|
||||
if: github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add or update artifact links
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const run = context.payload.workflow_run;
|
||||
let pullNumber = run.pull_requests[0]?.number;
|
||||
|
||||
if (!pullNumber) {
|
||||
const head = `${run.head_repository.owner.login}:${run.head_branch}`;
|
||||
const candidates = await github.paginate(github.rest.pulls.list, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
head,
|
||||
per_page: 100,
|
||||
});
|
||||
pullNumber = candidates.find((pull) => pull.head.sha === run.head_sha)?.number;
|
||||
}
|
||||
|
||||
if (!pullNumber) {
|
||||
core.setFailed(`Could not find the pull request for workflow run ${run.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: pull } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pullNumber,
|
||||
});
|
||||
|
||||
if (pull.head.sha !== run.head_sha) {
|
||||
core.notice(`Skipping superseded workflow run ${run.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const marker = '<!-- chameleon-ultra-pr-build-artifacts -->';
|
||||
const lines = [marker, `# Build result for commit ${run.head_sha}`, ''];
|
||||
|
||||
if (run.conclusion === 'success') {
|
||||
const baseUrl = `https://nightly.link/${context.repo.owner}/${context.repo.repo}/actions/runs/${run.id}`;
|
||||
lines.push(
|
||||
'## Firmware',
|
||||
'',
|
||||
`- [Ultra APP DFU Package](${baseUrl}/ultra-dfu-app.zip)`,
|
||||
`- [Ultra binaries](${baseUrl}/ultra-firmware.zip)`,
|
||||
`- [Lite APP DFU Package](${baseUrl}/lite-dfu-app.zip)`,
|
||||
`- [Lite binaries](${baseUrl}/lite-firmware.zip)`,
|
||||
'',
|
||||
'## Client',
|
||||
'',
|
||||
`- [Linux](${baseUrl}/client-linux.zip)`,
|
||||
`- [macOS](${baseUrl}/client-macos.zip)`,
|
||||
`- [Windows](${baseUrl}/client-windows.zip)`,
|
||||
);
|
||||
} else {
|
||||
lines.push(
|
||||
`**Status:** ${run.conclusion ?? 'unknown'}`,
|
||||
'',
|
||||
`[View workflow run](${run.html_url})`,
|
||||
);
|
||||
}
|
||||
|
||||
const body = lines.join('\n');
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
const existing = comments.find((comment) =>
|
||||
comment.user?.login === 'github-actions[bot]' &&
|
||||
(comment.body?.includes(marker) || comment.body?.includes('# Built artifacts for commit '))
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
@@ -3,10 +3,6 @@ name: Push handler
|
||||
on:
|
||||
push:
|
||||
|
||||
concurrency:
|
||||
group: push-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
firmware_pipeline:
|
||||
name: Build Firmware
|
||||
@@ -16,8 +12,6 @@ jobs:
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
client_pipeline:
|
||||
name: Build Firmware
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/build_client.yml
|
||||
create_dev_release:
|
||||
permissions:
|
||||
@@ -30,15 +24,15 @@ jobs:
|
||||
- client_pipeline
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
- name: Download release artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-artifacts-*
|
||||
merge-multiple: true
|
||||
path: release-artifacts
|
||||
- name: Upload to dev release
|
||||
uses: softprops/action-gh-release@v3
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body: |
|
||||
Auto-Generated DFU packages from latest `main` commit.
|
||||
@@ -55,7 +49,7 @@ jobs:
|
||||
- name: Fix up release tag
|
||||
run: |
|
||||
git tag -f dev
|
||||
git push origin refs/tags/dev --force
|
||||
git push --tags -f
|
||||
create_release:
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -67,17 +61,15 @@ jobs:
|
||||
- client_pipeline
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v4
|
||||
- name: Download release artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-artifacts-*
|
||||
merge-multiple: true
|
||||
path: release-artifacts
|
||||
- name: Upload to tagged release
|
||||
uses: softprops/action-gh-release@v3
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body: |
|
||||
Auto-Generated DFU packages for Release ${{ github.ref_name }}
|
||||
@@ -88,15 +80,3 @@ jobs:
|
||||
generate_release_notes: true
|
||||
append_body: true
|
||||
files: release-artifacts/*
|
||||
|
||||
start_feature_freeze:
|
||||
name: Start the next PR review cycle
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: create_release
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
uses: ./.github/workflows/pr_review_cycle.yml
|
||||
with:
|
||||
start_feature_freeze: true
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
name: PR review cycle
|
||||
|
||||
on:
|
||||
# The tagged-release workflow calls this explicitly because events created by GITHUB_TOKEN do not normally start another workflow.
|
||||
workflow_call:
|
||||
inputs:
|
||||
start_feature_freeze:
|
||||
description: Label the next review batch
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
pull_request_target:
|
||||
types: [converted_to_draft, edited, ready_for_review, reopened, synchronize]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
schedule:
|
||||
- cron: "23 4 * * *"
|
||||
|
||||
env:
|
||||
DRAFT_WARNING_DAYS: 30
|
||||
DRAFT_RESPONSE_DAYS: 14
|
||||
DRAFT_CLOSE_DAYS: 30
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: pr-review-cycle-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
start-feature-freeze:
|
||||
name: Start feature-freeze batch
|
||||
if: >-
|
||||
inputs.start_feature_freeze == true ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'release' && github.event.release.prerelease == false)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out trusted automation
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
- name: Label all currently ready PRs
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const run = require('./.github/scripts/pr-review-cycle.js');
|
||||
await run({ github, context, core, mode: 'start-feature-freeze' });
|
||||
|
||||
handle-pull-activity:
|
||||
name: Update labels after PR activity
|
||||
if: github.event_name == 'pull_request_target'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out trusted automation
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
- name: Update the PR lifecycle
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const run = require('./.github/scripts/pr-review-cycle.js');
|
||||
await run({ github, context, core, mode: 'pull-activity' });
|
||||
|
||||
handle-discussion-activity:
|
||||
name: Reset inactive draft after discussion
|
||||
if: github.event_name == 'issue_comment' && github.event.issue.pull_request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out trusted automation
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
- name: Update the draft lifecycle
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const run = require('./.github/scripts/pr-review-cycle.js');
|
||||
await run({ github, context, core, mode: 'discussion-activity' });
|
||||
|
||||
scan-inactive-drafts:
|
||||
name: Scan inactive drafts
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out trusted automation
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
- name: Warn, label, or close inactive drafts
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const run = require('./.github/scripts/pr-review-cycle.js');
|
||||
await run({ github, context, core, mode: 'scan-drafts' });
|
||||
@@ -1,17 +1,15 @@
|
||||
name: Lint (pyrefly + ruff)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- "software/**"
|
||||
- ".github/workflows/**"
|
||||
- "**.py"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint-formatting:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
@@ -20,12 +18,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v7
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
cache: "pip"
|
||||
@@ -37,42 +33,25 @@ jobs:
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Install dependencies with uv
|
||||
- name: Install dependencies with uv (if lockfile present)
|
||||
if: ${{ hashFiles('software/uv.lock') != '' }}
|
||||
run: uv sync --dev
|
||||
|
||||
- name: Install tools with pip (fallback)
|
||||
if: ${{ hashFiles('software/uv.lock') == '' }}
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
# Try project requirements if present
|
||||
if [ -f script/requirements.txt ]; then pip install -r script/requirements.txt || true; fi
|
||||
# Ensure ruff and pyrefly are available
|
||||
pip install ruff pyrefly
|
||||
|
||||
- name: Ruff check
|
||||
run: uv run ruff check .
|
||||
|
||||
lint-types:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: software
|
||||
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: "3.13"
|
||||
cache: "pip"
|
||||
cache-dependency-path: |
|
||||
software/pyproject.toml
|
||||
software/uv.lock
|
||||
software/script/requirements.txt
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
|
||||
- name: Install dependencies with uv
|
||||
run: uv sync --dev
|
||||
run: |
|
||||
set -e
|
||||
(uv run ruff --version && uv run ruff check .) || ruff check .
|
||||
|
||||
- name: Pyrefly check
|
||||
run: uv run pyrefly check
|
||||
run: |
|
||||
set -e
|
||||
(uv run pyrefly --help >/dev/null 2>&1 && uv run pyrefly check) || pyrefly check
|
||||
|
||||
@@ -708,8 +708,3 @@ FodyWeavers.xsd
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,c++,c,python,visualstudiocode,macos,windows
|
||||
software/script/tests/nonces.bin
|
||||
software/script/nonces.bin
|
||||
.vscode/settings.json
|
||||
.vscode/tasks.json
|
||||
firmware/compile_commands.json
|
||||
firmware/application/compile_commands.json
|
||||
software/src/target_arch_detect.c
|
||||
|
||||
+1
-24
@@ -3,31 +3,8 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
|
||||
## [v2.2.0][2026-07-04]
|
||||
- Added Jablotron LF protocol support: read, emulate and T55xx clone (@midlan)
|
||||
- Added IDTECK LF protocol support: tag emulation (PSK1 RF/32) and T55xx clone. No reader path yet; PSK demodulation on the envelope-only receive chain is left for a follow-up.
|
||||
- Added PAC/Stanley LF protocol support: read, emulate and T55xx clone (@kevihiiin, @danieltwagner)
|
||||
- Fix firmware application USB serial number (@taichunmin)
|
||||
- Added ioProx LF protocol support (read, emulate and T55xx clone)
|
||||
- Added `hf mfu nfcimport` to import Flipper Zero `.nfc` files into MFU/NTAG emulator slots, with `--amiibo` flag for automatic PWD/PACK derivation (@fmuk)
|
||||
- Added commands to dump and clone Mifare tags
|
||||
- Fix bad missing tools warning (@suut)
|
||||
- Fix for FAST_READ command for nfc - mf0 tags
|
||||
- Rewrite of the dynamic and static locks logic for NTAG213, NTAG215 and NTAG216; we shouldn't take into account the block lock bits
|
||||
- Fixed an issue where we wouldn't be able to change CFG0 and CFG1 for NTAG213, NTAG215 and NTG216 once a password was added even if the cfg bit was reset.
|
||||
- Added clang formatter (@GameTec-live)
|
||||
- Fix for static nested key recovery (@jekkos)
|
||||
- Fix LEDs being stuck on after battery check (@suut)
|
||||
- Add TCP support for the CLI (@suut)
|
||||
- Fix build on Android in Termux (@suut)
|
||||
- Fix the issue where some reader cause CU to enter a strange state (@xianglin1998)
|
||||
- The transmission performance of USB has been improved (@xianglin1998)
|
||||
- Added cmd for set mf1 config 'field_off_do_reset' (@xianglin1998)
|
||||
- Fix Windows build (@suut)
|
||||
- Added `hf 14a config` to deal with badly configured cards (@azuwis)
|
||||
- New Symmetrical LED Animation Mode and Improved Minimal Mode (@WillyJL)
|
||||
- Fix MF1 state reset logic and access control conditions (@unkernet)
|
||||
- Added support for SEOS credentials (@aaronjamt)
|
||||
|
||||
## [v2.1.0][2025-09-02]
|
||||
- Added UV, formatter and linter. Contribution guidelines. (@GameTec-live)
|
||||
|
||||
@@ -19,26 +19,3 @@ Heres a bit of info and a few guidelines to get you started:
|
||||
- Type safety is important. The CLI should be typesafe. Python 3.9+ offer a wide variety of type declarations. Metas [pyrefly](https://pyrefly.org/) is used to do type validation. It is recommended to install the appropriate vscode extension and check your types before opening a PR.
|
||||
- Formatting matters. Mostly. While pixelpeeping and exact rules are annoying and unnescesary, format your code in a readable and logical way. [Ruff](https://docs.astral.sh/ruff/) is used to enforce various formatting rules. You may install the Ruff vscode extension or use the CLI to format before opening a PR.
|
||||
- Avoid extra packages. Almost everyone knows the "meme" of the javascript ["is-even"](https://www.npmjs.com/package/is-even) package. While it is encouraged and makes sense to use packages where appropriate, just installing packages for the hell of it even if its a 2 liner is not sensible.
|
||||
|
||||
## Feature freeze and review cycle
|
||||
|
||||
Pull requests are reviewed in batches so maintainers and testers can focus on a manageable set of changes. The `feature freeze` label marks the PRs in the current batch. This does not mean that you can not make new Pull requests or contributions.
|
||||
|
||||
The cycle works as follows:
|
||||
|
||||
1. At the start of a cycle, every open PR that is ready for review is given the `feature freeze` label. Draft PRs are not included.
|
||||
2. Maintainers and contributors focus their review and testing on the labeled PRs. These PRs are then merged, closed, or converted back to drafts as appropriate.
|
||||
3. PRs opened or marked ready after the batch starts normally wait for the next cycle. Maintainers may make exceptions when necessary.
|
||||
4. Once no open, ready PRs remain in the current batch, a release is made and the next cycle begins. All PRs that are ready at that point enter the new batch.
|
||||
|
||||
If a PR in the current batch is converted to a draft, it leaves that batch. Marking it ready again does not automatically add it back to the current batch. It may be included in the next one.
|
||||
|
||||
### Inactive drafts
|
||||
|
||||
Draft PRs are welcome and the ideal solution while work is in progress and you may want to gather early feedback. To prevent stale PRs from cluttering up the list, inactive draft PRs will follow the following process:
|
||||
|
||||
- After 30 days without activity, a bot asks whether there is still interest in completing the PR.
|
||||
- After another 14 days without activity, the PR receives the `close soon` label.
|
||||
- After 30 more days without activity, the draft is closed. It can be reopened if development resumes.
|
||||
|
||||
A new commit, an edit to the PR, or a new comment resets the inactivity countdown. An author can also reply to the bot with `no`, `no longer interested`, `please close`, or `/close-soon` to move the PR directly to the `close soon` stage.
|
||||
@@ -26,8 +26,6 @@ Read the [available documentation](https://github.com/RfidResearchGroup/Chameleo
|
||||
|
||||
* [ChameleonUltraGUI](https://github.com/GameTec-live/ChameleonUltraGUI)
|
||||
* [MTools BLE](https://github.com/RfidResearchGroup/ChameleonUltra/wiki/mtoolsble)
|
||||
* [Mifare Chameleon Tool (iOS only, Beta)](https://apps.apple.com/it/app/mifare-chameleon-tool/id6761231484)
|
||||
* [Chameleon Ultra (Sailfish OS only)](https://sailfishos-chum.github.io/apps/harbour-chameleon-ultra)
|
||||
|
||||
# Videos
|
||||
|
||||
@@ -47,9 +45,4 @@ Where do you find the community?
|
||||
* Devices/chameleon-ultra for usage discussions
|
||||
* [GameTec_live discord server](https://discord.gg/DJ2A4wxncK)
|
||||
|
||||
Other notable projects:
|
||||
* [Fantasi](https://fantasi.cloud)
|
||||
* [Discord](https://fantasi.cloud/discord)
|
||||
* [chameleon-ultra.js](https://github.com/taichunmin/chameleon-ultra.js/)
|
||||
|
||||
###### Searching for the docs repo? Find it [here](https://github.com/RfidResearchGroup/ChameleonUltraDocs)
|
||||
###### Searching for the docs repo? Find it [here](https://github.com/RfidResearchGroup/ChameleonUltraDocs)
|
||||
@@ -28,24 +28,16 @@ SRC_FILES += \
|
||||
$(PROJ_DIR)/rfid/nfctag/tag_persistence.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/hf/crypto1_helper.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a_4.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf1.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/hf/nfc_seos.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/fskdemod.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/circular_buffer.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/manchester.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/psk1.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/hidprox.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/pac.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/ioprox.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/viking.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/jablotron.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/diphase.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/idteck.c \
|
||||
$(PROJ_DIR)/utils/dataframe.c \
|
||||
$(PROJ_DIR)/utils/delayed_reset.c \
|
||||
$(PROJ_DIR)/utils/fds_util.c \
|
||||
@@ -348,17 +340,11 @@ ifeq (${CURRENT_DEVICE_TYPE}, ${CHAMELEON_ULTRA})
|
||||
$(PROJ_DIR)/rfid/reader/hf/rc522.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_125khz_radio.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_em410x_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_em4x05_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_gap.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_main.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_t55xx_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_hidprox_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_pac_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_ioprox_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_viking_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_jablotron_data.c \
|
||||
|
||||
INC_FOLDERS +=\
|
||||
${PROJ_DIR}/rfid/reader/ \
|
||||
|
||||
+18
-1616
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,6 @@
|
||||
#include "nrf_delay.h"
|
||||
#include "nrf_drv_gpiote.h"
|
||||
#include "nrf_drv_rng.h"
|
||||
#include "nfc_mf1.h" // for nfc_tag_mf1_prng_seed
|
||||
#include "nrf_power.h"
|
||||
#include "nrf_pwr_mgmt.h"
|
||||
#include "nrfx_nfct.h"
|
||||
@@ -42,10 +41,6 @@ NRF_LOG_MODULE_REGISTER();
|
||||
#include "tag_persistence.h"
|
||||
#include "settings.h"
|
||||
|
||||
#if defined(PROJECT_CHAMELEON_ULTRA)
|
||||
#include "rc522.h"
|
||||
#endif
|
||||
|
||||
// Defining soft timers
|
||||
APP_TIMER_DEF(m_button_check_timer); // Timer for button debounce
|
||||
|
||||
@@ -61,9 +56,6 @@ static bool m_is_a_btn_release = false;
|
||||
|
||||
static bool m_system_off_processing = false;
|
||||
|
||||
// NFC field generator state
|
||||
volatile bool m_is_field_on = false;
|
||||
|
||||
// cpu reset reason
|
||||
static uint32_t m_reset_source;
|
||||
static uint32_t m_gpregret_val;
|
||||
@@ -137,11 +129,6 @@ void rng_drv_and_srand_init(void) {
|
||||
|
||||
// Finally initialize the srand seeds in the c standard library
|
||||
srand(rand_int);
|
||||
|
||||
// Seed the MFC LFSR PRNG with the same hardware random value.
|
||||
// This makes nonce generation follow the real Mifare Classic LFSR pattern
|
||||
// so readers that fingerprint PRNG type (e.g. Eltis) accept the emulated card.
|
||||
nfc_tag_mf1_prng_seed(rand_int);
|
||||
}
|
||||
|
||||
/**@brief Initialize GPIO matrix library
|
||||
@@ -152,41 +139,12 @@ static void gpio_te_init(void) {
|
||||
APP_ERROR_CHECK(err_code);
|
||||
}
|
||||
|
||||
#if defined(PROJECT_CHAMELEON_ULTRA)
|
||||
static void field_generator_rainbow_loop(void) {
|
||||
static uint8_t color_index = 0;
|
||||
static uint32_t last_update = 0;
|
||||
|
||||
if (!m_is_field_on) return;
|
||||
|
||||
uint32_t now = app_timer_cnt_get();
|
||||
|
||||
if (app_timer_cnt_diff_compute(now, last_update) < APP_TIMER_TICKS(100)) {
|
||||
return;
|
||||
}
|
||||
last_update = now;
|
||||
|
||||
// Rainbow colors
|
||||
const uint8_t colors[] = {RGB_RED, RGB_YELLOW, RGB_GREEN, RGB_CYAN, RGB_BLUE, RGB_MAGENTA};
|
||||
|
||||
set_slot_light_color(colors[color_index]);
|
||||
uint32_t *led_pins = hw_get_led_array();
|
||||
|
||||
// Light up all LEDs with current color
|
||||
for (int i = 0; i < RGB_LIST_NUM; i++) {
|
||||
nrf_gpio_pin_set(led_pins[i]);
|
||||
}
|
||||
|
||||
color_index = (color_index + 1) % 6;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**@brief Button Matrix Events
|
||||
*/
|
||||
static void button_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
|
||||
device_mode_t mode = get_device_mode();
|
||||
// Allow button operations in both tag and reader mode
|
||||
if (mode == DEVICE_MODE_TAG || mode == DEVICE_MODE_READER) {
|
||||
// Temporarily allow only the analog card mode to respond to button operations
|
||||
if (mode == DEVICE_MODE_TAG) {
|
||||
static nrf_drv_gpiote_pin_t pin_static; // Use static internal variables to store the GPIO where the current event occurred
|
||||
pin_static = pin; // Cache the button that currently triggers the event into an internal variable
|
||||
app_timer_start(m_button_check_timer, APP_TIMER_TICKS(50), &pin_static); // Start timer anti-shake
|
||||
@@ -204,9 +162,7 @@ static void timer_button_event_handle(void *arg) {
|
||||
NRF_LOG_INFO("BUTTON press during shutdown");
|
||||
return;
|
||||
}
|
||||
|
||||
nrf_drv_gpiote_pin_t pin = *(nrf_drv_gpiote_pin_t *)arg;
|
||||
|
||||
// Check here if the current GPIO is at the pressed level
|
||||
if (nrf_gpio_pin_read(pin) == 1) {
|
||||
if (pin == BUTTON_1) {
|
||||
@@ -307,28 +263,24 @@ static void system_off_enter(void) {
|
||||
for (uint8_t i = 0; i < RGB_LIST_NUM; i++) {
|
||||
nrf_gpio_pin_clear(p_led_array[i]);
|
||||
}
|
||||
// Power off animation
|
||||
uint8_t animation_config = settings_get_animation_config();
|
||||
uint8_t slot = tag_emulation_get_slot();
|
||||
uint8_t dir = slot > 3 ? 1 : 0;
|
||||
uint8_t color = get_color_by_slot(slot);
|
||||
if (m_reset_source & (NRF_POWER_RESETREAS_NFC_MASK | NRF_POWER_RESETREAS_LPCOMP_MASK)) {
|
||||
if (m_reset_source & NRF_POWER_RESETREAS_NFC_MASK) {
|
||||
color = 1;
|
||||
} else {
|
||||
color = 2;
|
||||
}
|
||||
}
|
||||
if (animation_config == SettingsAnimationModeFull) {
|
||||
if (m_system_off_processing) rgb_marquee_sweep_from_to(color, slot, dir ? 7 : 0);
|
||||
if (m_system_off_processing) rgb_marquee_sweep_fade(color, dir, 7, 99, 75);
|
||||
if (m_system_off_processing) rgb_marquee_sweep_fade(color, !dir, 7, 75, 50);
|
||||
if (m_system_off_processing) rgb_marquee_sweep_fade(color, dir, 7, 50, 25);
|
||||
if (m_system_off_processing) rgb_marquee_sweep_fade(color, !dir, 7, 25, 0);
|
||||
} else if (animation_config == SettingsAnimationModeMinimal) {
|
||||
if (m_system_off_processing) rgb_marquee_sweep_from_to(color, slot, !dir ? 7 : 0);
|
||||
} else if (animation_config == SettingsAnimationModeSymmetric) {
|
||||
if (m_system_off_processing) rgb_marquee_symmetric_in(color, slot);
|
||||
uint8_t slot = tag_emulation_get_slot();
|
||||
// Power off animation
|
||||
uint8_t dir = slot > 3 ? 1 : 0;
|
||||
uint8_t color = get_color_by_slot(slot);
|
||||
if (m_reset_source & (NRF_POWER_RESETREAS_NFC_MASK | NRF_POWER_RESETREAS_LPCOMP_MASK)) {
|
||||
if (m_reset_source & NRF_POWER_RESETREAS_NFC_MASK) {
|
||||
color = 1;
|
||||
} else {
|
||||
color = 2;
|
||||
}
|
||||
}
|
||||
if (m_system_off_processing) ledblink5(color, slot, dir ? 7 : 0);
|
||||
if (m_system_off_processing) ledblink4(color, dir, 7, 99, 75);
|
||||
if (m_system_off_processing) ledblink4(color, !dir, 7, 75, 50);
|
||||
if (m_system_off_processing) ledblink4(color, dir, 7, 50, 25);
|
||||
if (m_system_off_processing) ledblink4(color, !dir, 7, 25, 0);
|
||||
}
|
||||
rgb_marquee_stop();
|
||||
if (!m_system_off_processing) {
|
||||
@@ -470,13 +422,11 @@ static void check_wakeup_src(void) {
|
||||
// Button wake-up boot animation
|
||||
uint8_t animation_config = settings_get_animation_config();
|
||||
if (animation_config == SettingsAnimationModeFull) {
|
||||
rgb_marquee_sweep_to(color, !dir, 11);
|
||||
rgb_marquee_sweep_to(color, dir, 11);
|
||||
rgb_marquee_sweep_to(color, !dir, dir ? slot : 7 - slot);
|
||||
ledblink2(color, !dir, 11);
|
||||
ledblink2(color, dir, 11);
|
||||
ledblink2(color, !dir, dir ? slot : 7 - slot);
|
||||
} else if (animation_config == SettingsAnimationModeMinimal) {
|
||||
rgb_marquee_sweep_to(color, !dir, dir ? slot : 7 - slot);
|
||||
} else if (animation_config == SettingsAnimationModeSymmetric) {
|
||||
rgb_marquee_symmetric_out(color, slot);
|
||||
ledblink2(color, !dir, dir ? slot : 7 - slot);
|
||||
} else {
|
||||
set_slot_light_color(color);
|
||||
}
|
||||
@@ -509,12 +459,9 @@ static void check_wakeup_src(void) {
|
||||
uint8_t animation_config = settings_get_animation_config();
|
||||
if (animation_config == SettingsAnimationModeFull) {
|
||||
// In the case of field wake-up, only one round of RGB is swept as the power-on animation
|
||||
rgb_marquee_sweep_to(color, !dir, dir ? slot : 7 - slot);
|
||||
} else if (animation_config == SettingsAnimationModeSymmetric) {
|
||||
rgb_marquee_symmetric_out(color, slot);
|
||||
} else {
|
||||
set_slot_light_color(color);
|
||||
ledblink2(color, !dir, dir ? slot : 7 - slot);
|
||||
}
|
||||
set_slot_light_color(color);
|
||||
light_up_by_slot();
|
||||
|
||||
// We can only run tag emulation at field wakeup source.
|
||||
@@ -541,20 +488,9 @@ static void check_wakeup_src(void) {
|
||||
tag_emulation_factory_init();
|
||||
|
||||
// RGB
|
||||
uint8_t animation_config = settings_get_animation_config();
|
||||
if (animation_config == SettingsAnimationModeFull) {
|
||||
rgb_marquee_sweep_to(0, !dir, 11);
|
||||
rgb_marquee_sweep_to(1, dir, 11);
|
||||
rgb_marquee_sweep_to(2, !dir, 11);
|
||||
} else if (animation_config == SettingsAnimationModeMinimal) {
|
||||
rgb_marquee_sweep_from_to(0, 0, 2);
|
||||
rgb_marquee_sweep_from_to(1, 2, 5);
|
||||
rgb_marquee_sweep_from_to(2, 5, 7);
|
||||
} else if (animation_config == SettingsAnimationModeSymmetric) {
|
||||
rgb_marquee_symmetric_out(0, ~0);
|
||||
rgb_marquee_symmetric_in(1, ~0);
|
||||
rgb_marquee_symmetric_out(2, ~0);
|
||||
}
|
||||
ledblink2(0, !dir, 11);
|
||||
ledblink2(1, dir, 11);
|
||||
ledblink2(2, !dir, 11);
|
||||
|
||||
// Show RGB for slot.
|
||||
set_slot_light_color(color);
|
||||
@@ -585,12 +521,6 @@ static void cycle_slot(bool dec) {
|
||||
}
|
||||
// Update status only if the new card slot switch is valid
|
||||
tag_emulation_change_slot(slot_new, true); // Tell the analog card module that we need to switch card slots
|
||||
// Turn off the LEDs in case we were showing the battery status
|
||||
rgb_marquee_stop();
|
||||
uint32_t *led_pins = hw_get_led_array();
|
||||
for (int i = 0; i < RGB_LIST_NUM; i++) {
|
||||
nrf_gpio_pin_clear(led_pins[i]);
|
||||
}
|
||||
// Go back to the color corresponding to the field enablement type
|
||||
apply_slot_change(slot_now, slot_new);
|
||||
}
|
||||
@@ -673,38 +603,16 @@ static void btn_fn_copy_lf(uint8_t slot, tag_specific_type_t type) {
|
||||
size = LF_HIDPROX_TAG_ID_SIZE;
|
||||
data = id_buffer;
|
||||
break;
|
||||
case TAG_TYPE_IOPROX:
|
||||
status = scan_ioprox(id_buffer, 0);
|
||||
size = LF_IOPROX_TAG_ID_SIZE;
|
||||
data = id_buffer;
|
||||
break;
|
||||
case TAG_TYPE_EM410X:
|
||||
case TAG_TYPE_EM410X_ELECTRA: {
|
||||
status = scan_em410x(id_buffer);
|
||||
tag_specific_type_t detected_type = (id_buffer[0] << 8) | id_buffer[1];
|
||||
tag_specific_type_t new_type =
|
||||
detected_type == TAG_TYPE_EM410X_ELECTRA ? TAG_TYPE_EM410X_ELECTRA : TAG_TYPE_EM410X;
|
||||
|
||||
// If we read Electra but the slot was classic (or vice versa), switch slot type automatically.
|
||||
if (new_type != type) {
|
||||
tag_emulation_change_type(slot, new_type);
|
||||
type = new_type;
|
||||
}
|
||||
|
||||
size = (new_type == TAG_TYPE_EM410X_ELECTRA) ? LF_EM410X_ELECTRA_TAG_ID_SIZE : LF_EM410X_TAG_ID_SIZE;
|
||||
data = id_buffer + 2; // skip tag type
|
||||
size = LF_EM410X_TAG_ID_SIZE;
|
||||
data = id_buffer + 2; // skip tag type
|
||||
break;
|
||||
}
|
||||
case TAG_TYPE_VIKING:
|
||||
status = scan_viking(id_buffer);
|
||||
size = LF_VIKING_TAG_ID_SIZE;
|
||||
data = id_buffer;
|
||||
break;
|
||||
case TAG_TYPE_JABLOTRON:
|
||||
status = scan_jablotron(id_buffer);
|
||||
size = LF_JABLOTRON_TAG_ID_SIZE;
|
||||
data = id_buffer;
|
||||
break;
|
||||
default:
|
||||
NRF_LOG_ERROR("Unsupported LF tag type")
|
||||
offline_status_error();
|
||||
@@ -849,67 +757,13 @@ static void run_button_function_by_settings(settings_button_function_t sbf) {
|
||||
case SettingsButtonCloneIcUid:
|
||||
btn_fn_copy_ic_uid();
|
||||
break;
|
||||
case SettingsButtonNfcFieldGenerator:
|
||||
if (!m_is_field_on) {
|
||||
// Initialize reader hardware if not already in reader mode
|
||||
device_mode_t current_mode = get_device_mode();
|
||||
if (current_mode != DEVICE_MODE_READER) {
|
||||
// Temporarily init reader hardware just for the field
|
||||
nrf_gpio_cfg_output(READER_POWER);
|
||||
nrf_gpio_pin_set(READER_POWER); // reader power enable
|
||||
nrf_gpio_cfg_output(HF_ANT_SEL);
|
||||
nrf_gpio_pin_clear(HF_ANT_SEL); // hf ant switch to reader mode
|
||||
|
||||
pcd_14a_reader_init();
|
||||
bsp_delay_ms(10);
|
||||
}
|
||||
|
||||
pcd_14a_reader_reset();
|
||||
pcd_14a_reader_antenna_on();
|
||||
m_is_field_on = true;
|
||||
NRF_LOG_INFO("NFC field ON");
|
||||
|
||||
// Set initial rainbow state
|
||||
set_slot_light_color(RGB_RED);
|
||||
uint32_t *led_pins = hw_get_led_array();
|
||||
for (int i = 0; i < RGB_LIST_NUM; i++) {
|
||||
nrf_gpio_pin_set(led_pins[i]);
|
||||
}
|
||||
|
||||
// Stop sleep timer while field is active
|
||||
NRF_LOG_INFO("Stopping sleep timer for field generator");
|
||||
sleep_timer_stop();
|
||||
NRF_LOG_INFO("Sleep timer stopped");
|
||||
} else {
|
||||
pcd_14a_reader_antenna_off();
|
||||
m_is_field_on = false;
|
||||
NRF_LOG_INFO("NFC field OFF");
|
||||
|
||||
// If we're not in reader mode, clean up the hardware
|
||||
device_mode_t current_mode = get_device_mode();
|
||||
if (current_mode != DEVICE_MODE_READER) {
|
||||
pcd_14a_reader_uninit();
|
||||
nrf_gpio_pin_clear(READER_POWER); // reader power disable
|
||||
nrf_gpio_pin_set(HF_ANT_SEL); // hf ant switch back to tag mode
|
||||
}
|
||||
|
||||
// Restore normal LED
|
||||
light_up_by_slot();
|
||||
|
||||
// Restart sleep timer
|
||||
NRF_LOG_INFO("Field off, restarting sleep timer");
|
||||
sleep_timer_start(SLEEP_DELAY_MS_BUTTON_CLICK);
|
||||
NRF_LOG_INFO("Sleep timer restarted");
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case SettingsButtonShowBattery:
|
||||
show_battery();
|
||||
break;
|
||||
|
||||
default:
|
||||
NRF_LOG_ERROR("Unsupported button function");
|
||||
NRF_LOG_ERROR("Unsupported button function")
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -938,10 +792,8 @@ static void button_press_process(void) {
|
||||
}
|
||||
// Disable led marquee for usb at button pressed.
|
||||
g_usb_led_marquee_enable = false;
|
||||
// Re-delay into hibernation (unless field is on)
|
||||
if (!m_is_field_on) {
|
||||
sleep_timer_start(SLEEP_DELAY_MS_BUTTON_CLICK);
|
||||
}
|
||||
// Re-delay into hibernation
|
||||
sleep_timer_start(SLEEP_DELAY_MS_BUTTON_CLICK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,17 +812,12 @@ static void blink_usb_led_status(void) {
|
||||
}
|
||||
} else {
|
||||
// The light effect is enabled and can be displayed
|
||||
if (rgb_marquee_is_enabled()) {
|
||||
if (is_rgb_marquee_enable()) {
|
||||
is_working = true;
|
||||
if (g_usb_port_opened) {
|
||||
uint8_t animation_config = settings_get_animation_config();
|
||||
if (animation_config == SettingsAnimationModeSymmetric) {
|
||||
rgb_marquee_usb_open_symmetric(color);
|
||||
} else {
|
||||
rgb_marquee_usb_open_sweep(color, dir);
|
||||
}
|
||||
ledblink1(color, dir);
|
||||
} else {
|
||||
rgb_marquee_usb_idle();
|
||||
ledblink6();
|
||||
}
|
||||
} else {
|
||||
if (is_working) {
|
||||
@@ -1040,17 +887,8 @@ int main(void) {
|
||||
lesc_event_process();
|
||||
// Button event process
|
||||
button_press_process();
|
||||
|
||||
#if defined(PROJECT_CHAMELEON_ULTRA)
|
||||
// Field generator rainbow animation
|
||||
field_generator_rainbow_loop();
|
||||
#endif
|
||||
|
||||
// Led blink at usb status (only if field generator is off)
|
||||
if (!m_is_field_on) {
|
||||
blink_usb_led_status();
|
||||
}
|
||||
|
||||
// Led blink at usb status
|
||||
blink_usb_led_status();
|
||||
// Data pack process
|
||||
data_frame_process();
|
||||
// Log print process
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#define STATUS_LF_TAG_OK (0x40) // Some of the low -frequency cards are successful!
|
||||
#define STATUS_LF_TAG_NO_FOUND (0x41) // Can't search for valid LF tags
|
||||
#define STATUS_LF_TAG_LOGIN_REQUIRED (0x42) // Tag requires LOGIN before read
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// other status
|
||||
@@ -32,9 +31,5 @@
|
||||
#define STATUS_FLASH_WRITE_FAIL (0x70) // Flash writing failed
|
||||
#define STATUS_FLASH_READ_FAIL (0x71) // Flash read failed
|
||||
#define STATUS_INVALID_SLOT_TYPE (0x72) // Invalid slot type
|
||||
#define STATUS_MEM_ERR (0x73) // Can't allocate memory or work with memory error
|
||||
#define STATUS_CREATE_RESPONSE_ERR (0x74) // Can't create response for command
|
||||
#define STATUS_CMD_ERR (0x75) // Execution of command failed
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user