ANY_SCAN_MS209)Build your own single-scan or four-sensor scanner for AnyScan209 with Arduino, BASCOM, C or MicroPython. This page describes the exact data packet, command words and battery display used in Live Scan (professional scan) mode. The protocol is open and free to use under the MIT License.
In Live Scan mode a microcontroller with a Bluetooth serial module (HC-05, HC-06 or any SPP device) streams sensor readings to AnyScan209. The app can receive one sensor or four sensors at the same time, so a four-sensor scanner fills four rows at once and scans four times faster. The scanner also reports its battery level, which the app shows as a progress bar with a low-battery alarm.
Without the special name described below, the app stays in simple external mode: every incoming input moves the scan forward by one step. Both modes work, so scanners you already built keep working.
Rename your Bluetooth module to exactly:
ANY_SCAN_MS209
| Bluetooth module name | App behavior |
|---|---|
| No name, or any other name | Simple mode: each input advances the scan one step |
ANY_SCAN_MS209 | Live Scan options unlock: single scan, four scan, battery display, Next and Bal commands |
Copy the name exactly, including the underscores and digits. Most modules are renamed with an AT command, for example AT+NAME=ANY_SCAN_MS209 on an HC-05 (the command differs between modules, so check your module's datasheet). After renaming, pair the phone with the module again so the new name appears.
After the name is set, choose the scan type in the app options to match your hardware:
The option in the app must match what the scanner sends (see the next section). Scanning starts from the scan button in the app, or when the scanner sends the word Next.
Every scan packet is a numeric string of exactly 20 characters: five fields of four digits, each from 0000 to 1024 and always padded with leading zeros. Each packet ends with a normal line break (CR+LF), which Print in BASCOM and println in Arduino add automatically.
| Field | Digits | Meaning |
|---|---|---|
| 1 | 1 – 4 | Sensor 1 |
| 2 | 5 – 8 | Sensor 2 |
| 3 | 9 – 12 | Sensor 3 |
| 4 | 13 – 16 | Sensor 4 |
| 5 | 17 – 20 | Battery |
512 and the packet is rejected; send 0512.Field 1 holds the real value, fields 2–4 are sent as 0000, field 5 is the battery.
0512 0000 0000 0000 0980 → send as 05120000000000000980
All four sensor fields hold real values.
0512 0300 0700 0090 0980 → send as 05120300070000900980
Keep every value between 0 and 1024 (a 10-bit ADC range). The app clamps sensor values to a small safety margin beyond that range, but a well-behaved sender stays inside 0–1024.
Two optional words let the user control the app from the scanner. Send the word alone on its own line.
| Word | Meaning | What the app does |
|---|---|---|
Next | Go to the next scan | Same as pressing the scan button in the app |
Bal | Balance request | Shows the Balance Device message and starts the balance process |
Both words are optional. They are meant for two hardware buttons on the scanner, so the user does not have to touch the phone each time. If you skip the buttons nothing breaks: the app already has an on-screen scan button and a balance button. Adding the two hardware buttons is recommended for comfort.
The app knows nothing about voltages. Field 5 is just a number, and your scanner is responsible for mapping its real battery voltage to that number. The app turns the number into a percentage with a linear formula, averages 50 packets before updating the bar (so the icon does not flicker), and colors the bar from dark red (low) to yellow-green (full).
| Scanner type | Number for empty battery (0%) | Number for full battery (100%) |
|---|---|---|
| Magnetometer | 600 | 860 |
| Metal detector | 820 | 1024 |
Percentage formulas used by the app: magnetometer (value − 600) / 2.6, metal detector (value − 820) / 2.04; at or below the empty point the bar shows 3%.
Choose the empty and full voltages of your battery, then map linearly:
number = emptyNumber + (voltage − emptyVoltage) × (fullNumber − emptyNumber) / (fullVoltage − emptyVoltage)
Example, single lithium cell (3.3 V empty, 4.2 V full) on a magnetometer: number = 600 + (voltage − 3.3) × 260 / 0.9. The same formula works for any battery: 3.3–4.2 V, 9.6–12.4 V, 14–18 V and so on. Only the empty and full voltages change.
A microcontroller ADC can only read voltages up to its reference voltage, so a higher battery voltage must be reduced by a resistor voltage divider before it reaches the ADC pin. Designing the divider (resistor values, accuracy, protecting the ADC pin) is entirely the builder's responsibility.
The relationship between divided voltage and ADC value is nearly linear. If your divider cannot hit the ranges above exactly, correct the value in your microcontroller code with the mapping formula. Any AI assistant can calculate the resistors and the code for you (see section 9).
This follows the reference scanner: one sensor on ADC0, battery on ADC1 with a fixed offset of 305 so the number lands in the range the app expects. Calibrate the offset for your own hardware.
Dim Volt1 As Word Dim Sampl1 As Word Dim Magno11 As String * 4 Dim Volt11 As String * 4 Dim Volt12 As String * 4 Sampl1 = Getadc(0) Volt1 = Getadc(1) + 305 ' calibrate: empty battery = 600, full = 860 Magno11 = Str(sampl1) : Magno11 = Format(magno11 , "0000") Volt11 = Str(volt1) : Volt12 = Format(volt11 , "0000") ' Single scan: the three unused fields are mandatory Print #1 , Magno11 ; "0000" ; "0000" ; "0000" ; Volt12 ' Balance button Print #1 , "Bal"
const float V_EMPTY = 3.3, V_FULL = 4.2; // your battery
const int N_EMPTY = 600, N_FULL = 860; // magnetometer (metal detector: 820 to 1024)
const float ADC_REF = 5.0; // ADC reference voltage
const float DIVIDER = 1.0; // (R1+R2)/R2 of your voltage divider
int batteryNumber() {
float v = analogRead(A4) * ADC_REF / 1023.0 * DIVIDER;
float n = N_EMPTY + (v - V_EMPTY) * (N_FULL - N_EMPTY) / (V_FULL - V_EMPTY);
return constrain((int)n, 0, 1024);
}
void sendScan(int s1, int s2, int s3, int s4) {
char buf[21];
snprintf(buf, sizeof(buf), "%04d%04d%04d%04d%04d",
constrain(s1, 0, 1024), constrain(s2, 0, 1024),
constrain(s3, 0, 1024), constrain(s4, 0, 1024),
batteryNumber());
Serial.println(buf); // or bluetooth.println(buf)
}
void loop() {
// Four-sensor mode
sendScan(analogRead(A0), analogRead(A1), analogRead(A2), analogRead(A3));
// Single-scan mode: sendScan(analogRead(A0), 0, 0, 0);
// Optional buttons
// if (digitalRead(NEXT_PIN) == LOW) Serial.println("Next");
// if (digitalRead(BAL_PIN) == LOW) Serial.println("Bal");
}
def make_packet(s1, s2=0, s3=0, s4=0, batt=0):
vals = [max(0, min(1024, v)) for v in (s1, s2, s3, s4, batt)]
return "".join(f"{v:04d}" for v in vals)
print(make_packet(512, 300, 700, 90, 980)) # 05120300070000900980
print(make_packet(512, batt=980)) # 05120000000000000980Test with a serial terminal first: every line must be exactly 20 digits.
You do not need to be an embedded expert. Copy this page into an AI assistant and ask for the code for your board. A good request looks like this:
"Using the AnyScan209 Live Scan protocol on this page, write sender firmware for an ESP32 with an HC-05 module, four sensors, a 3.3–4.2 V lithium battery and a Next button. Also calculate the voltage divider resistors."
The assistant should ask for your microcontroller, language, Bluetooth baud rate, scan type (single or four), device type (magnetometer or metal detector), battery empty and full voltages, and the ADC reference voltage. Then it designs the divider, writes the battery mapping, builds the 20-character packet and reminds you to rename the Bluetooth module to ANY_SCAN_MS209.
Rename your Bluetooth module to exactly ANY_SCAN_MS209, pair it again, then choose single scan or four-sensor scan in the app options.
The packet must be exactly 20 characters: five fields of four digits with leading zeros. Unused sensor fields must be sent as 0000.
Magnetometer scanners send 600 for an empty battery and 860 for a full one. Metal detector scanners send 820 for empty and 1024 for full. Map your real voltage to this range linearly.
No. They are optional hardware conveniences. The app has its own scan and balance buttons.
Yes. The app only sees a number, so any battery works as long as you map its empty and full voltages to the expected range and scale the voltage down for the ADC with a divider.