Before going into detail. Let’s review some previous articles:
- Article 1: Infrared (IR) communication using PHPoC
- Article 2: Capture and analyze infrared (IR) signal from remote controller
Article 1 describes the basic knowledge of IR communication and how to transmit/receive data through IR using PHPoC.
Article 2 shows how to get a command set of the remote controllers. Through this article, I already known the command set of a remote controller.
Demo
System Architecture
Data Flow
Things Used In This Project
- PHPoC Blue
- One Grove IR emitter
- Resistor 1kOhm
- USB wireless LAN dongle
- Grove expansion board for PHPoC (Optional)
Wiring Diagram
Or
Source Code
Source Code <task0.php>
PHP Code:
<?php
if(_SERVER("REQUEST_METHOD"))
exit; // avoid php execution via http request
include_once "/lib/sd_340.php";
include_once "/lib/sn_tcp_ws.php";
include_once "/lib/vd_nec_infrared.php";
define("AIRCON_ADDRESS", 0x01ff);
// To get the command set, please visit https://forum.phpoc.com/blogs/khanh-s-blog/471-capture-and-analyze-infrared-ir-signal-from-remote-controller
define("CMD_POWER", 57);
define("CMD_AUTO", 105);
define("CMD_WIND_STRONG", 153);
define("CMD_WIND_NORMAL", 217);
define("CMD_WIND_WEAK", 89);
define("CMD_TEMP_INC", 41);
define("CMD_TEMP_DEC", 169);
define("CMD_FUNCTION", 185);
define("CMD_TIMER", 121);
define("CMD_SLEEP", 249);
$rbuf = "";
ws_setup(0, "phpoc_serial_camera", "csv.phpoc");
while(1)
{
if(ws_state(0) == TCP_CONNECTED)
{
$rlen = ws_read_line(0, $rbuf);
if($rlen)
{
$cmd = (int)$rbuf;
$data = (AIRCON_ADDRESS << 16) | ($cmd <<8) | ((~$cmd)&0xff);
nec_infrared_send($data, 32);
}
}
}
?>
Source Code <index.php>
PHP Code:
<?php
$ws_host = _SERVER("HTTP_HOST");
// To get the command set, please visit https://forum.phpoc.com/blogs/khanh-s-blog/471-capture-and-analyze-infrared-ir-signal-from-remote-controller
define("CMD_POWER", 57);
define("CMD_AUTO", 105);
define("CMD_WIND_STRONG", 153);
define("CMD_WIND_NORMAL", 217);
define("CMD_WIND_WEAK", 89);
define("CMD_TEMP_INC", 41);
define("CMD_TEMP_DEC", 169);
define("CMD_FUNCTION", 185);
define("CMD_TIMER", 121);
define("CMD_SLEEP", 249);
?>
<!DOCTYPE html>
<html>
<head>
<title>PHPoC Infrared Control</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<style type="text/css">
body { text-align: center; }
#img {
margin-right: auto;
margin-left: auto;
width: 320px;
height: 240px;
background-size: contain;
position: relative;
margin-bottom: 10px;
border: 1px solid #000;
}
#container {
margin-right: auto;
margin-left: auto;
width: 480px;
height: 480px;
position: relative;
margin-bottom: 10px;
border: 1px solid #000;
}
.button {
position: absolute;
width:100px; height:50px;
background-color: gray;
}
</style>
<script type="text/javascript">
var CMD_POWER = 57;
var CMD_AUTO = 105;
var CMD_WIND_STRONG = 153;
var CMD_WIND_NORMAL = 217;
var CMD_WIND_WEAK = 89;
var CMD_TEMP_INC = 41;
var CMD_TEMP_DEC = 169;
var CMD_FUNCTION = 185;
var CMD_TIMER = 121;
var CMD_SLEEP = 249;
var btn_array = [
CMD_AUTO,
CMD_WIND_STRONG,
CMD_WIND_NORMAL,
CMD_WIND_WEAK,
CMD_TEMP_INC,
CMD_TEMP_DEC,
CMD_FUNCTION,
CMD_TIMER,
CMD_SLEEP,
CMD_POWER
]
var cmd = 0;
var ws = null;
function init()
{
var container = document.querySelector("#container");
container.addEventListener("touchstart", mouse_down);
container.addEventListener("touchend", mouse_up);
container.addEventListener("touchcancel", mouse_up);
container.addEventListener("mousedown", mouse_down);
container.addEventListener("mouseup", mouse_up);
container.addEventListener("mouseout", mouse_up);
var btn_width = 80;
var btn_height = 40;
var gap = 20;
document.getElementById("container").style.width = (3*btn_width + 4*gap) + "px";
document.getElementById("container").style.height = (5*btn_height + 6*gap) + "px";
var y = 20;
var x = 20;
for(var i = 0; i < 4; i++)
{
document.getElementById(btn_array[i]).style.width = btn_width + "px";
document.getElementById(btn_array[i]).style.height = btn_height + "px";
document.getElementById(btn_array[i]).style.left = x + "px";
document.getElementById(btn_array[i]).style.top = y + "px";
y += btn_height + gap;
}
x += btn_width + gap;
y = 20;
for(var i = 4; i < 6; i++)
{
document.getElementById(btn_array[i]).style.width = btn_width + "px";
document.getElementById(btn_array[i]).style.height = btn_height + "px";
document.getElementById(btn_array[i]).style.left = x + "px";
document.getElementById(btn_array[i]).style.top = y + "px";
y += btn_height + gap;
if(i == 4)
y += btn_height + gap;
}
x += btn_width + gap;
y = 20;
for(var i = 6; i < 10; i++)
{
document.getElementById(btn_array[i]).style.width = btn_width + "px";
document.getElementById(btn_array[i]).style.height = btn_height + "px";
document.getElementById(btn_array[i]).style.left = x + "px";
document.getElementById(btn_array[i]).style.top = y + "px";
y += btn_height + gap;
}
document.getElementById(btn_array[9]).style.top = y + "px";
document.getElementById(btn_array[9]).style.borderRadius = btn_height + "px";
}
function ws_onmessage(e_msg)
{
}
function ws_onopen()
{
document.getElementById("ws_state").innerHTML = "OPEN";
document.getElementById("wc_conn").innerHTML = "Disconnect";
}
function ws_onclose()
{
document.getElementById("ws_state").innerHTML = "CLOSED";
document.getElementById("wc_conn").innerHTML = "Connect";
console.log("socket was closed");
ws.onopen = null;
ws.onclose = null;
ws.onmessage = null;
ws = null;
}
function wc_onclick()
{
if(ws == null)
{
ws = new WebSocket("ws://<?echo $ws_host?>/phpoc_infrared", "csv.phpoc");
document.getElementById("ws_state").innerHTML = "CONNECTING";
ws.onopen = ws_onopen;
ws.onclose = ws_onclose;
ws.onmessage = ws_onmessage;
}
else
ws.close();
}
function mouse_down(event)
{
if (event.target !== event.currentTarget)
{
var id = event.target.id;
document.getElementById(id).style.backgroundColor = "rgb(0, 255, 255)";
//console.log(id);
send_command(id);
}
event.stopPropagation();
event.preventDefault();
}
function mouse_up(event)
{
if (event.target !== event.currentTarget)
{
var id = event.target.id;
document.getElementById(id).style.backgroundColor = "gray";
}
event.stopPropagation();
event.preventDefault();
}
function send_command(cmd)
{
if(ws != null)
if(ws.readyState == 1)
ws.send(cmd + "\r\n");
}
window.onload = init;
</script>
</head>
<body>
<div id="container">
<button type="button" id="<?echo CMD_POWER?>" class="button">전원</button type="button">
<button type="button" id="<?echo CMD_AUTO?>" class="button">자동</button type="button">
<button type="button" id="<?echo CMD_WIND_STRONG?>" class="button">강</button type="button">
<button type="button" id="<?echo CMD_WIND_NORMAL?>" class="button">중</button type="button">
<button type="button" id="<?echo CMD_WIND_WEAK?>" class="button">하</button type="button">
<button type="button" id="<?echo CMD_TEMP_INC?>" class="button">↑</button type="button">
<button type="button" id="<?echo CMD_TEMP_DEC?>" class="button">↓</button type="button">
<button type="button" id="<?echo CMD_FUNCTION?>" class="button">기능</button type="button">
<button type="button" id="<?echo CMD_TIMER?>" class="button">타이머</button type="button">
<button type="button" id="<?echo CMD_SLEEP?>" class="button">수면</button type="button">
</div>
<p>
WebSocket : <span id="ws_state">null</span><br>
</p>
<button id="wc_conn" type="button" onclick="wc_onclick();">Connect</button>
</body>
</html>
Library <lib/vd_nec_infrared.php>
PHP Code:
<?php
if(_SERVER("REQUEST_METHOD"))
exit; // avoid php execution via http request
include_once "/lib/sd_340.php";
define("BASIC_CLOCK", 42000000); // basic clock of PHPoC 42MHz
$recv_ht_id = 0; // timer id which connect to an infrared receiver to capture data
$emit_control_ht_id = 1; // timer id which control an infrared emitter
$emit_carrier_ht_id = 2; // timer id which create the carier for infrared modulation
function infrared_setup($rec_ht_id, $control_ht_id, $carrier_ht_id)
{
global $recv_ht_id;
global $emit_control_ht_id;
global $emit_carrier_ht_id;
$recv_ht_id = $rec_ht_id;
$emit_control_ht_id = $control_ht_id;
$emit_carrier_ht_id = $carrier_ht_id;
}
/*
This function make timer to start capturing signal from an infrared receiver.
It is used to analyze the pulse chain, so it it set to capture toggle mode.
Paramerer:
-$unit (microsecond);
-$repc: the number of pulse need to be captured
*/
function infrared_capture_start($unit, $repc)
{
global $recv_ht_id;
$repc++; // plus one dummy pulse
$unit = $unit * BASIC_CLOCK / 1000000;
// setup capture timer
ht_ioctl($recv_ht_id, "reset");
ht_ioctl($recv_ht_id, "set div $unit");
ht_ioctl($recv_ht_id, "set mode capture toggle");
ht_ioctl($recv_ht_id, "set trigger from pin fall");
ht_ioctl($recv_ht_id, "set repc $repc");
ht_ioctl($recv_ht_id, "start"); // start trigger pulse
}
/*
This function make timer to start capturing signal from an infrared receiver.
It is used to get data, so it it set to capture fall mode.
Paramerer:
-$unit (microsecond);
-$repc: the number of pulse need to be captured
*/
function infrared_recv_start($unit, $repc)
{
global $recv_ht_id;
$repc++; // plus one dummy pulse
$unit = $unit * BASIC_CLOCK / 1000000;
// setup capture timer
ht_ioctl($recv_ht_id, "reset");
ht_ioctl($recv_ht_id, "set div $unit");
ht_ioctl($recv_ht_id, "set mode capture fall");
ht_ioctl($recv_ht_id, "set trigger from pin fall");
ht_ioctl($recv_ht_id, "set repc $repc");
ht_ioctl($recv_ht_id, "start"); // start trigger pulse
}
function infrared_recv_stop()
{
global $recv_ht_id;
ht_ioctl($recv_ht_id, "stop");
}
function infrared_carrier_start($freq)
{
global $emit_carrier_ht_id;
$div = BASIC_CLOCK / ($freq * 2);
ht_ioctl($emit_carrier_ht_id, "reset");
ht_ioctl($emit_carrier_ht_id, "set div $div"); // div 13.14us
ht_ioctl($emit_carrier_ht_id, "set mode output pwm");
ht_ioctl($emit_carrier_ht_id, "set output od");
ht_ioctl($emit_carrier_ht_id, "set count 1 1");
ht_ioctl($emit_carrier_ht_id, "start");
}
function infrared_carrier_stop()
{
global $emit_carrier_ht_id;
ht_ioctl($emit_carrier_ht_id, "stop");
ht_ioctl($emit_carrier_ht_id, "set output high");
}
/*
see nec_infrared_send($data, $bit_length) function to know how to use this function
*/
function infrared_emit($count_buf, $cnt_buf_len, $unit)
{
global $emit_control_ht_id;
$unit = $unit * BASIC_CLOCK / 1000000;
infrared_carrier_start(38000); //enable 38KHz PWM signal - carrier frequency
//ht_ioctl($emit_control_ht_id, "reset");
ht_ioctl($emit_control_ht_id, "set div $unit");
ht_ioctl($emit_control_ht_id, "set mode output toggle"); // set mode: toggle
ht_ioctl($emit_control_ht_id, "set output od");
$pid_ht = pid_open("/mmap/ht$emit_control_ht_id");
pid_write($pid_ht, $count_buf);
pid_ioctl($pid_ht, "set repc $cnt_buf_len");
pid_close($pid_ht);
ht_ioctl($emit_control_ht_id, "start"); // start HT
while(ht_ioctl($emit_control_ht_id, "get state"));
ht_ioctl($emit_control_ht_id, "stop");
infrared_carrier_stop(); // stop to save energy.
}
/*
Paramerer:
-$lower_bound: minimum value of the first captured pulse in microsecond
-$upper_bound: minimum value of the first captured pulse in microsecond
Return:
-true: value of the first captured pulse varies from $lower_bound to $upper_bound
-false: otherwise.
*/
function infrared_available($lower_bound, $upper_bound)
{
global $recv_ht_id;
$unit = ht_ioctl($recv_ht_id, "get div"); // in number clock stick
$unit = $unit * 1000000 / BASIC_CLOCK; // in microsecond
$lower_bound /= $unit;
$upper_bound /= $unit;
$count = ht_ioctl($recv_ht_id, "get count 1");
if( ($count >= $lower_bound) && ($count <= $upper_bound))
return true;
return false;
}
/*
Paramerer: $count_id start from 0.
Return: width of captured pulse in microsecond
*/
function infrared_get_count($count_id)
{
global $recv_ht_id;
$count_id++; // due to the dummy value (first value)
$unit = ht_ioctl($recv_ht_id, "get div");
$count = ht_ioctl($recv_ht_id, "get count $count_id");
$reval = $unit * $count * 1000000 / BASIC_CLOCK;
return $reval;
}
function nec_infrared_send($data, $bit_length)
{
$unit = 562.5;// 562.5µs
$count_buf = int2bin(1, 2); // first value is dummy.
$count_buf .= int2bin(16, 2); // 9ms leading pulse burst (16 * 562.5us)
$count_buf .= int2bin(8, 2); // 4.5ms space (8 * 562.5us)
$cnt_buf_len = 3;
$mask = 1 << ($bit_length-1);
while($mask)
{
$count_buf .= int2bin(1, 2); // 562.5µs pulse burst
$cnt_buf_len++;
if($data & $mask)
{
$count_buf .= int2bin(3, 2); // logical 1: 1.6875ms space
}
else
{
$count_buf .= int2bin(1, 2); // Logical 0 562.5µs space
}
$cnt_buf_len++;
$mask = $mask >> 1;
}
$count_buf .= int2bin(1, 2); // 562.5µs stop code
$cnt_buf_len++;
infrared_emit($count_buf, $cnt_buf_len, $unit);
}
function nec_infrared_recv($bit_length)
{
$data = 0;
$mask = 1 << ($bit_length -1);
for($i = 1; $i <= $bit_length; $i++)
{
$us = infrared_get_count($i);
if($us < 2500)
if($us > 1500)
{
$data |= ($mask>> ($i-1));
}
}
return $data;
}
?>
Config file <phpoc.ini>
Code:
ht0_count_buf_size = 256 ht1_count_buf_size = 256 ht2_count_buf_size = 256 ht3_count_buf_size = 256