|
|
| Line 1: |
Line 1: |
| == 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.
| |
| [[File:Vastaanotin-oled.jpg|thumb|Vastaanottimen oled näyttö]]
| |
| === Mitä näyttö kertoo? ===
| |
| Näyttö on jaettu kahteen osaan:
| |
|
| |
|
| * Vasemmassa reunassa (suurena):
| |
| ** '''ID-numero''' (esim. <code>5</code>)
| |
| ** '''Huutomerkki <code>!</code>''' näkyy, jos koodi on '''aktiivinen''' (viesti saatu viimeisen 5 minuutin aikana)
| |
|
| |
| * Oikeassa reunassa (pienellä tekstillä):
| |
| # '''Ylärivi:''' Esim. <code>2/(3)/[5]</code>
| |
| #* <code>2</code> = aktiivisten lähetysten määrä
| |
| #* <code>(3)</code> = passiivisten lähetysten määrä (viesti yli 5 min vanha)
| |
| #* <code>[5]</code> = kaikkien vastaanotettujen lähetyksien määrä
| |
| # '''Toinen rivi (vain jos koodi on passiivinen):'''
| |
| #* Aika viimeisestä viestistä minuutteina (esim. <code>17m</code>)
| |
| # '''Kolmas rivi (vain jos passiivinen):'''
| |
| #* Sana <code>PASSIVE</code>
| |
| # '''Alarivi:'''
| |
| #* DIP-koodi (esim. <code>0100</code>)
| |
|
| |
| == ID Dip taulukko ==
| |
| {| class="wikitable"
| |
| !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: ==
| |
| {| class="wikitable"
| |
| !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ä ====
| |
| {| class="wikitable"
| |
| !OLED-pinni
| |
| !Arduino Nano -pinni
| |
| !Selitys
| |
| |-
| |
| |VCC
| |
| |5V
| |
| |Virransyöttö
| |
| |-
| |
| |GND
| |
| |GND
| |
| |Maa
| |
| |-
| |
| |SDA
| |
| |A4
| |
| |I2C-data
| |
| |-
| |
| |SCL
| |
| |A5
| |
| |I2C-kello
| |
| |}
| |
|
| |
| ==== Vastaanottimen kytkentä ====
| |
| {| class="wikitable"
| |
| !Vastaanottimen pinni
| |
| !Arduino Nano -pinni
| |
| !Selitys
| |
| |-
| |
| |VCC
| |
| |5V
| |
| |Virransyöttö
| |
| |-
| |
| |GND
| |
| |GND
| |
| |Maa
| |
| |-
| |
| |DATA (OUT)
| |
| |D12
| |
| |Vastaanottodata
| |
| |}
| |
|
| |
| ==== Näytön pikarullausnappi ====
| |
| {| class="wikitable"
| |
| !Nappi
| |
| !Arduino Nano
| |
| !Selitys
| |
| |-
| |
| |1
| |
| |D2
| |
| |Luku (INPUT_PULLUP)
| |
| |-
| |
| |2
| |
| |GND
| |
| |Painettaessa yhdistää maahan
| |
| |}
| |
|
| |
| ==== Lisää tarvittavat kirjastot ====
| |
|
| |
| * RadioHead (RH_ASK)
| |
| * Adafruit SSD1306
| |
| * Adafruit GFX
| |
| * Wire
| |
|
| |
| == 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;
| |
|
| |
| // Kerätään vastaanotetut DIP:t ja lasketaan aktiiviset/passiiviset
| |
| for (int i = 0; i < 16; i++) {
| |
| if (dipSeen[i] != 0) {
| |
| allSeenIndices[seenCount++] = i;
| |
| unsigned long ageMin = (now - dipSeen[i]) / 60000UL;
| |
| if (ageMin <= 5) {
| |
| activeCount++;
| |
| } else {
| |
| passiveCount++;
| |
| }
| |
| }
| |
| }
| |
|
| |
| display.clearDisplay();
| |
|
| |
| if (seenCount <= 0) {
| |
| // Jos ei ole aktiivisia koodeja
| |
| currentActiveIndex = -1;
| |
| display.setTextSize(1);
| |
| display.setCursor(0, 0);
| |
| display.println(F("Kuunnellaan radiota..."));
| |
|
| |
| // Vilkku aina näkyviin
| |
| 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();
| |
| return;
| |
| }
| |
|
| |
| // Päivitä ruutu vain tarvittaessa
| |
| if (now - lastDisplaySwitch >= DISPLAY_DURATION || buttonPressed) {
| |
| currentActiveIndex = (currentActiveIndex + 1) % seenCount;
| |
| lastDisplaySwitch = now;
| |
| buttonPressed = false;
| |
| }
| |
|
| |
| // Haetaan näytettävä DIP
| |
| int dipIndex = allSeenIndices[currentActiveIndex];
| |
| int id = dipIndex + 1;
| |
|
| |
| unsigned long ageMin = (now - dipSeen[dipIndex]) / 60000UL;
| |
| bool isPassive = (ageMin > 5);
| |
|
| |
| // Vasemmalle iso ID-numero
| |
| display.setTextSize(4);
| |
| display.setCursor(0, 0);
| |
| display.print(id);
| |
| if (!isPassive) {
| |
| display.print("!");
| |
| }
| |
|
| |
| // Oikealle tilastorivi
| |
| 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-merkinnät jos yli 5 min vanha
| |
| 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]);
| |
|
| |
| // Vilkku (aina näkyvissä)
| |
| 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>
| |