433Mhz trip wire sensor system: Difference between revisions

From Pessin randon wiki
 
(6 intermediate revisions by the same user not shown)
Line 1: Line 1:
Järjestelmä on suunniteltu tilanteisiin, jossa halutaan ilmoitus jostain mekaanisesta liikkeestä tai kulusta. Lähettimestä voidaan säätää lähetystunnust 0-15 välille säädettävissä. Vastaanotin lukee radioviestin ja näyttää sen näytöltä ja laskee milloin lähetin on viimeksi vastaanottanut kyseisen ID numeron.
== Vastaanotinyksikkö ==
== Vastaanotinyksikkö ==


=== Vastaanottimen käyttö ===
=== 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.
Vastaanotin vastaanottaa radiolähetystä, jossa mukana tarkistussuma, sekä lähettimen ID sekä näyttää sen näytöllä saapumisjärjestyksessä vanhimmasta uusimpaan. Lähetin päivittää myös ajan milloin ID on lähetetty viimeksi.  
[[File:Vastaanotin-oled.jpg|thumb|Vastaanottimen oled näyttö]]
==== Mitä näyttö kertoo? ====
[[File:V2.screen.ids.jpg|thumb|Näytöllä näkyvät ID:t suurina, kun näytössä on 1-3 ID:tä näkyvillä.]]
Näyttö näyttää kaikki sen hetken luletut ID:t. Mikäli ID:n vastaanotosta on yli tunti häviää se näytöstä. Näyttö päivittyy 2,5 sekunnin välein. Jokaisella näytön päivityssyklillä näytön vasemmassa alareunassa välkkyy piste, josta voit tarkistaa, että järjestelmä on vielä käyttökuntoinen.
 
Jos yhtään ID:tä ei ole vastaanotettu näytössä näkyy himmeästi "Waiting for message..."


==== Mitä näyttö kertoo? ====
Näytön ID:t skaalautuvat määrän mukaan. Kun on vastaanotettu 1-3 ID:tä näkyy teksti suurena. 4-8 näkyy kahdella rivillä ja sen jälkeen numerot jakautuvat kolmelle riville.
Näyttö on jaettu kahteen osaan:


* Vasemmassa reunassa (suurena):
Korostetun ID:n viimeksi vastaanotettu lähetysaika näkyy minuutteina näytön oikeassa alareunassa. Maksimiajan ollessa 60min, jolloin lähetin kyseisellä ID:llä ei ole lähettänyt viestiä.
** '''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ä):
Jos kaikki tallennetut ID:t ovat yli 60min vanhoja näyttö palaa ruutuun "Waiting for message..." ja näyttö himmenee.
# '''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>)


=== Vastaanottimen kytkentäkaavio ja tarvittavat osat ===
=== Vastaanottimen kytkentäkaavio ja tarvittavat osat ===
Line 55: Line 48:
|9V Paristo
|9V Paristo
|1
|1
|1,2
|5
|[https://www.amazon.de/-/en/dp/B0DD3ZG4K9?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1 GutAlkaLi Batteries 9 V Block Battery, Pack of 6, Longlife Power for Smoke Detectors, Fire & Fire Detectors, Microphone 6lr61 6F22 PP3 MN1604]
|-
|Momenttikytkin (painonappi) (EI PAKOLLINEN)
|1
|
|
|[https://www.amazon.de/-/en/DAOKAI-Switch-Straight-Moment-Arduino/dp/B09WVMRD5V?pd_rd_w=O2Fz4&content-id=amzn1.sym.4bc731ae-4eb4-4e98-9a86-3ba652c5b44e&pf_rd_p=4bc731ae-4eb4-4e98-9a86-3ba652c5b44e&pf_rd_r=ZVS0D9FANNXXFY26W3FV&pd_rd_wg=pSMHX&pd_rd_r=b5040f30-5385-4685-8609-4b1d19d0597b&pd_rd_i=B09WVMRD5V&psc=1&ref_=pd_bap_d_grid_rp_0_1_ec_pr_pd_rhf_ee_s_rp_c_d_sccl_2_5_t DAOKAI Micro Switch NC+NO Hinge Straight Lever Micro Limit Switch 125V 1A PDT Limit Micro Switch Moment Switch for Arduino (Pack of 20)]
|-
|-
|Yhteensä
|Yhteensä
Line 109: Line 97:
|D12
|D12
|Vastaanottodata
|Vastaanottodata
|-
|Antenni
| -
|32cm long single core cable, spiral wound
|}
|}


Line 125: Line 117:
|Painettaessa yhdistää maahan
|Painettaessa yhdistää maahan
|}
|}
==== Lisää tarvittavat kirjastot ====
* RadioHead (RH_ASK)
* Adafruit SSD1306
* Adafruit GFX
* Wire


=== Vastaanottimen lähdekoodi ===
=== Vastaanottimen lähdekoodi ===
<syntaxhighlight lang="c++" line="1">#include <RH_ASK.h>
[[Vastaanotin v1 lähdekoodi]]
#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) {
      return;
    }
 
    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();
[[Vastaanottimen lähdekoodi V2]]
}</syntaxhighlight>


== Lähetinyksikkö ==
== Lähetinyksikkö ==
Line 424: Line 160:
|9V Paristo
|9V Paristo
|1
|1
|1,2
|5,0
|[https://www.amazon.de/-/en/dp/B0DD3ZG4K9?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1 GutAlkaLi Batteries 9 V Block Battery, Pack of 6, Longlife Power for Smoke Detectors, Fire & Fire Detectors, Microphone 6lr61 6F22 PP3 MN1604]
|
|-
|-
|Momenttikytkin (painonappi)
|Momenttikytkin (painonappi)
Line 447: Line 183:
|-
|-
|VCC
|VCC
|5V
|3.3V
|Virransyöttö
|Virransyöttö
|-
|-
Line 460: Line 196:
|Antenni
|Antenni
| -
| -
|Antennin optimaalinen pituus ~17cm
|Antennin optimaalinen pituus ~17cm (valmistajan suositus 25 cm ordinary multi-core or single-core cable.)
|}
 
===== Virtalähteen kytkentä pikarullausnappi =====
{| class="wikitable"
!Nappi
!Arduino Nano
!Selitys
|-
|1
|D2
|Luku (INPUT_PULLUP)
|-
|2
|GND
|Painettaessa yhdistää maahan
|}
|}


=== Lähettimen lähdekoodi ===
=== Lähettimen lähdekoodi ===
<syntaxhighlight lang="bash" line="1">
[[Lähettimen lähdekoodi v1]]
#include <RH_ASK.h>
#include <SPI.h>


RH_ASK driver(1000);
[[Lähettimen lähdekoodi V2]]
#define TX_PIN 12
 
#define DIP1 2
#define DIP2 3
#define DIP3 4
#define DIP4 5
 
byte crc8_dallas(const String &data) {
  byte crc = 0x00;
  for (int i = 0; i < data.length(); i++) {
    crc ^= data[i];
    for (int j = 0; j < 8; j++) {
      crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1);
    }
  }
  return crc;
}
 
void setup() {
  driver.init();
  pinMode(DIP1, INPUT_PULLUP);
  pinMode(DIP2, INPUT_PULLUP);
  pinMode(DIP3, INPUT_PULLUP);
  pinMode(DIP4, INPUT_PULLUP);
  pinMode(TX_PIN, OUTPUT);
  randomSeed(analogRead(0));
}
 
String readDipCode() {
  String code = "";
  code += (digitalRead(DIP1) == LOW) ? '1' : '0';
  code += (digitalRead(DIP2) == LOW) ? '1' : '0';
  code += (digitalRead(DIP3) == LOW) ? '1' : '0';
  code += (digitalRead(DIP4) == LOW) ? '1' : '0';
  return code;
}
 
void loop() {
  String code = readDipCode();
  String core = "S" + code + "E";
  byte crc = crc8_dallas(core);
  String message = core + String(crc, HEX);
 
  driver.send((uint8_t*)message.c_str(), message.length());
  driver.waitPacketSent();
 
  delay(random(800, 1800));  // satunnainen lähetysväli
}
</syntaxhighlight>


== ID Dip taulukko ==
== ID Dip taulukko ==
{| class="wikitable"
{| class="wikitable"
!ID
|+DIP taulukko
!DIP-koodi
!Kytkin
!
!0000
!1000
!0100
!1100
!0010
!1010
!0110
!1110
!0001
!1001
!0101
!1101
!0011
!1011
!0111
!1111
|-
|-
|ID
|0
|1
|1
|0000
|
|-
|2
|2
|0001
|
|-
|3
|3
|0010
|
|-
|4
|4
|0011
|
|-
|5
|5
|0100
|
|-
|6
|6
|0101
|Ei toimi
|-
|7
|7
|0110
|
|-
|8
|8
|0111
|
|-
|9
|9
|1000
|
|-
|10
|10
|1001
|
|-
|11
|11
|1010
|
|-
|12
|12
|1011
|Ei toimi
|-
|13
|13
|1100
|
|-
|14
|14
|1101
|
|-
|15
|15
|1110
|
|-
|-
|16
|1111
|
|
|OFF-OFF-OFF-OFF
|'''ON'''-OFF-OFF-OFF
|OFF-'''ON'''-OFF-OFF
|'''ON'''-'''ON'''-OFF-OFF
|OFF-OFF-'''ON'''-OFF
|'''ON'''-OFF-'''ON'''-OFF
|OFF-'''ON'''-'''ON'''-OFF
|'''ON'''-'''ON'''-'''ON'''-OFF
|OFF-OFF-OFF-'''ON'''
|'''ON'''-OFF-OFF-'''ON'''
|OFF-'''ON'''-OFF-'''ON'''
|'''ON'''-'''ON'''-OFF-'''ON'''
|OFF-OFF-'''ON'''-'''ON'''
|'''ON'''-OFF-'''ON'''-'''ON'''
|OFF-'''ON'''-'''ON'''-'''ON'''
|'''ON'''-'''ON'''-'''ON'''-'''ON'''
|}
|}

Latest revision as of 18:11, 6 October 2025

Järjestelmä on suunniteltu tilanteisiin, jossa halutaan ilmoitus jostain mekaanisesta liikkeestä tai kulusta. Lähettimestä voidaan säätää lähetystunnust 0-15 välille säädettävissä. Vastaanotin lukee radioviestin ja näyttää sen näytöltä ja laskee milloin lähetin on viimeksi vastaanottanut kyseisen ID numeron.

Vastaanotinyksikkö

Vastaanottimen käyttö

Vastaanotin vastaanottaa radiolähetystä, jossa mukana tarkistussuma, sekä lähettimen ID sekä näyttää sen näytöllä saapumisjärjestyksessä vanhimmasta uusimpaan. Lähetin päivittää myös ajan milloin ID on lähetetty viimeksi.

Mitä näyttö kertoo?

Näytöllä näkyvät ID:t suurina, kun näytössä on 1-3 ID:tä näkyvillä.

Näyttö näyttää kaikki sen hetken luletut ID:t. Mikäli ID:n vastaanotosta on yli tunti häviää se näytöstä. Näyttö päivittyy 2,5 sekunnin välein. Jokaisella näytön päivityssyklillä näytön vasemmassa alareunassa välkkyy piste, josta voit tarkistaa, että järjestelmä on vielä käyttökuntoinen.

Jos yhtään ID:tä ei ole vastaanotettu näytössä näkyy himmeästi "Waiting for message..."

Näytön ID:t skaalautuvat määrän mukaan. Kun on vastaanotettu 1-3 ID:tä näkyy teksti suurena. 4-8 näkyy kahdella rivillä ja sen jälkeen numerot jakautuvat kolmelle riville.

Korostetun ID:n viimeksi vastaanotettu lähetysaika näkyy minuutteina näytön oikeassa alareunassa. Maksimiajan ollessa 60min, jolloin lähetin kyseisellä ID:llä ei ole lähettänyt viestiä.

Jos kaikki tallennetut ID:t ovat yli 60min vanhoja näyttö palaa ruutuun "Waiting for message..." ja näyttö himmenee.

Vastaanottimen kytkentäkaavio ja tarvittavat osat

Tarvittavat osat

Komponentti Määrä Hinta Linkki
Arduino Nano v3 1 4,2 € Binghe Development Board with Chip CH340 Type-C Connector 5V 16M Microcontroller Compatible with Arduino IDE Pack of 5
433 MHz ASK-radiovastaanotin 1 2,8 € ALAMSCN Set of 5 433 MHz RF Receiver and Radio Transmission Module + RF 433 MHz Spring Antenna Kit Compatible with Arduino
OLED-näyttö (128×32, I2C, SSD1306) 1 4,5 € MakerHawk I2C OLED Display Module I2C SSD1306 Tiny Screen Module 0.91 Inch, in White 128X32 I2C OLED Driver
9V patteriliitin 1 0,8 € HeyNana 5 x Battery Clip 9 Volt Block Battery I Type Clip Snap with 15 cm Connection Cable for 9 V Blocks Plug Connection Cable
9V Paristo 1 5 €
Yhteensä

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
Antenni - 32cm long single core cable, spiral wound

Näytön pikarullausnappi

Nappi Arduino Nano Selitys
1 D2 Luku (INPUT_PULLUP)
2 GND Painettaessa yhdistää maahan

Vastaanottimen lähdekoodi

Vastaanotin v1 lähdekoodi

Vastaanottimen lähdekoodi V2

Lähetinyksikkö

Lähettimen käyttö

Lähetinyksikön pohjassa on DIP kytkinkaavio, josta valitaan lähetettävä ID. ID tulisi olla jokaisessa lähettimessä oma, jotta hälytykset on identifioitavissa.

Lähettimen pohjayksikkö on irtirepäistävä 360 astetta pyörivä pohja joka irrottuaan aktivoi lähetyksen välittömästi.

Lähetinyksikön voi myös laittaa omalla painollaan pohja edellä jollekkin tasaiselle alustalle ja mikäli se kaatuu alkaa lähetys.

Järjestelmä toimii 9v paristolla.

Lähettimen kytkentäkaavio ja tarvittavat osat

Tarvittavat osat

Komponentti Määrä Hinta Linkki
Arduino Nano v3 1 4,2 € Binghe Development Board with Chip CH340 Type-C Connector 5V 16M Microcontroller Compatible with Arduino IDE Pack of 5
433 MHz ASK-radiolähetin 1 2,8 € ALAMSCN Set of 5 433 MHz RF Receiver and Radio Transmission Module + RF 433 MHz Spring Antenna Kit Compatible with Arduino
9V patteriliitin 1 0,8 € HeyNana 5 x Battery Clip 9 Volt Block Battery I Type Clip Snap with 15 cm Connection Cable for 9 V Blocks Plug Connection Cable
9V Paristo 1 5,0 €
Momenttikytkin (painonappi) 1 0,25 € DAOKAI Micro Switch NC+NO Hinge Straight Lever Micro Limit Switch 125V 1A PDT Limit Micro Switch Moment Switch for Arduino (Pack of 20)
Yhteensä

Kytkentäkaavio

Lähettimen kytkentä
Vastaanottimen pinni Arduino Nano -pinni Selitys
VCC 3.3V Virransyöttö
GND GND Maa
DATA (OUT) D12 Lähetysdata
Antenni - Antennin optimaalinen pituus ~17cm (valmistajan suositus 25 cm ordinary multi-core or single-core cable.)

Lähettimen lähdekoodi

Lähettimen lähdekoodi v1

Lähettimen lähdekoodi V2

ID Dip taulukko

DIP taulukko
Kytkin 0000 1000 0100 1100 0010 1010 0110 1110 0001 1001 0101 1101 0011 1011 0111 1111
ID 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
OFF-OFF-OFF-OFF ON-OFF-OFF-OFF OFF-ON-OFF-OFF ON-ON-OFF-OFF OFF-OFF-ON-OFF ON-OFF-ON-OFF OFF-ON-ON-OFF ON-ON-ON-OFF OFF-OFF-OFF-ON ON-OFF-OFF-ON OFF-ON-OFF-ON ON-ON-OFF-ON OFF-OFF-ON-ON ON-OFF-ON-ON OFF-ON-ON-ON ON-ON-ON-ON