RF Power Meter V1.1

This device is part of my APRS radio transmitter backpack rig published later on.

PCB layout file, schematic diagram and AVR program available on GitHub: https://github.com/Juvar1/RFPowerMeter
It is designed with KiCad.

Improvements

Here is second version of my RF power meter device. It has improved RF shielding and noise immunity. A little bit more expensive to make but quality pays.

C14 is also added for improving noise immunity from supply line.

C14 added and other SMA connector removed. It’s replaced with T splitter externally.

Casing

Case is bought from Digi-Key. It’s part number is PIP-11766-C. DC jack outer diameter is 5.5mm and inner 2.5mm.

RF Power Meter

This device is part of my APRS radio transmitter backpack rig published later on.

PCB layout file, schematic diagram and AVR program available on GitHub: https://github.com/Juvar1/RFPowerMeter
It is designed with KiCad. Untested!

Introduction

This meter measures the transmitting power of the radio transmitter and the voltage of the battery used as a power source. Power is displayed in watts and dBm. The device has a three-digit LCD display. The functions are controlled by a push button. The unit will automatically turn off after five minutes if the button is not pressed. By pressing once device wakes from sleep mode and show measured power in wats. By pressing again device switches to dBm mode and show power in dBm. Pressing third time device show measured supply voltage.

Comes as a bag of parts kit and is easily assembled if customer can follow the silkscreen indicators and have beginning experience with a soldering iron. Customer will need to read the resistor bands or use a multimeter to determine the resistor sizes.

Works with single 7…14 volts DC power supply connected with DC barrel plug. Internally 5.0V voltage is used through regulator 7805. ADC reference voltage is taken from supply voltage with voltage divider. Its voltage is 2.5V. One analog input is connected with supply voltage also with voltage divider. its voltage is adjusted to a max. of 2.5V at 14V so that accurate voltage measurement is made possible.

The heart of the device is AtMega328P-PU at 16MHz clock frequency. This makes it to be Arduino™ compatible system.

For measuring power, an AD8307 chip is used. It allows the RF power to be converted to a voltage between 0 and 2.5V. The permissible power is between 0 and 1kW. The permissible frequency range is between 10MHz and 1GHz. It is possible to calibrate the measuring range by ± 3dB with on-board trimmer resistor. The output is connected to the analog input of the microcontroller.

Measurement is made with following equations:

dBm = 40(U-1)
W = 10(dBm/10)/1000

The display is a three-digit LCD display. LCD display requires AC voltage. It can be simple square wave signal from DC 5V. Frequency is not critical. It can be between 30-200Hz. Phase difference lights the segment. This device uses regular CMOS series IC’s to control LCD segments. 4056 is used to control digits. Every digit needs own IC. One 4054 is used to control two decimal points and to invert common backplane signal. Frequency is made with Arduino™ PWM output. It is about 30Hz. Below is an self-explanatory illustration.

Current consumption is very low because of CMOS integrated circuits used and AtMega328P power saving mode which is programmatically activated. LCD display consumes very little current compared to regular LED display. Also power measurement circuit ’chip enable’ signal is controlled with AVR.

Physical size of device is with casing 13x13cm. PCB is 10x10cm. Dimensions excludes connectors and push button.

Usage examples

  • Amateur radio station power meter
  • Power measurement

Circuit diagram

Development stage

One prototype has been ordered so far with double sided printed circuit board and with through hole components. UNTESTED at this point. Work is in progress. It is designed with KiCad.

Target MSRP

BOM (bill-of-material) cost with casing and SMA connectors is about 50 euros. It does not include any basic components with standard values (I use my own stock).

10 pieces small batch BOM cost is $64.90 + PCB $3.02. PCB will cost $1.33 in order quantity of 1000 pieces. PCB’s are bought from Itead Studio from China. The components are bought from Digi-Key from USA. The same components can also be purchased from Mouser. The price in larger quantities are much less. If order quantity is 1000 pieces then it costs $40.04 + PCB $1.33. Manufacturing costs should also be taken into account. (In this case packing.) So total cost for parts is roughly $43.00.

Typical distribution margin is 5-7% and retail margin is 15-25% with this type of products. So the MSRP would be between $51.93 and $57.51. Realistic actual selling price would be $79.00.

Advantages

Ease of use. The device has only one button with three functions. ’Plug-and-play’. Safe operating voltage. Safe to use. Hackable design.

Other similar devices

Surecom SW-102 Digital Antenna Power & SWR Meter VHF/UHF 125-525MHz.

Soldering

The project requires no special soldering skills. All components are their through hole versions.

Programming

All parts are open source. The program is solely written in C++. The program is very simple and with its 200 lines of code it uses only 4.6kB of program memory. AtMega328P-PU chip is used.

The bootloader must first be programmed using external programmer. I personally like to use Arduino™ as ISP and Arduino™ IDE. Be sure to choose the Arduino™ Nano from the Tools→Board menu. After installing the bootloader, the program can be uploaded using same setup.

Program does not use any external libraries. Only standard built-in power saving related libraries are used.

Approvals

Arduino is a registered trademark. It is still ok to build a commercial product based on Arduino. Only the name may not be the same. https://www.arduino.cc/en/Main/FAQ#toc10

Battery level indicator

This device monitors battery voltage and shows it on 5 LED’s. Further development will include a use of AVR power saving functions. At this moment circuit uses 31mA. LED position vs. voltage:

  1. <= 12.05
  2. 12.06 – 12.25
  3. 12.26 – 12.45
  4. 12.46 – 12.65
  5. >= 12.66

Full lead acid battery has 12.7V and when it’s empty voltage is under 12.0V. Below is circuit diagram. All works just as planned. Simple math.

Here is Arduino code before power saving functions:

/* battLow2.ino
 * 
 * 
 */

void setup() {
  // initialize LED outputs
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
  pinMode(11,OUTPUT);
  pinMode(12,OUTPUT);
  pinMode(13,OUTPUT);

  // LED's to off state
  digitalWrite(9,HIGH);
  digitalWrite(10,HIGH);
  digitalWrite(11,HIGH);
  digitalWrite(12,HIGH);
  digitalWrite(13,HIGH);
}

void loop() {

  int val = analogRead(A5); // 0 to 1023

  // voltage divider input---56---22---gnd
  // (1023/17.727) * voltage
  if(val <= 695) {
    digitalWrite(9,LOW);
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
    digitalWrite(12,HIGH);
    digitalWrite(13,HIGH);
  } else if(val > 695 && val < 706) {
    digitalWrite(9,HIGH);
    digitalWrite(10,LOW);
    digitalWrite(11,HIGH);
    digitalWrite(12,HIGH);
    digitalWrite(13,HIGH);
  } else if(val >= 706 && val < 718) {
    digitalWrite(9,HIGH);
    digitalWrite(10,HIGH);
    digitalWrite(11,LOW);
    digitalWrite(12,HIGH);
    digitalWrite(13,HIGH);
  } else if(val >= 718 && val < 730) {
    digitalWrite(9,HIGH);
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
    digitalWrite(12,LOW);
    digitalWrite(13,HIGH);
  } else if(val > 730) { // 12.65
    digitalWrite(9,HIGH);
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
    digitalWrite(12,HIGH);
    digitalWrite(13,LOW);
  }
  
  delay(200);
}

After adding power saving things to code current consumption is only 18mA. It contains LED driving current and power dissipation of the linear regulator. AVR restarts every two seconds to make one ADC measurement.

/* battLow2.ino
 * 
 * Displays 12V lead acid battery voltage level on LED bar
 * 
 * Author: jpvarjonen@gmail.com
 * Copyright (C) 2019 Juha-Pekka Varjonen
 */

#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/power.h>

void setup() {

  // power up every 2 seconds
  wdt_enable(WDTO_2S);
  // set sleep mode (not activated yet)
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  // disable things
  power_spi_disable();
  power_usart0_disable();
  power_twi_disable();
  power_timer0_disable();
  power_timer1_disable();
  power_timer2_disable();
  
  // initialize LED outputs
  digitalWrite(9,HIGH);
  digitalWrite(10,HIGH);
  digitalWrite(11,HIGH);
  digitalWrite(12,HIGH);
  digitalWrite(13,HIGH);
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
  pinMode(11,OUTPUT);
  pinMode(12,OUTPUT);
  pinMode(13,OUTPUT);

  int val = analogRead(A5); // 0 to 1023

  // disable ADC
  power_adc_disable();

  // voltage divider input---56---22---gnd
  // (1023/17.727) * voltage
  if(val <= 695) {
    digitalWrite(9,LOW);
  } else if(val > 695 && val < 706) {
    digitalWrite(10,LOW);
  } else if(val >= 706 && val < 718) {
    digitalWrite(11,LOW);
  } else if(val >= 718 && val < 730) {
    digitalWrite(12,LOW);
  } else if(val >= 730) { // 12.65
    digitalWrite(13,LOW);
  }

  // go to sleep (watchdog will restart)
  sleep_mode();
}

void loop() {
  // nothing here
}

Little improvement to circuit. Now it’s current consumption is only 11mA.

AVR/Arduino RFID Reader

This simple reader is based on previous post circuit and code. LCD display and custom AVR/Arduino circuit is added. Tag number is displayed on screen when detected. Detecting distance is about 2-3cm. Working voltage is 4.5V. This is maximum voltage of LCD display.

Video: https://www.youtube.com/watch?v=PWo2UU3oikA

Modified code with LCD driving:

#include <LiquidCrystal.h>

uint8_t cparity;
uint8_t numbers[5];

LiquidCrystal lcd(4,2,5,6,8,9);

void setup() {
  //Serial.begin(115200);
  pinMode(11, OUTPUT); // PWM output
  pinMode(13, OUTPUT); // Status LED

  lcd.begin(8,2);
  lcd.print("RFID");
  lcd.setCursor(0,1);
  lcd.print("reader");

  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0;
  TCCR2A = 0b01000001;
  TCCR2B = 0b00001001;
  OCR2A  = 32; // 32=125kHz, 27=143kHz == (16000000/(2*1*143000))/2
}

void wait(uint8_t t){
  uint8_t counter = 0,last = 0,next;
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (counter < t);
}

uint8_t readBit(uint8_t time1, uint8_t time2) {
  
  wait(time1);
  
  uint8_t counter = 0, last = 0, state1 = PIND & (1 << 7), state2;
  do {
    uint8_t next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
    state2 = PIND & (1 << 7);
  } while (counter <= time2 && state1 == state2);
  
  if (state1 == state2) return 2;
  if (state2 == 0) return 0; else return 1;
}

int8_t read4Bits(uint8_t time1, uint8_t time2) {
  
  uint8_t number = 0, parity = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(time1, time2);
    if (bit1 == 2) return -1;
    number = (number << 1) + bit1;
    parity += bit1;
  }
  
  uint8_t bit1 = readBit(time1, time2);
  if (bit1 == 2 || (parity & 1) != bit1) return -1;
  cparity ^= number;
  return number;
}

uint8_t readCard() {
  loop_until_bit_is_clear(PIND, 7);
  loop_until_bit_is_set(PIND, 7);
  
  uint8_t counter = 0, last = 0, next;  
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (bit_is_set(PIND, 7) && counter<0xFF);
  
  if (counter == 0xFF && bit_is_set(PIND, 7)) return 1;
  
  uint8_t halfbit = counter, offset = counter >> 1;  
  if (readBit(offset, halfbit) != 1) return 1;
  
  for (uint8_t i = 7; i > 0; i--)
    if (readBit(halfbit + offset, halfbit) != 1) return 1;
    
  cparity=0;
  for (uint8_t i = 0; i < 5; i++) {
    int8_t n1 = read4Bits(halfbit + offset, halfbit),
           n2 = read4Bits(halfbit + offset, halfbit);
    if (n1 < 0 || n2 < 0) return 1;
    numbers[i] = (n1 << 4) + n2;
  }
  
  uint8_t cp = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(halfbit + offset, halfbit);
    if (bit1 == 2) return 1;
    cp = (cp << 1) + bit1;
  }
  
  if (cparity != cp) return 1;
  if (readBit(halfbit + offset, halfbit) != 0) return 1;
  
  return 0;
}

void loop() {

  uint8_t result;
  do {
    result = readCard();
    PORTB ^= (1<<5); // LED drive
  } while (result != 0);

  lcd.clear();
  lcd.print("Raw data");
  lcd.setCursor(0,1);
  //Serial.print("Raw data: ");
  for (uint8_t i=0;i<5;i++) lcd.print(numbers[i],HEX); //Serial.print(numbers[i],HEX);
  //Serial.println();

  delay(1000);
  lcd.clear();
  lcd.print("Number");
  lcd.setCursor(0,1);

  //Serial.print("Card number: ");
  uint8_t numbersr[4];
  numbersr[0]=numbers[4];
  numbersr[1]=numbers[3];
  numbersr[2]=numbers[2];
  numbersr[3]=numbers[1];
  //Serial.print(*(uint32_t*)(&numbersr),DEC);
  //Serial.println();
  lcd.print(*(uint32_t*)(&numbersr),DEC);
  
  delay(1000);
}

Part list:

  • R1, R9, R10 – 1k resistor
  • R2 – 47k resistor
  • R3, R6, R7, R8 – 22k resistor
  • R4, R5 – 2k2 resistor
  • R11 – 680 resistor
  • R12 – 10k resistor trimmer
  • C1 – 4n7 capacitor
  • C2 – 2nF capacitor
  • C3, C4, C6, C9 – 100nF capacitor
  • C5 – 47pF capacitor
  • C7, C8 – 18pF capacitor
  • D1, D2, D3 – 1N4148 diode
  • D4 – Green LED diode
  • Y1 – 16MHz crystal
  • Q1 – 2N3904 transistor
  • Q2 – 2N3906 transistor
  • U1 – LM324 operational amplifier ic
  • IC1 – AtMega328P microcontroller ic

L1 is not normal component. Instead it is hand wound coil with inductance of 375µH. It can be physically any size or shape. Only important thing is inductance. Online calculator is good tool for this. Calculator asks permeability for air in this case. It is 1.

LCD connection:

  1. GND
  2. VCC
  3. V5 – (CONTR)
  4. RS – (D4)
  5. RW – (GND)
  6. E – (D2)
  7. DB0 – (NC = No Connection)
  8. DB1 – (NC)
  9. DB2 – (NC)
  10. DB3 – (NC)
  11. DB4 – (D5)
  12. DB5 – (D6)
  13. DB6 – (B0)
  14. DB7 – (B1)

Schematics:

Simple Arduino RFID reader

This simple circuit does not use any external modules to interfacing RFID reader to Arduino. The code below generates 125kHz frequency to other end of circuit. Then the other end sends the received data to Arduino input.

Inductor must be 375µH, but can be any size physically. Good reference to calculate correct value is here: https://www.allaboutcircuits.com/tools/coil-inductance-calculator/. I personally used 46 turns 58mm diameter coil of 0.5mm normal interconnecting wire. I used an empty can of Red Bull energy drink as former. Coil diameter is measured in the center of coil ‘edges’. Detecting distance is about 2-3cm.

Arduino program sends RFID tag data to serial port. This can then be used to for example open an electric lock. 125kHz output is pin 11 and data input is pin 7.

uint8_t cparity;
uint8_t numbers[5];

void setup() {
  Serial.begin(115200);

  pinMode(11, OUTPUT); // PWM output

  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0;
  TCCR2A = 0b01000001;
  TCCR2B = 0b00001001;
  OCR2A  = 32; // 32=125kHz, 27=143kHz == (16000000/(2*1*143000))/2
}

void wait(uint8_t t){
  uint8_t counter = 0,last = 0,next;
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (counter < t);
}

uint8_t readBit(uint8_t time1, uint8_t time2) {
  
  wait(time1);
  
  uint8_t counter = 0, last = 0, state1 = PIND & (1 << 7), state2;
  do {
    uint8_t next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
    state2 = PIND & (1 << 7);
  } while (counter <= time2 && state1 == state2);
  
  if (state1 == state2) return 2;
  if (state2 == 0) return 0; else return 1;
}

int8_t read4Bits(uint8_t time1, uint8_t time2) {
  
  uint8_t number = 0, parity = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(time1, time2);
    if (bit1 == 2) return -1;
    number = (number << 1) + bit1;
    parity += bit1;
  }
  
  uint8_t bit1 = readBit(time1, time2);
  if (bit1 == 2 || (parity & 1) != bit1) return -1;
  cparity ^= number;
  return number;
}

uint8_t readCard() {
  loop_until_bit_is_clear(PIND, 7);
  loop_until_bit_is_set(PIND, 7);
  
  uint8_t counter = 0, last = 0, next;  
  do {
    next = PINB & (1 << 3);
    if (next != last) counter++;
    last = next;
  } while (bit_is_set(PIND, 7) && counter<0xFF);
  
  if (counter == 0xFF && bit_is_set(PIND, 7)) return 1;
  
  uint8_t halfbit = counter, offset = counter >> 1;  
  if (readBit(offset, halfbit) != 1) return 1;
  
  for (uint8_t i = 7; i > 0; i--)
    if (readBit(halfbit + offset, halfbit) != 1) return 1;
    
  cparity=0;
  for (uint8_t i = 0; i < 5; i++) {
    int8_t n1 = read4Bits(halfbit + offset, halfbit),
           n2 = read4Bits(halfbit + offset, halfbit);
    if (n1 < 0 || n2 < 0) return 1;
    numbers[i] = (n1 << 4) + n2;
  }
  
  uint8_t cp = 0;
  for (uint8_t i = 4; i > 0; i--) {
    uint8_t bit1 = readBit(halfbit + offset, halfbit);
    if (bit1 == 2) return 1;
    cp = (cp << 1) + bit1;
  }
  
  if (cparity != cp) return 1;
  if (readBit(halfbit + offset, halfbit) != 0) return 1;
  
  return 0;
}

void loop() {

  uint8_t result;
  do {
    result = readCard();
  } while (result != 0);

  Serial.print("Raw data: ");
  for (uint8_t i=0;i<5;i++) Serial.print(numbers[i],HEX);
  Serial.println();

  Serial.print("Card number: ");
  uint8_t numbersr[4];
  numbersr[0]=numbers[4];
  numbersr[1]=numbers[3];
  numbersr[2]=numbers[2];
  numbersr[3]=numbers[1];
  Serial.print(*(uint32_t*)(&numbersr),DEC);
  Serial.println();

  delay(1000);
}

References:

Dual DC motor controller with Arduino (part 2)

Now with wireless control using 433MHz frequency. Code uses RadioHead-library.

Transmitter Arduino code:

#include <RH_ASK.h>
#include <SPI.h> // Not actualy used but needed to compile

RH_ASK driver(2000, 2, A5, 4);

byte old_mot_1;
byte old_mot_2;

void setup() {
  driver.init();
}

void loop() {
  int pot_1 = analogRead(1); // potentiometers values
  int pot_2 = analogRead(0);

  byte mot_1 = map(pot_1, 0, 1023, 0, 255);
  byte mot_2 = map(pot_2, 0, 1023, 0, 255);

  byte msg[3];
  msg[0] = mot_1;
  msg[1] = mot_2;
  driver.send(msg, 3);
  driver.waitPacketSent();
  delay(200);
}

Receiver Arduino code:

#include <RH_ASK.h>
#include <SPI.h> // Not actualy used but needed to compile

RH_ASK driver(2000, 13, 2, 4);

int pot_1 = 128;
int pot_2 = 128;

void setup() {
  driver.init();
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
}

void loop() {
  byte buf[3];
  if (driver.recv(buf, 3)) { // Non-blocking
    pot_1 = buf[0]; // potentiometers values
    pot_2 = buf[1];
  }

// MOTOR ONE
  // direction backward
  if(pot_1 < 128-10) {
    digitalWrite(7,0); // motor direction
    int mot_1 = map(pot_1, 0, 128-10, 255, 0);
    analogWrite(3,mot_1);
  }
  // stop motor in middle position
  else if((pot_1 > 128-9) && (pot_1 < 128+9)) {
    digitalWrite(7,0); // motor direction
    analogWrite(3,0); // motor speed
  }
  // direction forward
  else if(pot_1 > 128+10) {
    digitalWrite(7,1); // motor direction
    int mot_1 = map(pot_1, 128+10, 255, 255, 0);
    analogWrite(3,mot_1);
  }

// MOTOR TWO
  // direction backward
  if(pot_2 < 128-10) {
    digitalWrite(8,0); // motor direction
    int mot_2 = map(pot_2, 0, 128-10, 255, 0);
    analogWrite(11,mot_2);
  }
  // stop motor in middle position
  else if((pot_2 > 128-9) && (pot_2 < 128+9)) {
    digitalWrite(8,0); // motor direction
    analogWrite(11,0); // motor speed
  }
  // direction forward
  else if(pot_2 > 128+10) {
    digitalWrite(8,1); // motor direction
    int mot_2 = map(pot_2, 128+10, 255, 255, 0);
    analogWrite(11,mot_2);
  }
}

 

UART-controlled 7-segment display with Python GUI

This project uses this 7-segment display. It is based on Arduino. Display shows current gasoline price in Tampere, Finland. Gasoline price is middle price for 95E10 and it’s downloaded from www.polttoaine.net with Python code.

GUI (graphical user interface) is made with wxPython and wxFormBuilder. Update interval and serial port is user selectable.

Python code:

#! /usr/bin/python
# -*- coding: utf-8 -*-

import wx, wx.xrc
import threading
import serial
import urllib
from lxml.html import fromstring

class MyApp(wx.App):

    delay = 0
    runTime = 0

    def OnInit(self):
        self.res = wx.xrc.XmlResource("gui.xrc")
        self.frame = self.res.LoadFrame(None, "MyFrame1")
        self.text1 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl1")
        self.text2 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl2")
        self.slider1 = wx.xrc.XRCCTRL(self.frame, "m_slider1")
        self.frame.Bind(wx.EVT_BUTTON, self.on_evt_button, id=wx.xrc.XRCID("m_button1"))

        self.SetTopWindow(self.frame)
        self.frame.Show()
        
        self.delay = self.slider1.GetValue()*60
        self.updatePrice()
        self.thread = threading.Thread(target=self.delayTimer)
        self.thread.daemon = True
        self.thread.start()
        return True

    def on_evt_button(self, evt):
        self.delay = self.slider1.GetValue()*60

    def updatePrice(self):
        fopen = urllib.urlopen("https://www.polttoaine.net/Tampere")
        content = fopen.read()
        doc = fromstring(content)
        price = doc.find_class("Hinnat")[3].text_content()
        self.text1.SetValue(price)

        try:
            with serial.Serial(self.text2.GetValue(), 9600, timeout=1) as ser:
                time.sleep(5)
                ser.write(price+"\r")
        except IOError:
            print("Port cannot be opened")

    def delayTimer(self):
        self.runTime += 1
        if self.runTime >= self.delay:
            self.runTime = 0
            self.updatePrice()
        self.thread = threading.Timer(1,self.delayTimer)
        self.thread.daemon = True
        self.thread.start()       

app = MyApp(False)
app.MainLoop()

XRC layout file generated from wxFormBuilder:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resource xmlns="http://www.wxwindows.org/wxxrc" version="2.3.0.1">
  <object class="wxFrame" name="MyFrame1">
    <style>wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL</style>
    <size>380,165</size>
    <title>95E10 Gasoline Price Display</title>
    <centered>1</centered>
    <aui_managed>0</aui_managed>
    <object class="wxFlexGridSizer">
      <rows>3</rows>
      <cols>3</cols>
      <minsize>380,165</minsize>
      <vgap>0</vgap>
      <hgap>0</hgap>
      <growablecols></growablecols>
      <growablerows></growablerows>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText1">
          <label>Current output</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl1">
          <style>wxTE_READONLY</style>
          <value></value>
        </object>
      </object>
      <object class="spacer">
        <option>1</option>
        <flag>wxEXPAND</flag>
        <border>5</border>
        <size>0,0</size>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText2">
          <label>Update interval (minutes)</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_BOTTOM|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxSlider" name="m_slider1">
          <style>wxSL_HORIZONTAL|wxSL_LABELS</style>
          <size>100,-1</size>
          <value>30</value>
          <min>10</min>
          <max>120</max>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxButton" name="m_button1">
          <label>Save settings</label>
          <default>0</default>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText3">
          <label>Serial port</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl2">
          <value>/dev/ttyUSB0</value>
        </object>
      </object>
    </object>
  </object>
</resource>

 

Reading UART with Python GUI application

Finally, I can say that I can program in the Python language. I learned Python from this tutorial. It is a good combination of the languages that I previously knew (PHP, Javascript and SQL). Structure also reminds me of Delphi and Basic languages.

Here is a program that reads 1-wire temperature sensor DS18B20 and displays it’s value on GUI (graphical user interface). GUI is made with wxPython library and with wxFormBuilder software.

In between sensor and computer there is Arduino Nano which transforms data from 1-wire to UART.

Python code:

#! /usr/bin/python
# -*- coding: utf-8 -*-

import wx
import wx.xrc
import serial
from thread import start_new_thread

class MyApp(wx.App):

    def OnInit(self):
        self.res = wx.xrc.XmlResource("gui.xrc")
        self.frame = self.res.LoadFrame(None, "MyFrame1")
        self.text1 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl1")
        self.text2 = wx.xrc.XRCCTRL(self.frame, "m_textCtrl2")
        self.button1 = wx.xrc.XRCCTRL(self.frame, "m_button1")
        self.frame.Bind(wx.EVT_BUTTON, self.on_evt_button, id=wx.xrc.XRCID("m_button1"))
        
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

    def on_evt_button(self, evt):
        self.button1.SetLabel("Wait...")
        self.button1.Enable(False)
        start_new_thread(readSerial,(self,))

def readSerial(self):
    try:
        with serial.Serial(self.text2.GetValue(), 9600, timeout=1) as ser:
            line = ""
            while len(line) == 0:
                ser.write("\n")
                line = ser.readline()
            self.text1.SetValue(line.splitlines()[0] + u"°C")
    except IOError:
        print("Port cannot be opened")
    self.button1.Enable(True)
    self.button1.SetLabel("Read sensor")

app = MyApp(False)
app.MainLoop()

XRC layout file generated from wxFormBuilder:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resource xmlns="http://www.wxwindows.org/wxxrc" version="2.3.0.1">
  <object class="wxFrame" name="MyFrame1">
    <style>wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL</style>
    <size>-1,100</size>
    <title>1-Wire Temperature</title>
    <centered>1</centered>
    <aui_managed>0</aui_managed>
    <object class="wxGridSizer">
      <minsize>-1,100</minsize>
      <rows>2</rows>
      <cols>3</cols>
      <vgap>0</vgap>
      <hgap>0</hgap>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText1">
          <label>Outdoor temp</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl1">
          <style>wxTE_READONLY</style>
          <size>100,-1</size>
          <value>---</value>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxButton" name="m_button1">
          <label>Read sensor</label>
          <default>0</default>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL</flag>
        <border>5</border>
        <object class="wxStaticText" name="m_staticText2">
          <label>Port addr</label>
          <wrap>-1</wrap>
        </object>
      </object>
      <object class="sizeritem">
        <option>0</option>
        <flag>wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL</flag>
        <border>5</border>
        <object class="wxTextCtrl" name="m_textCtrl2">
          <size>100,-1</size>
          <value>/dev/ttyUSB0</value>
        </object>
      </object>
    </object>
  </object>
</resource>

Arduino code:

#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

void setup(void) {
  Serial.begin(9600); // start serial port
  sensors.begin(); // Start up the library
}

void loop(void) { 
  while (Serial.available() > 0) { // if there's any serial available, read it
    if (Serial.read() == '\n') { // look for the newline
      sensors.requestTemperatures(); // Send the command to get temperatures
      Serial.println(sensors.getTempCByIndex(0)); // get temperature from first sensor only
    }
  }
}

 

Dual DC motor controller with Arduino

Two DC motor is independently controlled with Arduino Nano (Micro compatible)  and with a dual H-bridge using PWM. Speed and direction is selectable with two potentiometers. Each potentiometer value is read with Arduino analog input. When potentiometer is on it’s center position then motor is off state.

Video: https://youtu.be/dS4pnsJSH2k

Arduino Nano and Micro contains three timers. Timer0 is used with Arduino internal commands so it leaves two timer (timer1 and timer2) to use with custom programs. Timer1 works with pins 9, 10 and timer2 works with pins 11 and 3.

Pulse width is selected with command analogWrite(pin, value). Analog input is read with command analogRead(pin). Code is below.

void setup() {
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
}

void loop() {
  
  int pot_1 = analogRead(1); // potentiometers values
  int pot_2 = analogRead(0);

// MOTOR ONE
  // direction backward
  if(pot_1 < 512-20) {
    digitalWrite(7,0); // motor direction
    int mot_1 = map(pot_1, 0, 512-20, 255, 0);
    analogWrite(10,mot_1);
  }
  // stop motor in middle position
  else if((pot_1 > 512-19) && (pot_1 < 512+19)) {
    digitalWrite(7,0); // motor direction
    analogWrite(10,0); // motor speed
  }
  // direction forward
  else if(pot_1 > 512+20) {
    digitalWrite(7,1); // motor direction
    int mot_1 = map(pot_1, 512+20, 1023, 255, 0);
    analogWrite(10,mot_1);
  }

// MOTOR TWO
  // direction backward
  if(pot_2 < 512-20) {
    digitalWrite(8,0); // motor direction
    int mot_2 = map(pot_2, 0, 512-20, 255, 0);
    analogWrite(11,mot_2);
  }
  // stop motor in middle position
  else if((pot_2 > 512-19) && (pot_2 < 512+19)) {
    digitalWrite(8,0); // motor direction
    analogWrite(11,0); // motor speed
  }
  // direction forward
  else if(pot_2 > 512+20) {
    digitalWrite(8,1); // motor direction
    int mot_2 = map(pot_2, 512+20, 1023, 255, 0);
    analogWrite(11,mot_2);
  }
}

 

8 Channel IoT Triac Controller

Introduction

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

Software

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

Connections

Reference schematics for how to make connection between the ENC28J60 and HR911105A.
https://www.banggood.com/ENC28J60-Ethernet-LAN-Network-Module-Schematic-For-Arduino-51-AVR-LPC-p-87596.html

Direct link from above
https://img.banggood.com/thumb/water/upload/2012/lidanpo/SKU076708.jpg

ENC28J60 datasheet
http://ww1.microchip.com/downloads/en/DeviceDoc/39662b.pdf

HR911105A datasheet
http://www.kosmodrom.com.ua/pdf/HR911105A.pdf

Schematics and board layout

Here is the schematics. Click to enlarge.

Soldering

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.

Advantages

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.

Programming

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
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  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.begin(9600);
  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');
      break;
    }
  }
  parseJSON(temp);
  
  // Ethernet Chip Select
  pinMode(53, OUTPUT);
  digitalWrite(53, LOW);
  
  // start the Ethernet connection
  Ethernet.begin(mac);//,IPAddress(192,168,1,36));
  // Start listening for clients
  server.begin();
}

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
            parseJSON(input);
            
            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"));
            client.println();
            client.print(pos+1);
          }
          // 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"));
            client.println();
            client.print(temp);
          }
          // 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();
              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();
              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.println();
              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(IPaddr);
              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(IPaddr);
              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(IPaddr);
              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"));
              client.println();
            }
          }
          // Display received HTTP request on serial port
          Serial.print(HTTP_req);
          break;
        }
        if (c == '\n') currentLineIsBlank = true;
        else if (c != '\r') currentLineIsBlank = false;
      }
    }
    // Give the web browser time to receive the data
    delay(10);
    // Close the connection
    client.stop();
  }

  // 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++) {
      EEPROM.update(i,temp.charAt(i));
    }
  }

  // Update outputs
  if (currentMillis - PrgMillis >= delayArr[pos]) {
    PrgMillis = currentMillis;
    digitalWrite(33,String(contrArr[pos][0]).toInt());
    digitalWrite(34,String(contrArr[pos][1]).toInt());
    digitalWrite(35,String(contrArr[pos][2]).toInt());
    digitalWrite(36,String(contrArr[pos][3]).toInt());
    digitalWrite(37,String(contrArr[pos][4]).toInt());
    digitalWrite(12,String(contrArr[pos][5]).toInt());
    digitalWrite(11,String(contrArr[pos][6]).toInt());
    digitalWrite(10,String(contrArr[pos][7]).toInt());
    pos++;
    if (pos > rowTotal) pos = 0;
  }
}

void parseJSON(String input) {
  String tempStr = "";
  int row = 0;
  int col = 0;
  input.replace("[","");
  input.replace("]","");
  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 = "";
      row++;
      col = 0;
    }
  }
  delayArr[row] = tempStr.toInt(); // last line
  rowTotal = row;
}

Other projects

Other projects which uses VO2223 phototriac

ARDUINO is a registered trademark.