433Mhz trip sensor system: Difference between revisions
From Pessin randon wiki
No edit summary |
|||
| Line 161: | Line 161: | ||
== Lähettimen lähdekoodi == | == Lähettimen lähdekoodi == | ||
<syntaxhighlight lang="c++" line="1"> | |||
#include <RH_ASK.h> | |||
#include <Wire.h> | |||
#include <Adafruit_GFX.h> | |||
#include <Adafruit_SSD1306.h> | |||
#define SCREEN_WIDTH 128 | |||
#define SCREEN_HEIGHT 32 | |||
#define OLED_RESET -1 | |||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); | |||
// Radiovastaanotin | |||
RH_ASK driver(1000, 12, 11, 10); // RX = D12 | |||
// Ajastukset | |||
const unsigned long ENTRY_TIMEOUT = 30 * 60 * 1000UL; // 30 min | |||
const unsigned long DISPLAY_DURATION = 2500; // 2.5 s | |||
// Seuraava ruutu nappi | |||
#define BUTTON_PIN 2 | |||
// Kiinteät DIP-koodit (16 kpl) | |||
const char* dipIdMap[16] = { | |||
"0000", "0001", "0010", "0011", | |||
"0100", "0101", "0110", "0111", | |||
"1000", "1001", "1010", "1011", | |||
"1100", "1101", "1110", "1111" | |||
}; | |||
//Vilkku | |||
bool blinkState = false; | |||
unsigned long lastBlink = 0; | |||
const unsigned long BLINK_INTERVAL = 500; | |||
// Tallennetaan viimeisin aikaleima DIP-koodeille | |||
unsigned long dipSeen[16] = {0}; | |||
// Painallusmuuttujat | |||
bool buttonPressed = false; | |||
unsigned long lastDisplaySwitch = 0; | |||
// CRC8 Dallas/Maxim | |||
byte crc8_dallas(const char* data, uint8_t len) { | |||
byte crc = 0x00; | |||
for (uint8_t i = 0; i < len; i++) { | |||
crc ^= data[i]; | |||
for (uint8_t j = 0; j < 8; j++) { | |||
crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1); | |||
} | |||
} | |||
return crc; | |||
} | |||
// Palauttaa ID:n 1–16 tai 0 jos tuntematon | |||
int getDipId(const char* dip) { | |||
for (int i = 0; i < 16; i++) { | |||
if (strncmp(dipIdMap[i], dip, 4) == 0) { | |||
return i + 1; | |||
} | |||
} | |||
return 0; | |||
} | |||
void setup() { | |||
// Serial.begin(9600); | |||
// delay(1000); | |||
pinMode(BUTTON_PIN, INPUT_PULLUP); | |||
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { | |||
display.clearDisplay(); | |||
display.setTextSize(1); | |||
display.setCursor(0, 0); | |||
display.println(F("OLED ei vastaa")); | |||
display.display(); | |||
while (1); // Pysäyttää ohjelman | |||
} | |||
display.clearDisplay(); | |||
display.setTextSize(1); | |||
display.setTextColor(SSD1306_WHITE); | |||
display.setCursor(0, 0); | |||
display.println(F("Odotetaan...")); | |||
display.display(); | |||
if (!driver.init()) { | |||
display.clearDisplay(); | |||
display.setCursor(0, 0); | |||
display.println(F("Radio ei vastaa")); | |||
display.display(); | |||
while (1); // | |||
} | |||
pinMode(12, INPUT); | |||
} | |||
void loop() { | |||
receiveRadio(); | |||
cleanExpiredEntries(); | |||
checkButton(); | |||
updateDisplay(); | |||
delay(10); | |||
} | |||
void checkButton() { | |||
static bool prevState = HIGH; | |||
bool state = digitalRead(BUTTON_PIN); | |||
if (prevState == HIGH && state == LOW) { | |||
buttonPressed = true; | |||
} | |||
prevState = state; | |||
} | |||
void receiveRadio() { | |||
uint8_t buf[32]; | |||
uint8_t buflen = sizeof(buf); | |||
if (driver.recv(buf, &buflen)) { | |||
if (buflen < 8 || buf[0] != 'S' || buf[5] != 'E') return; | |||
byte expectedCrc = crc8_dallas((char*)buf, 6); | |||
char crcStr[3] = { (char)buf[6], (char)buf[7], '\0' }; | |||
byte receivedCrc = strtoul(crcStr, NULL, 16); | |||
if (expectedCrc != receivedCrc) return; | |||
char dip[5]; | |||
memcpy(dip, &buf[1], 4); | |||
dip[4] = '\0'; | |||
int dipId = getDipId(dip); | |||
/* | |||
if (dipId == 0) { | |||
Serial.print(F("⚠️ Tuntematon DIP: ")); | |||
Serial.println(dip); | |||
return; | |||
} | |||
Serial.print(F("✅ DIP ")); | |||
Serial.print(dip); | |||
Serial.print(F(" → ID ")); | |||
Serial.println(dipId); | |||
*/ | |||
dipSeen[dipId - 1] = millis(); // Päivitä aikaleima | |||
} | |||
} | |||
void cleanExpiredEntries() { | |||
unsigned long now = millis(); | |||
for (int i = 0; i < 16; i++) { | |||
if (dipSeen[i] != 0 && now - dipSeen[i] > ENTRY_TIMEOUT) { | |||
dipSeen[i] = 0; // Nollaa aikaleima = ei enää aktiivinen | |||
} | |||
} | |||
} | |||
void updateDisplay() { | |||
static int currentActiveIndex = -1; | |||
unsigned long now = millis(); | |||
int allSeenIndices[16]; | |||
int seenCount = 0; | |||
int activeCount = 0; | |||
int passiveCount = 0; | |||
for (int i = 0; i < 16; i++) { | |||
if (dipSeen[i] != 0) { | |||
seenCount++; | |||
unsigned long ageMin = (now - dipSeen[i]) / 60000UL; | |||
if (ageMin <= 5) { | |||
activeCount++; | |||
} else { | |||
passiveCount++; | |||
} | |||
allSeenIndices[seenCount - 1] = i; | |||
} | |||
} | |||
if (seenCount > 0 && (now - lastDisplaySwitch >= DISPLAY_DURATION || buttonPressed)) { | |||
currentActiveIndex = (currentActiveIndex + 1) % seenCount; | |||
lastDisplaySwitch = now; | |||
buttonPressed = false; | |||
} | |||
display.clearDisplay(); | |||
if (seenCount == 0) { | |||
display.setTextSize(1); | |||
display.setCursor(0, 0); | |||
display.println(F("Kuunnellaan radiota...")); | |||
} else { | |||
int dipIndex = allSeenIndices[currentActiveIndex]; | |||
int id = dipIndex + 1; | |||
unsigned long ageMin = (now - dipSeen[dipIndex]) / 60000UL; | |||
bool isPassive = (ageMin > 5); | |||
// ISO ID vasemmalle | |||
display.setTextSize(4); | |||
display.setCursor(0, 0); | |||
display.print(id); | |||
if (!isPassive) display.print("!"); | |||
// Ruutuindeksi | |||
display.setTextSize(1); | |||
int xTextStart = SCREEN_WIDTH - 6 * 10; | |||
char indexStr[20]; | |||
snprintf(indexStr, sizeof(indexStr), "%d/(%d)/[%d]", activeCount, passiveCount, seenCount); | |||
display.setCursor(xTextStart, 0); | |||
display.print(indexStr); | |||
// PASSIVE-info jos tarpeen | |||
if (isPassive) { | |||
char timeStr[10]; | |||
snprintf(timeStr, sizeof(timeStr), "%lum", ageMin); | |||
display.setCursor(xTextStart, 8); | |||
display.print(timeStr); | |||
display.setCursor(xTextStart, 16); | |||
display.print(F("PASSIVE")); | |||
} | |||
// DIP-koodi alariville | |||
display.setCursor(xTextStart, SCREEN_HEIGHT - 8); | |||
display.print(dipIdMap[dipIndex]); | |||
} | |||
// YHTENÄINEN vilkkupiste – toimii aina | |||
if (now - lastBlink >= BLINK_INTERVAL) { | |||
blinkState = !blinkState; | |||
lastBlink = now; | |||
} | |||
if (blinkState) { | |||
display.fillRect(SCREEN_WIDTH - 3, SCREEN_HEIGHT - 3, 2, 2, SSD1306_WHITE); | |||
} | |||
display.display(); | |||
} | |||
</syntaxhighlight> | |||
Revision as of 21:41, 19 June 2025
Vastaanottimen käyttö
Tämä laite vastaanottaa 433 MHz -radioviestejä, joissa on kiinteitä DIP-koodeja. Jokaisella DIP-koodilla on pysyvä ID-numero (1–16). Näytöllä näkyy aina yksi koodi kerrallaan.
Mitä näyttö kertoo?
Näyttö on jaettu kahteen osaan:
- Vasemmassa reunassa (suurena):
- ID-numero (esim.
5) - Huutomerkki
!näkyy, jos koodi on aktiivinen (eli viesti saatu viimeisen 5 minuutin aikana)
🔹 Oikeassa reunassa (pienellä tekstillä):
- Ylärivi: Esim.
2 / (3) / [5]2= aktiivisten määrä(3)= passiivisten määrä (viesti yli 5 min vanha)[5]= kaikkien vastaanotettujen määrä
- Toinen rivi (vain jos koodi on passiivinen):
- Aika viimeisestä viestistä minuutteina (esim.
17m)
- Aika viimeisestä viestistä minuutteina (esim.
- Kolmas rivi (vain jos passiivinen):
- Sana
PASSIVE
- Sana
- Alarivi:
- DIP-koodi (esim.
0101)
- DIP-koodi (esim.
ID Dip taulukko
| ID | DIP-koodi |
|---|---|
| 1 | 0000 |
| 2 | 0001 |
| 3 | 0010 |
| 4 | 0011 |
| 5 | 0100 |
| 6 | 0101 |
| 7 | 0110 |
| 8 | 0111 |
| 9 | 1000 |
| 10 | 1001 |
| 11 | 1010 |
| 12 | 1011 |
| 13 | 1100 |
| 14 | 1101 |
| 15 | 1110 |
| 16 | 1111 |
Vastaanottimen kytkentäkaavio ja tarvittavat osat
Tarvittavat osat:
| Komponentti | Määrä |
|---|---|
| Arduino Nano v3 | 1 |
| 433 MHz ASK-radiovastaanotin | 1 |
| OLED-näyttö (128×32, I2C, SSD1306) | 1 |
| Momenttikytkin (painonappi) | 1 |
Näytön kytkentä
| OLED-pinni | Arduino Nano -pinni | Selitys |
|---|---|---|
| VCC | 5V | Virransyöttö |
| GND | GND | Maa |
| SDA | A4 | I2C-data |
| SCL | A5 | I2C-kello |
Vastaanottimen kytkentä
| Vastaanottimen pinni | Arduino Nano -pinni | Selitys |
|---|---|---|
| VCC | 5V | Virransyöttö |
| GND | GND | Maa |
| DATA (OUT) | D12 | Vastaanottodata |
Näytön pikarullausnappi
| Nappi | Arduino Nano | Selitys |
|---|---|---|
| 1 | D2 | Luku (INPUT_PULLUP) |
| 2 | GND | Painettaessa yhdistää maahan |
Lisää tarvittavat kirjastot
- RadioHead (RH_ASK)
- Adafruit SSD1306
- Adafruit GFX
Lähettimen lähdekoodi
#include <RH_ASK.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Radiovastaanotin
RH_ASK driver(1000, 12, 11, 10); // RX = D12
// Ajastukset
const unsigned long ENTRY_TIMEOUT = 30 * 60 * 1000UL; // 30 min
const unsigned long DISPLAY_DURATION = 2500; // 2.5 s
// Seuraava ruutu nappi
#define BUTTON_PIN 2
// Kiinteät DIP-koodit (16 kpl)
const char* dipIdMap[16] = {
"0000", "0001", "0010", "0011",
"0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011",
"1100", "1101", "1110", "1111"
};
//Vilkku
bool blinkState = false;
unsigned long lastBlink = 0;
const unsigned long BLINK_INTERVAL = 500;
// Tallennetaan viimeisin aikaleima DIP-koodeille
unsigned long dipSeen[16] = {0};
// Painallusmuuttujat
bool buttonPressed = false;
unsigned long lastDisplaySwitch = 0;
// CRC8 Dallas/Maxim
byte crc8_dallas(const char* data, uint8_t len) {
byte crc = 0x00;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1);
}
}
return crc;
}
// Palauttaa ID:n 1–16 tai 0 jos tuntematon
int getDipId(const char* dip) {
for (int i = 0; i < 16; i++) {
if (strncmp(dipIdMap[i], dip, 4) == 0) {
return i + 1;
}
}
return 0;
}
void setup() {
// Serial.begin(9600);
// delay(1000);
pinMode(BUTTON_PIN, INPUT_PULLUP);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.println(F("OLED ei vastaa"));
display.display();
while (1); // Pysäyttää ohjelman
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Odotetaan..."));
display.display();
if (!driver.init()) {
display.clearDisplay();
display.setCursor(0, 0);
display.println(F("Radio ei vastaa"));
display.display();
while (1); //
}
pinMode(12, INPUT);
}
void loop() {
receiveRadio();
cleanExpiredEntries();
checkButton();
updateDisplay();
delay(10);
}
void checkButton() {
static bool prevState = HIGH;
bool state = digitalRead(BUTTON_PIN);
if (prevState == HIGH && state == LOW) {
buttonPressed = true;
}
prevState = state;
}
void receiveRadio() {
uint8_t buf[32];
uint8_t buflen = sizeof(buf);
if (driver.recv(buf, &buflen)) {
if (buflen < 8 || buf[0] != 'S' || buf[5] != 'E') return;
byte expectedCrc = crc8_dallas((char*)buf, 6);
char crcStr[3] = { (char)buf[6], (char)buf[7], '\0' };
byte receivedCrc = strtoul(crcStr, NULL, 16);
if (expectedCrc != receivedCrc) return;
char dip[5];
memcpy(dip, &buf[1], 4);
dip[4] = '\0';
int dipId = getDipId(dip);
/*
if (dipId == 0) {
Serial.print(F("⚠️ Tuntematon DIP: "));
Serial.println(dip);
return;
}
Serial.print(F("✅ DIP "));
Serial.print(dip);
Serial.print(F(" → ID "));
Serial.println(dipId);
*/
dipSeen[dipId - 1] = millis(); // Päivitä aikaleima
}
}
void cleanExpiredEntries() {
unsigned long now = millis();
for (int i = 0; i < 16; i++) {
if (dipSeen[i] != 0 && now - dipSeen[i] > ENTRY_TIMEOUT) {
dipSeen[i] = 0; // Nollaa aikaleima = ei enää aktiivinen
}
}
}
void updateDisplay() {
static int currentActiveIndex = -1;
unsigned long now = millis();
int allSeenIndices[16];
int seenCount = 0;
int activeCount = 0;
int passiveCount = 0;
for (int i = 0; i < 16; i++) {
if (dipSeen[i] != 0) {
seenCount++;
unsigned long ageMin = (now - dipSeen[i]) / 60000UL;
if (ageMin <= 5) {
activeCount++;
} else {
passiveCount++;
}
allSeenIndices[seenCount - 1] = i;
}
}
if (seenCount > 0 && (now - lastDisplaySwitch >= DISPLAY_DURATION || buttonPressed)) {
currentActiveIndex = (currentActiveIndex + 1) % seenCount;
lastDisplaySwitch = now;
buttonPressed = false;
}
display.clearDisplay();
if (seenCount == 0) {
display.setTextSize(1);
display.setCursor(0, 0);
display.println(F("Kuunnellaan radiota..."));
} else {
int dipIndex = allSeenIndices[currentActiveIndex];
int id = dipIndex + 1;
unsigned long ageMin = (now - dipSeen[dipIndex]) / 60000UL;
bool isPassive = (ageMin > 5);
// ISO ID vasemmalle
display.setTextSize(4);
display.setCursor(0, 0);
display.print(id);
if (!isPassive) display.print("!");
// Ruutuindeksi
display.setTextSize(1);
int xTextStart = SCREEN_WIDTH - 6 * 10;
char indexStr[20];
snprintf(indexStr, sizeof(indexStr), "%d/(%d)/[%d]", activeCount, passiveCount, seenCount);
display.setCursor(xTextStart, 0);
display.print(indexStr);
// PASSIVE-info jos tarpeen
if (isPassive) {
char timeStr[10];
snprintf(timeStr, sizeof(timeStr), "%lum", ageMin);
display.setCursor(xTextStart, 8);
display.print(timeStr);
display.setCursor(xTextStart, 16);
display.print(F("PASSIVE"));
}
// DIP-koodi alariville
display.setCursor(xTextStart, SCREEN_HEIGHT - 8);
display.print(dipIdMap[dipIndex]);
}
// YHTENÄINEN vilkkupiste – toimii aina
if (now - lastBlink >= BLINK_INTERVAL) {
blinkState = !blinkState;
lastBlink = now;
}
if (blinkState) {
display.fillRect(SCREEN_WIDTH - 3, SCREEN_HEIGHT - 3, 2, 2, SSD1306_WHITE);
}
display.display();
}