8 Channel IoT Triac Controller


With this low-cost IoT device it is easy to control a standard wall outlet devices with your internet browser. No additional software installation required. Working with single 5 volts DC power supply and controlling eight outputs with voltages between 1.7-600VAC. The power can be taken from the USB port or external power supply. There is a screw terminal for that.

Video https://youtu.be/RsZQNslSJ4I

Outputs uses a VO2223 power phototriacs. Designed to drive low power (<0.6A per channel) AC loads. Can be extended with external AC relay or power triac module. The outputs have screw terminals.

Ethernet connection is established using ENC28J60 chip and HanRun HR911105A ethernet connector because it includes transformers and filter elements needed for proper working. This simplifies board design.

The heart of the device is AtMega2560 chip. This makes it to be Arduino™ Mega compatible system. On board standard ICSP connector makes it easy to upload bootloader with using Arduino™ as ISP or other AVR programmers.

Device uses FT232RL chip to communicate via USB. With bootloader installed can be programmed via USB using Arduino™ IDE or similar programs. The DTR pin is connected through capacitor to the AtMega2560 reset pin. So reset works when it is needed by software.

AtMega2560 is programmed so that ethernet connection supports automatic DHCP, so you do not have to enter the IP address manually.

It includes easy to use web interface to modify and save custom triac control programs. The web interface is stored in to the AtMega2560’s program memory and it is included in the program code. It is programmed using HTML, CSS and javascript. It uses AJAX and HTTP Get request to communicate with the AtMega2560 which acts as a web server. The web interface is available with computers connected to the same LAN network.

Custom triac control programs is saved in to EEPROM memory. It automatically begins on startup and loops forevermore regardless of the ethernet connection. There is also individual manual override possibility with external pulse from AC side on each channel.

The device supports additional cards via the P2 connector. It is also used as an ICSP connector (SPI line) but it contains two extra pins that are serial port 2 RX and TX line. They can also be used as GPIO or as an SS signal in the SPI line. Additional cards are not yet designed but it can be anything. For example real time clock or digital input port expander or RS485 interface.

Usage examples

  • amusement park lighting systems
  • carneval lights
  • building face lights
  • household lights and appliances


The background of the menu bar is green if the connection is ok and red if there is no connection. It is possible to upload and download user program to/from local hard drive by using ‘Open list’ and ‘Save list’ buttons. It is in JSON format.

The control panel has line numbering and lines can be added and deleted with related buttons. Eight channels are individually programmed on each line by clicking it on or off. If it is on ‘on’-state then it’s background color is green otherwise it’s white. The delay in each line is given in milliseconds and it can be any between zero and near infinity (32-bit number from 0 to 4,294,967,295).

The program is automatically saved once per minute if changes are made. Program runs continuously and is editable in real time.

Development stage

One prototype has been made so far with double sided printed circuit board and with through hole components. Thoroughly tested and proven to work.

Further development ideas includes DIN rail mounting option, power input protective diode and controlling outputs over the USB connection. The top idea for an additional card is the RS485 interface.

Bill Of Materials

  • R1, 10k, 1*0.01
  • R2, R3, R6, R7, R10, R11, R14, R15, R23-R28, 470 ohm, 14*0.01
  • R4, R5, R8, R9, R12, R13, R16, R17, 180 ohm (optional), 8*0.01
  • R18, 2.7k, 1*0.01
  • R19-R22, 50 ohm, 4*0.01

Subtotal $0.28

  • C1, C2, C7, C10, 18pF, 4*0.01
  • C3, C4-C6, C9, C11, C12, C14, C19-C22, 100nF, 12*0.01
  • C16-C18, 10nF, 3*0.01
  • C8, C13, C15, 10µF tantalum 3*0.12

Subtotal $0.55

  • L1, L2, Ferrite bead with 4-14 turns (10-100µH) of thin enameled wire, 2*0.08
  • D17-D20, 5mm LED, 4*0.03
  • Y1, 16MHz, 0.1
  • Y2, 25MHz, 0.1

Subtotal $0.39

  • IC1, ATMEGA2560-A, 5.75
  • U2-U9, VO2223A, 8*0.79
  • U10, LM1117-3.3, 0.4
  • U11, ENC28J60-I/SO, 5.03
  • U12, FT232RL, 1.88
  • U13, MC74VHC1GT125, 2.08

Subtotal $21.46

  • P1, USB mini connector, 1.38
  • P2, pin header 2X4, 0.021
  • J1, HANRUN HR911105A, 1.76
  • J2-J9, screw terminal 1X3, 8*0.20
  • J10, screw terminal 1X2, 0.15
  • 8-pin DIP socket, 8*0.03
  • PCB, 8.88

Subtotal $14.031
Total $36.711


Reference schematics for how to make connection between the ENC28J60 and HR911105A.

Direct link from above

ENC28J60 datasheet

HR911105A datasheet

Schematics and board layout

Here is the schematics. Click to enlarge.


The project requires special soldering skills. Microcontroller AtMega2560 is 100 pin TQFP package. USB-to-serial interface FT232RL is 28-pin SSOP package. And ENC28J60 is 28 pin SOIC package. Board also contains MC74VHC1GT125 buffer and level shifter ic in SOT-353 package. Hot air soldering gun is recommended tool and use of solder paste.

All trough hole and SMD components are hand-soldered on the PCB. After the SMD components are soldered, the remaining components are easy to solder on the circuit board. VO2223A is sensitive to heat so using a ic socket is recommended.


My device works when simple interface is needed with little effort. If more current driving capacity is needed then user can use AC relays with DIN rail mounting. For example in the amusement park lighting.

This product works on all the different mains voltages in Europe and rest of the world too. My product has eight outputs and internet connection as IoT means.

This product does not use bulky relays so is smaller than its competitors.

USB control is also an option on my device. It just depends on how it is programmed. I also have an option for additional cards which can be anything. For example RS485 connection.


All parts are open source.

The program is written in C++, HTML, Javascript and CSS languages. The code uses more than 41KB of program memory so it is too long to fit into AtMega328 chip therefore AtMega2560 chip has been used.

The bootloader must first be programmed using on board standard ICSP connector and ISP. I personally like to use Arduino™ as ISP and Arduino™ IDE. Be sure to choose the Arduino™ Mega from the Tools→Board menu. After installing the bootloader, the program can be uploaded using a normal on board USB-to-serial interface.

FT232RL also needs to be programmed. Programming is done with FT-PROG program found on the FTDIchip.com website. It only works in Windows. Simplest method is to clone other ready made device. I cloned memory content from Sparkfun FTDI Basic board.

ENC28J60 is configurable via SPI but default settings are fine. Full-duplex operation is selected with LEDB. Connection of the LED is automatically read on reset. When the pin sinks current to illuminate LED, the PHCON1.PDPXMD bit is set and the PHY defaults to full-duplex operation. In full-duplex mode, switch and station can send and receive simultaneously, and therefore it is completely collision-free. Connection speed is 100Mbit/s.

I’m using UIPEthernet library for ENC28J60. It is licensed under GNU General Public License version 3. Other libraries available are Arduino uIP by Norbert Truchsess (GPL3), EtherCard (GPL2), EtherSia (3-clause BSD), ETHER_28J60 (LGPLv2.1).

Program code.

/*  Ethernet Triac Board
 *    - Program ver. 1.0
 *    - Compatible with board ver. 1.1
 *  Eight channel digital triac control board with Ethernet and USB 
 *  connection. Drives 8 output with AC load. Ethernet connection 
 *  supports automatic DHCP. No need to manual enter IP address. 
 *  Arduino Mega based system with AtMega2560 chip.
 *  Easy to use web interface to modify and save custom triac control 
 *  programs. Program is saved in to EEPROM. It automatically begins on 
 *  startup and loops forevermore.
 *  Control Panel layout by Lari Varjonen
 *  Copyright (C) 2017-2018  Juha-Pekka Varjonen
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  GNU General Public License for more details.
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.

#include <UIPEthernet.h>
#include <EEPROM.h>

unsigned long LedMillis = 0; // delays
unsigned long PrgMillis = 0;
unsigned long SaveMillis = 0;

int pos = 0;
boolean changed = false; // save program to EEPROM

// MAC address
const uint8_t mac[6] PROGMEM = {
  0x80, 0xAA, 0xBB, 0xCC, 0xDE, 0x02

// Stores the HTTP request
String HTTP_req;

// stores current program
int rowTotal = 0;
char contrArr[30][8];
int delayArr[30];

// Initialize the Ethernet server library
EthernetServer server(80); // port 80

void setup() {
  pinMode(4, OUTPUT); // LED output

  pinMode(33, OUTPUT); // data outputs
  pinMode(34, OUTPUT);
  pinMode(35, OUTPUT);
  pinMode(36, OUTPUT);
  pinMode(37, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(10, OUTPUT);
  // Open serial communication
  Serial.println(F("Ethernet Triac Board Copyright (C) 2017-2018  Juha-Pekka Varjonen"));
  Serial.println(F("This program comes with ABSOLUTELY NO WARRANTY; for details "
    "see license. This is free software, and you are welcome to redistribute it "
    "under certain conditions; see license for details."));

  // EEPROM initial content
//  String temp = "[[1,1,1,1,1,1,1,1,500],[0,0,0,0,0,0,0,0,500]]";
//  for (int i = 0; i < temp.length(); i++) {
//    EEPROM.write(i,temp.charAt(i));
//  }

  // Read saved program from EEPROM
  String temp;  
  boolean valid = false;
  for (int i = 0; i < EEPROM.length(); i++) {
    char n = EEPROM.read(i);
    char nn = EEPROM.read(i+1);
    if (n == '[' && nn == '[') valid = true;
    if (valid) temp += String(n);
    if (n == ']' && nn == ']') {
      valid = false;
      temp += String(nn);
      temp += String('\0');
  // Ethernet Chip Select
  pinMode(53, OUTPUT);
  digitalWrite(53, LOW);
  // start the Ethernet connection
  // Start listening for clients

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - LedMillis >= 100) {
    digitalWrite(4, !digitalRead(4));
    LedMillis = currentMillis;
  // Wait for a new client
  EthernetClient client = server.available();

  // Got client?
  if (client) {
    Serial.println(F("New client"));
    // Empty string
    HTTP_req = "";
    // An HTTP request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      // Client data available to read
      if (client.available()) {
        // Read one byte from client
        char c = client.read();
        // Save the HTTP request one char at a time
        HTTP_req += c;
        // Last line of client request is blank and ends with \n
        // Respond to client only after last line received        
        if (c == '\n' && currentLineIsBlank) {
          // GET receive program data
          if (HTTP_req.indexOf(F("?json=")) > -1) {
            int subStart = HTTP_req.indexOf(F("?json=")) + 6;
            int subEnd = HTTP_req.indexOf("HTTP") - 1;
            String input = HTTP_req.substring(subStart, subEnd);
            // Parse JSON string
            pos = 0;
            // Save to EEPROM
            changed = true;
            // start save counter from zero
            SaveMillis = currentMillis;
          // AJAX request current line
          if (HTTP_req.indexOf(F("line.txt")) > -1) {
            // Send current line number only
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/plain"));
            client.println(F("Connection: keep-alive"));
          // AJAX request program code
          else if (HTTP_req.indexOf(F("file.json")) > -1) {
            // Send program in JSON format
            String temp;
            temp = '[';
            for (int i = 0; i <= rowTotal; i++) {
              temp += '[';
              for (int ii = 0; ii < 8; ii++) {
                temp += contrArr[i][ii];
                temp += ',';
              temp += delayArr[i];
              temp += ']';
              if (i < rowTotal) temp += ',';
            temp += ']';
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: application/json"));
            client.println(F("Connection: keep-alive"));
          // HTTP request
          else {
            if (HTTP_req.indexOf(F("index.html")) > -1 || HTTP_req.indexOf(F("GET / ")) > -1) {
              // Send html page
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/html"));
              client.println(F("Connection: keep-alive"));
              client.println(F("<!DOCTYPE HTML><html><head><meta charset=\"UTF-8\"><title>8 Channel Triac Board - Control Panel</title><link rel=\"stylesheet\" type=\"text/css\" href=\"css/style.css\"></head><body><div class=\"top\"><div class=\"bar\"><div class=\"col\"><label for=\"file-upload\">Open list</label><input type=\"file\" name=\"open_button\" value=\"Open list\" id=\"file-upload\"/></div><div class=\"col\"><input type=\"button\" name=\"save_button\" value=\"Save list\"/></div><div class=\"col\"><input type=\"button\" name=\"remove_button\" value=\"Remove line\"/></div><div class=\"col\"><input type=\"button\" name=\"add_button\" value=\"Add line\"/></div></div></div><div class=\"background\"></div><div class=\"container\"><div class=\"row\" style=\"display:none\" id=\"1\"><div class=\"line_n\"><div class=\"arrow-right\"></div><div class=\"n\">1</div></div><input type=\"checkbox\" name=\"checkbox\"/><div class=\"checkbox_light\"></div><div class=\"led_background\"><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">1</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">2</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">3</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">4</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">5</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">6</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">7</div></div><div class=\"led\"><input type=\"checkbox\" name=\"led\" onchange=\"javascript: ledClick(this);\"/><div class=\"light\">8</div></div><input type=\"number\" min=\"0\" name=\"delay_t\" value=\"\" onchange=\"javascript: delayChange(this);\"/></div></div></div><script type=\"text/javascript\" src=\"js/java.js\" ></script></body></html>"));

            } else if (HTTP_req.indexOf(F("style.css")) > -1) {
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/css"));
              client.println(F("Connection: keep-alive"));
              client.println(F("body{margin:0;padding:0;background:#10162E;overflow-y:scroll;overflow-x:hidden}body,html{height:100%}@-moz-document url-prefix(){*::-moz-focus-inner,*::-moz-focus-outer,*:focus{border:none;outline:none;max-width:100%;border-radius:0;box-shadow:none;padding:0}}.top{position:fixed;z-index:10;width:100%;height:36px;background:#364168;display:block;margin:0;padding:0;top:0;left:0}.background,.container,.top .bar{width:269px}.top .bar{height:32px;margin:2px auto;display:table}.top .bar .col{display:table-cell;height:32px}input[type=file]{display:none !important}.top .bar input,label{width:100%;display:block;height:24px;border:none;outline:none;border-radius:0;box-shadow:none;margin:4px 0 0;padding:0;background:#fff;border-radius:6px;text-align:center;font-family:arial;font-size:14px;color:#7E7E7E;line-height:24px}.top .bar .col:nth-child(odd){padding:0 4px}.col:nth-child(1){padding:0 4px 0 0 !important}.top .bar input:hover,label:hover{background:#F8F8F8}.top .bar input:active{background:#F0F0F0;color:#000}.background{position:fixed;z-index:0;height:100%;margin:0 0 0 -134px;left:50%;background:#3E4769}.container{position:absolute;z-index:1;margin:36px 0 53px -134px;left:50%;padding:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.container .row,.container .row .led_background,.container .row .led_background .led,.container .row .led_background .led .light,.container .row .led_background .led input,.container .row input[name=checkbox],.container .row input[name=delay_t]{height:24px}.container .row .led_background .led,.container .row input[name=delay_t]{float:left}.container .row .led_background .led .light,.container .row .line_n .n,.container .row input[name=delay_t]{font-family:arial;font-size:10px;color:#848484;text-align:center}.container .row .led_background .led .light,.container .row input[name=delay_t]{line-height:24px}.container .row,.container .row .checkbox_light,.container .row .led_background,.container .row .line_n,.top{-o-transition:0.2s;-ms-transition:0.2s;-moz-transition:0.2s;-webkit-transition:0.2s;transition:0.2s}.container .row,.container .row .led_background{width:269px}.container .row{position:relative;background:#fff}.container .row:hover{background:#FBF7E1}.container .row:hover > .line_n{opacity:1}.container .row .line_n:hover{opacity:0.8}.container .row .line_n{height:16px;position:absolute;right:100%;margin-right:8px;opacity:0.8;margin-top:4px;background:#fff;border-radius:4px;padding:0 4px}.container .row .line_n .n{line-height:16px}.container .row .line_n .arrow-right{width:0;height:0;position:absolute;left:100%;margin-top:4px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #fff}.container .row input[name=delay_t]{border:none;outline:none;border-radius:0;box-shadow:none;margin:0;padding:0;width:68px;background:transparent;border-radius:0;line-height:30px;letter-spacing:1px}.container .row .led_background .led .light,.container .row .led_background .led input{position:absolute;top:0;left:0}.container .row .led_background .led,.container .row .led_background .led .light,.container .row .led_background .led input,.container .row input[name=checkbox]{width:24px}.container .row input[name=checkbox]{position:absolute;left:100%;margin:0}.container .row .led_background .led{position:relative;float:left;border-right:1px solid #F2F2F2;overflow:hidden}.container .row .led_background .led:last-child{border:none}.container .row .checkbox_light,.container .row .led_background .led .light,.container .row input[name=checkbox]{position:absolute}.container .row .checkbox_light{left:100%;width:12px;height:12px;margin:6px 0 0 6px;border-radius:6px}.container .row .led_background .led input,.container .row input[name=checkbox]{border:none;outline:none;box-shadow:none;margin:0;padding:0;vertical-align:middle;-webkit-appearance:none;cursor:pointer;z-index:1;opacity:0}.container .row .led_background .led input:checked + .light{color:#424242;background:#CEF6CE}.container .row input[name=checkbox]:hover + .checkbox_light{background:#3B0B0B}.container .row input[name=checkbox]:checked + .checkbox_light{background:red;background:-webkit-linear-gradient(left top, red, #610B0B);background:-o-linear-gradient(bottom right, red, #610B0B);background:-moz-linear-gradient(bottom right, red, #610B0B);background:linear-gradient(to bottom right, red, #610B0B)}.container .row input[name=checkbox]:checked ~ .led_background{background:#FFE1E1}"));
            } else if (HTTP_req.indexOf(F("java.js")) > -1) {
              String IPaddr;
              for (byte thisByte = 0; thisByte < 4; thisByte++) {
                // print the value of each byte of the IP address:
                IPaddr += String(Ethernet.localIP()[thisByte], DEC);
                if (thisByte < 3) IPaddr += ".";
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/javascript"));
              client.println(F("Connection: keep-alive"));
              client.print(F("var container,clone,data;var json_list=[];var button_click=false;function updateRemote(){var xhr=new XMLHttpRequest();xhr.open(\"GET\",\"http://"));
              client.print(F("/index.html?json=\"+JSON.stringify(json_list),true);xhr.timeout=2000;xhr.send(null)}function parseResult(){container.innerHTML=\"\";for(var i=0;i<json_list.length;i+=1){add_line(json_list[i][0],json_list[i][1],json_list[i][2],json_list[i][3],json_list[i][4],json_list[i][5],json_list[i][6],json_list[i][7],json_list[i][8],)};updateRemote()}function read_json_file(){var nocache=\"&nocache=\"+Math.random()*1000000;var xhr=new XMLHttpRequest();xhr.open(\"GET\",\"http://"));
              client.print(F("/file.json\"+nocache,true);xhr.timeout=2000;xhr.onreadystatechange=function(){if(this.readyState==4){if(this.status==200){if(this.responseText!=null){json_list=JSON.parse(this.responseText);parseResult()}}}};xhr.send(null)};function getCurrentLine(){var nocache=\"&nocache=\"+Math.random()*1000000;var xhr=new XMLHttpRequest();xhr.open(\"GET\",\"http://"));
              client.print(F("/line.txt\"+nocache,true);xhr.timeout=2000;var err=0;xhr.onreadystatechange=function(){if(this.readyState==4){if(this.status==200){if(this.responseText!=null){/*var pos=this.responseText;var obj=container.getElementsByClassName(\"row\");for(var i=0;i<obj.length;i+=1){obj[i].style.background=\"#fff\"}obj[pos].style.background=\"#FBF7E1\";*/setTimeout(\"getCurrentLine()\",10500);document.getElementsByClassName(\"top\")[0].title=\"online\";document.getElementsByClassName(\"top\")[0].style.background=\"#3bb151\"}else{err=1}}else{err=1}}; if(err==1){setTimeout(\"getCurrentLine()\",10500);document.getElementsByClassName(\"top\")[0].title=\"offline\";document.getElementsByClassName(\"top\")[0].style.background=\"#b13b3b\"}};xhr.send(null)}function readFile(event){json_list=JSON.parse(event.target.result);parseResult()}function open_button_change(){var file=document.getElementsByName(\"open_button\")[0].files[0];var reader=new FileReader();var tiedosto=reader.readAsText(file);reader.addEventListener('load',readFile)}function remove_button_click(){for(var i=0;i<container.getElementsByClassName(\"row\").length;i+=1){var obj=container.getElementsByClassName(\"row\")[i];if(obj.getElementsByTagName(\"input\")[0].checked){if(json_list.length==1){json_list=[]}else{var deletedItem=json_list.splice(i,1)}}}parseResult()}function save_button_click(){var blob=new Blob([JSON.stringify(json_list)],{type:'application/x-download'});var blobUrl=URL.createObjectURL(blob);var a=document.createElement('A');a.href=blobUrl;a.download=\"file.json\";document.body.appendChild(a);a.click();document.body.removeChild(a)}function ledClick(obj){var i=obj.parentNode.getElementsByClassName(\"light\")[0].innerHTML;var a=obj.parentNode.parentNode.parentNode.id;json_list[a-1][i-1]=obj.checked+0;updateRemote()}function delayChange(obj){var a=obj.parentNode.parentNode.id;json_list[a-1][8]=parseInt(obj.value);updateRemote()}function add_line(i1,i2,i3,i4,i5,i6,i7,i8,delay_t){var new_size=container.getElementsByClassName(\"row\").length+1;(clone.firstElementChild||clone.firstChild).getElementsByClassName(\"n\")[0].innerHTML=new_size;clone.id=new_size;container.appendChild(clone.cloneNode(true));var leds=document.getElementsByName(\"led\");leds[(new_size-1)*8].checked=i1;leds[((new_size-1)*8)+1].checked=i2;leds[((new_size-1)*8)+2].checked=i3;leds[((new_size-1)*8)+3].checked=i4;leds[((new_size-1)*8)+4].checked=i5;leds[((new_size-1)*8)+5].checked=i6;leds[((new_size-1)*8)+6].checked=i7;leds[((new_size-1)*8)+7].checked=i8;document.getElementsByName(\"delay_t\")[new_size-1].value=delay_t;button_click=true;window.location.hash=clone.id}function add_button_click(){add_line(0,0,0,0,0,0,0,0,500);json_list.push([0,0,0,0,0,0,0,0,500])}window.onload=function(){container=document.getElementsByClassName(\"container\")[0];clone=container.getElementsByClassName(\"row\")[0].cloneNode(true);clone.removeAttribute(\"style\");container.innerHTML=\"\";read_json_file();getCurrentLine();document.getElementsByName(\"open_button\")[0].onchange=open_button_change;document.getElementsByName(\"remove_button\")[0].onclick=remove_button_click;document.getElementsByName(\"save_button\")[0].onclick=save_button_click;document.getElementsByName(\"add_button\")[0].onclick=add_button_click};window.onhashchange=function(){if(!button_click){window.scrollBy(0,-36)}else{button_click=false}};"));
            } else {
              client.println(F("HTTP/1.1 404 Not Found"));
              client.println(F("Content-Type: text/html"));
              client.println(F("Connection: close"));
          // Display received HTTP request on serial port
        if (c == '\n') currentLineIsBlank = true;
        else if (c != '\r') currentLineIsBlank = false;
    // Give the web browser time to receive the data
    // Close the connection

  // Delayed save to EEPROM
  if (changed && currentMillis - SaveMillis >= 60000) {
    changed = false;
    SaveMillis = currentMillis;
    // make JSON string
    String temp;
    temp = '[';
    for (int i = 0; i <= rowTotal; i++) {
      temp += '[';
      for (int ii = 0; ii < 8; ii++) {
        temp += contrArr[i][ii];
        temp += ',';
      temp += delayArr[i];
      temp += ']';
      if (i < rowTotal) temp += ',';
    temp += ']';
    // save to EEPROM
    Serial.println("Saving to EEPROM");
    //EEPROM.put(0, temp); Does not work - too easy
    for (int i = 0; i < temp.length(); i++) {

  // Update outputs
  if (currentMillis - PrgMillis >= delayArr[pos]) {
    PrgMillis = currentMillis;
    if (pos > rowTotal) pos = 0;

void parseJSON(String input) {
  String tempStr = "";
  int row = 0;
  int col = 0;
  for (int i = 0; i < input.length(); i++) {
    if (input.charAt(i) == ',') col++;
    else if (col < 8) contrArr[row][col] = input.charAt(i);
    else if (col == 8) tempStr += input.charAt(i);
    if (col == 9) {
      delayArr[row] = tempStr.toInt();
      tempStr = "";
      col = 0;
  delayArr[row] = tempStr.toInt(); // last line
  rowTotal = row;

Other projects

Other projects which uses VO2223 phototriac

ARDUINO is a registered trademark.

Cathode Ray Tube (CRT) Display

The most complex project ever, but now it works. You can use Arduino to enter text and graphics on the screen.

The power supply makes a ±350 volt DC voltage. Deflection circuitry works with positive voltage and blanking circuit works with negative voltage. Before connecting CRT tube adjust the voltages according to the circuit diagram. Anode voltage is about +300V. Cathode voltage is about -290V. Focus voltage is about -200V and brightness voltage is about -320V. This circuit works for most small picture tubes found in eBay. I use 6LO1i. I will try others later.

The cathode voltage must be less (closer to zero) than brightness voltage otherwise blanking circuit does not work. First disconnect DAC from deflection circuit and adjust the dot in the center of the screen. Then adjust focus and anode voltage (astigmatism) so that dot is as sharp as possible. Be careful not to burn the phosphor coating of the CRT tube. Decrease brightness if necessary.

Digital to Analog Converter (DAC) circuit is commonly used R-2R resistor ladder network. The same circuit to horizontal and vertical deflection. Differential amplifier is used to amplify result.

Arduino example code:

static const byte ASCII[][5] =
 {0x00, 0x00, 0x00, 0x00, 0x00} // 20  
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c <
,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥
,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j 
,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ←
,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f →

unsigned long newMillis = 0;
unsigned long clockMillis = 0;
int second = 30;
int minute = 16;
int hour = 18;

long rndX=30, rndY=20;

void setup() {
  pinMode(2, OUTPUT); // 1 serial  D2   1 = vertical deflection
  pinMode(3, OUTPUT); // 1 OE      D3
  pinMode(4, OUTPUT); // 1 CLK     D4
  pinMode(5, OUTPUT); // 1 clear   D5
  pinMode(6, OUTPUT); // 2 serial  D6   2 = horizontal deflection
  pinMode(7, OUTPUT); // 2 OE      D7
  pinMode(8, OUTPUT); // 2 CLK     B0
  pinMode(9, OUTPUT); // 2 clear   B1

  pinMode(11, OUTPUT); // blanking B3

  digitalWrite(5, 1); // disable clear
  digitalWrite(9, 1);


void loop() {

  unsigned long currentMillis = millis();
  if (currentMillis - newMillis >= 5000) {  
    newMillis = currentMillis;
    rndX = random(28,35);
    rndY = random(18,23);

  if (currentMillis - clockMillis >= 1000) {  
    clockMillis = currentMillis;
    if (second >= 60) {
      second = 0;
      if (minute >= 60) {
        minute = 0;
        if (hour >= 24) {
          hour = 0;

  int upperHour = hour/10;
  int lowerHour = hour - (upperHour*10);
  int upperMin = minute/10;
  int lowerMin = minute - (upperMin*10);

  int upperSec = second/10;
  int lowerSec = second - (upperSec*10);
  byte text[] = {(0x30 + upperHour),
    (0x30 + lowerHour),
    (0x30 + upperMin),
    (0x30 + lowerMin),
    (0x30 + upperSec),
    (0x30 + lowerSec)};


void printText(byte arr[], int len, int line, int col) {
  for (int i = 0; i < len; i++) {
    printASCII(arr[i], 6*(col+i),9*line);

void printASCII(int e, int x, int y) {
  for (int i = 0; i < 5; i++) {
    for (int ii = 0; ii <= 7; ii++) {
      if (bitRead(ASCII[e-32][i],ii)) drawDot(x+i,y+ii);

void drawDot(int x, int y) {

  if (x > 255 || y > 255) return;

  for (int i = 7; i >= 0; i--) {
    // send data
    (bitRead(y,i)) ? (PORTD |= (1 << 2)) : (PORTD &=~(1 << 2));
    (bitRead(x,i)) ? (PORTD |= (1 << 6)) : (PORTD &=~(1 << 6));

    PORTD |= (1 << 4); // toggle clock
    PORTD &=~(1 << 4);
    PORTB |= (1 << 0);
    PORTB &=~(1 << 0);

  PORTD |= (1 << 4); // toggle clock
  PORTD &=~(1 << 4);
  PORTB |= (1 << 0);
  PORTB &=~(1 << 0);

  PORTB |= (1 << 3); // blanking 1
  PORTB &=~(1 << 3); // blanking 0
  PORTD &=~(1 << 5); // toggle clear
  PORTD |= (1 << 5);
  PORTB &=~(1 << 1);
  PORTB |= (1 << 1);


LED Matrix Clock

This clock is made of 60 LEDs and the AtMega328 chip with Arduino bootloader. Video of clock in operation. https://www.youtube.com/watch?v=L1F2BAmQfWg

Here is schematics. Board has two button to adjust hours and minutes and Real-time clock ic to keep time accurate.

Back side is not perfect because of cheap soldering iron.

Program code is below.

#include "Wire.h"

boolean LEDarr[60];
unsigned long newMillis = 0;
unsigned long blinkMillis = 0;
int second = 0;
int minute = 0;
int hour = 1;

unsigned long debounceTimer;
boolean A2state = 1;
boolean A3state = 1;

void setup() {
  pinMode(0, OUTPUT); // matrix
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);

  pinMode(8, OUTPUT); // matrix
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);

  pinMode(A2, INPUT_PULLUP); // buttons
  pinMode(A3, INPUT_PULLUP);

  Wire.begin(); // A4,A5

int ii = 0;

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - newMillis >= 500) {  
    newMillis = currentMillis;
  if(!digitalRead(A2)) {
    if(A2state) {
      A2state = 0;
      debounceTimer = millis();
    if(millis() - debounceTimer > 150) {
      A2state = 1;
      if(++hour == 13) hour = 1;
      newMillis = currentMillis;
  if(!digitalRead(A3)) {
    if(A3state) {
      A3state = 0;
      debounceTimer = millis();
    if(millis() - debounceTimer > 150) {
      A3state = 1;
      if(++minute == 60) minute = 0;
      newMillis = currentMillis;
  for(int i = 0; i <= 59; i++) LEDarr[i] = 0;
  if(second == 59) {
    if (ii < 59) for(int i = 0; i <= ii; i++) LEDarr[i] = 1;
    ii = ii + 2;
  } else {
    ii = 0;
    LEDarr[second] = 1;
  LEDarr[minute] = 1;
  LEDarr[(hour*5)-1] = 1;
  if(second == (hour*5)-1 && currentMillis - blinkMillis > 200) {
    blinkMillis = currentMillis;
    LEDarr[second] = !LEDarr[second];
  if(second == minute && currentMillis - blinkMillis > 200) {
    blinkMillis = currentMillis;
    LEDarr[second] = !LEDarr[second];
  if(minute == (hour*5)-1 && currentMillis - blinkMillis > 200) {
    blinkMillis = currentMillis;
    LEDarr[minute] = !LEDarr[minute];

byte decToBcd(byte val) {
  return ((val/10*16) + (val%10));

byte bcdToDec(byte val) {
  return ((val/16*10) + (val%16));

void setRTC() {

void getRTC() {
  Wire.requestFrom(0x51, 3);
  second = bcdToDec(Wire.read() & B01111111); // remove VL error bit
  minute = bcdToDec(Wire.read() & B01111111); // remove unwanted bits from MSB
  hour = bcdToDec(Wire.read() & B00111111);
  if(hour >= 12) hour = hour - 12;
  hour = hour + 1;

void LEDloop() {
  int led = 0;
  for(int a = 8; a <= 15; a++) {
    if (a <= 13) digitalWrite(a, 1);
    else if(a == 14) digitalWrite(A0, 1);
    else if(a == 15) digitalWrite(A1, 1);
    if(a != 8) {
      for(int i = 4; i <= 7; i++) {
        digitalWrite(i, !LEDarr[led++]);
        digitalWrite(i, 1);
    for(int i = 0; i <= 3; i++) {
      digitalWrite(i, !LEDarr[led++]);
      digitalWrite(i, 1);
    if (a <= 13) digitalWrite(a, 0);
    else if(a == 14) digitalWrite(A0, 0);
    else if(a == 15) digitalWrite(A1, 0);


APRS Weather Station

Automatic Packet Reporting System (APRS) is amateur radio -based system for sending and receiving short telemetry data locally. Local APRS stations can be seen at www.aprs.fi.

Picture of my station.

My hardware is based on Raspberry Pi computer and home made antenna with Baofeng UV-5R+Plus handheld transceiver. There is also a diy cable between Raspberry and radio. It is made from handsfree headset. There is a galvanic isolation between audio lines and opto-isolator on the PTT line. PTT is connected to Raspberrys GPIO pin 26.

Add this line to Dire Wolf config file:


Galvanic isolation and attenuation is made with following circuit:

DS18B20 temperature sensor is connected to Raspberrys GPIO pin 4 with parasitic power supply.

You must also activate parasitic power supply by adding following lines to associated files:

In /etc/modules

w1-gpio pullup=1

In /boot/config.txt


And then reboot.

On Raspberry Pi there is Xastir and Dire Wolf software installed. They are connected together with networked AGWPE. Temperature data is gathered with self made Python code. It talks with Xastir by emulating WX200 weather station. Code is here.

# WX200emu.py
# wx200, wx-200 weather station emulation and server
# Author:	Juha-Pekka Varjonen
#		 juvar@mbnet.fi
# License:	GNU General Public License, Version 3

version = '1.0'

import argparse, socket, sys, datetime, time

parser = argparse.ArgumentParser(prog='WX200Emu.py', description='WX200emu is a weather station emulator and server for client software. Listens for client connections and sends the 1-wire temperature data out to those clients.')
          help="host name or ip address, e.g. localhost")
          help="port number, e.g. 9753",
parser.add_argument("id", help="1-wire sensor 64-bit serial number")
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose",
          help="increase output verbosity",
group.add_argument("-q", "--quiet",
          help="do not display any message",
parser.add_argument('-V', '--version', 
          version='%(prog)s ' + version)
args = parser.parse_args()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = (args.host, args.port)
if args.verbose:
  print('starting up on %s port %s' % server_address)
  if args.quiet is False:
    print('cannot bind socket')
    print('reason: ', sys.exc_info()[1])

# Listen for incoming connections

while True:
    # Wait for a connection
    if args.verbose:
      print('waiting for a connection')
    elif args.quiet is False:
      print('server is up and running')
    connection, client_address = sock.accept()
    if args.quiet is False:
      print('exception occurred')
      print('reason: ', sys.exc_info()[0])
    if args.verbose:
      print('connection from', client_address)
    while True:
      # current date and time
      i = datetime.datetime.now()
      second = int(str(i.second),16).to_bytes(1, byteorder='big')
      minute = int(str(i.minute),16).to_bytes(1, byteorder='big')
      hour = int(str(i.hour),16).to_bytes(1, byteorder='big')
      day = int(str(i.day),16).to_bytes(1, byteorder='big')
      format_month = (0b00010000 + i.month).to_bytes(1, byteorder='big')

      # temperature to human readable format from 1-wire
      w1file = open('/sys/bus/w1/devices/' + args.id + '/w1_slave', 'r')
      text = w1file.read()
      out_temp = float("%.1f" % float(float(text.split("t=")[1])/1000))
      # outdoor temperature to wx200 format
      out_temp_sign = 0
      if out_temp < 0:
        out_temp_sign = 8
      out_temp_a = int((abs(out_temp) / 10) + out_temp_sign).to_bytes(1, byteorder='big')
      t = str(int(abs(out_temp) * 10))
      out_temp_bc = int(t[-2:len(t)],16).to_bytes(1, byteorder='big')

      # weather data wx200 format
      # http://wx200.planetfall.com/wx200.txt
      wx200file = [b''.join([b'\x8f', second, minute, hour, day, format_month, b'\0\0\xee\0\0\0\0\0\0\0\0\0\0\0\xee\0\0\0\0\0\0\0\0\0\0\0\0\0']),
          b''.join([b'\x9f\xee\0\0\0\0\0\0\0\0\0\0\0\0\0\0', out_temp_bc, out_temp_a, b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0']),

      if args.verbose:
        print('sending data to the client')

      # send data, normal range is 0,2 (first two lines)
      for i in range(1,2):
        Cksum = 0
        for ch in wx200file[i]:
            Cksum += ch
        connection.sendall(b''.join([wx200file[i],(Cksum & 0xff).to_bytes(1, byteorder='big')]))

  except FileNotFoundError:
    if args.quiet is False:
      print('cannot open 1-wire connection')
    if args.quiet is False:
      print('exception occurred')
      print('reason: ', sys.exc_info()[0])
    # Clean up the connection
    if args.verbose:
      print('closing connection')

Usage example. Reading command line help:

python3 wx200emu.py -h

Using with temperature sensor installed:

python3 wx200emu.py localhost 8008 10-000801b5a7a6

You can use any free port. Change sensor serial number according with your sensor.

Antenna is made according to these drawings: http://www.users.on.net/~endsodds/jpole.htm

I’m very happy with this antenna. I can receive station over a hundred kilometers.

I bought 3.5 inch touchscreen display and housing for Raspberry Pi, but display turned out to be too small and insensitive for anything useful use. So SSH and VNC connections came to good use. SSH works straight out of the box but VNC needs some fine-tuning.

First install RealVNC both to the host and client computers. Then setting up VNC server to Raspberry Pi is easy as pie with following command:

vncserver-virtual :1

Virtual display number can be any, but it must be the same as next command on client computer:

vncviewer xxx.xxx.xxx.xxx:1

Use Raspberry Pi’s IP address here.

Update – forget Xastir

It is possible to send and receive packets with Dire Wolf alone. First here is custom config file for Dire Wolf:

# OH1FWW config file for Dire Wolf


MODEM 1200
PBEACON DELAY=0:30 EVERY=30 VIA=WIDE2-2 LAT=61^25.95N LONG=23^48.85E commentcmd="python ~/read1wire.py"

Use your own call sign, not mine! All commands must be on single line. commentcmd is undocumented feature. It makes it possible to run scripts from Dire Wolf and print results to APRS packet comment line.

Every=30 means that it is repeated every 30 minutes.

Run with custom config file:

direwolf -c custom-config.conf

Dont forget to save this read1wire.py file too:

import time
def readtemp():
        w1file = open('/sys/bus/w1/devices/28-0000021ebb20/w1_slave', 'r')
        text = w1file.read()
        return float("%.1f" % float(float(text.split("t=")[1])/1000))
        print('cannot read 1-wire sensor. E2')
while True:
    out_temp = readtemp()
    if out_temp == 85.0:
        i = i+1
        print('out temp: {} deg.C'.format(out_temp))
    if i == 10:
        print('cannot read 1-wire sensor. E1')

Change sensor ID to yours.

After that it is again useful to use 3.5 inch built in display because there is not any graphical interface.

Deer Repeller

Convenient way to repel deers from eating trees and bushes in carden (in Finland). This device includes motion detector and twilight switch. When motion detected loud whistles and chirps keeps deers away from trees and bushes.

Here is circuit schematics.

Simple VU-meter

This simple VU-meter allows any kind of audio in and displays its volume on the led bar. Gain is adjustable with potentiometer. Circuit uses LM3915 chip.

Part list

All parts can be found on www.taydaelectronics.com.

DMX Controlled Relay Board

Now automation is made easy. With this nifty module one can control four different application by using an computer with a DMX software installed. Module uses four DMX512 address. Starting address is selectable withing DIP switches. Relay is switched on by sending value over 138 and switched off by sending value under 118. Module remember last states during DMX signal loss.

Easy to solder. Arduino compatible and programmable. Can use BSD licensed free library for receiving DMX data. Example code is coming later.

With this and previously published USB-DMX Interface its very easy to build home automation or stage lightning effects.

All relays capable to drive 5A, 230V loads. Uses only one supply voltage of 5V.

Part list

All parts can be bought from www.taydaelectronics.com.

  • R1, R2, R4, R6, R8 – 10k resistor
  • R3, R5, R7, R9 – 200 ohm resistor
  • C1, C2 – 100nF ceramic disc capacitor
  • C3, C4 – 12pF ceramic disc capacitor
  • D1, D3, D5, D7 – 5mm red led
  • D2, D4, D6, D8 – 1N4004 diode
  • Q1-Q4 – BC547 or 2N3904 general purpose transistor
  • Y1 – 16MHz crystal
  • U1 – atMega328P-PU microcontroller ic
  • U5 – MAX485 line driver ic
  • U2, U3, U4, U6 – HJF-3FF relay
  • SW1 – DIP switch 9 positions
  • J3 – 4 pin header
  • J1 – 2 pin screw terminal
  • J2, J4-J8 – 3 pin screw terminal