Etter å ha brutt ned mitt esp kontrollerte pepperkakehus var jeg i prosjekt vakuum etter jul, noe som utløste ideen om å koble min NAD C320BEE forsterker på nett for styring fra Home Assistant. Første tanken var en IR fra en Raspberry som står som streaming node i stereoanlegget men fikk lyst å grave litt dypere.
NAD har en IR in, IR ut og 12v trigger 3.5mm kontakt som jeg måtte utforske litt. Med litt googling fant jeg krets diagrammet for forsterkeren og det viser seg at det er relativt enkelt å koble seg på.
IR in/ut er koblet via transistor drivere, forventer 5v men fungerer på 3.3v
IR ut gir ett rått modulert IR signal, dvs den videresender alle IR signaler selv om de ikke er NAD (SR5 for C320BEE)
Så, etter litt testing med en WeMos D1 mini (esp8266) ender jeg opp med en enkel kobling.
En enkel spenningsdeler på 12v for å detektere power status på forsterkeren og en pulldown på input from IR out (motstandene er basert på hva jeg hadde i skuffen..).
Dermed var det klart for å sette opp i ESPHome/Home Assistant:
IR bruker remote_receiver/remote_transmitter i ESPHome
Trigger 12v kobles på en binary_sensor
Kobler mot MQTT for direkte kontroll (utenom HA)
Definerer fjernkontroll funksjoner som button's i ESPHome, fjernkontrolleren er en tilfeldig jeg hadde liggende med NEC encoding, adresse 0x1000
Jeg liker at stereo anlegget virker selv om hjemme serveren er nede så jeg koder inn "on_nec" lokalt i remote_receiver for direkte kontroll.
Definerer en power switch (template switch) og mute for bruk i HA samt en template select for å velge forsterker input.
Styring av andre enheter
Siden jeg publiserer alle NEC IR koder til MQTT kan jeg fange opp andre fjernkontrollere og gjøre aksjoner i Home Assistant, f.eks styring av en media_player (MPD i mitt tilfelle).
automation:
- alias: Hifi - play
trigger:
- platform: mqtt
topic: ir/nec/0x1000
encoding: utf-8
payload: 40800
value_template: "{{ value_json.command }}"
condition: []
action:
- service: media_player.media_play
data: {}
target:
entity_id: media_player.media_system
mode: single
- alias: Hifi - pause
trigger:
- platform: mqtt
topic: ir/nec/0x1000
encoding: utf-8
payload: 40290
value_template: "{{ value_json.command }}"
Notater om IR koder
Jeg hadde mistet NAD fjernkontrollen for mange år siden men jeg fant ut at den bruke SR5 i lirc som ser noe slikt ut:
begin remote
name NAD_SR5
bits 16
flags SPACE_ENC|CONST_LENGTH
eps 30
aeps 100
header 9060 4418
one 644 1600
zero 644 478
ptrail 644
repeat 9059 2172
pre_data_bits 16
pre_data 0xE13E
gap 107847
toggle_bit 0
begin codes
KEY_POWER_ON 0xA45B
KEY_POWER_OFF 0x13EC
KEY_CD 0xA15E
KEY_TUNER 0xBB44
KEY_AUX 0xD926
KEY_VIDEO 0x43BC
KEY_DISC 0x916E
KEY_MUTE 0x29D6
KEY_VOLUMEUP 0x11EE
KEY_VOLUMEDOWN 0x31CE
end codes
end remote
Forsterkerens NEC IR addresse er gitt i pre_data som 0xE13E, ihht ESPHome NEC må denne bit-reverseres så forsterkerens adressen er 0x07C87.
Kommandoene gitt i lirc er som de sendes, dvs 8b inverted + 8b. Dette må bit reverseres for å få ESPHome NEC koden.
Dermed kan vi konvertere lirc IR filer direkte til ESPHome NEC.
Eksempel:
Videre
Jeg ser videre på muligheten for å få opp i2s (audio) for å gjøre forsterkeren til en streaming enhet, litt usikker på om lydkvaliteten blir ok, må kanskje skifte til en ESP32 for å kunne håndtere multirom streaming (Snapcast)
Prosjektet kan egentlig bygges inn i forsterkeren (garantien løp ut for noen år siden, ca 20?), den har 5v "alltid på" for sin innebygde IR kontroller, spørsmål om ev. støy fra ESP.
ESPHome YAML
esphome:
name: "nad-c320bee"
esp8266:
board: esp01_1m
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "nad-c320bee"
password: "XXXXXXXXXX"
captive_portal:
# Should use hassio mqtt server
mqtt:
topic_prefix: esp
discovery: false
broker: 192.168.0.99
username: yyyyy
password: xxxxxxxxxx
id: mqtt_esphome
# Check 12v trigger
binary_sensor:
- platform: gpio
name: "NAD Power State"
id: nad_power_state
pin:
number: GPIO14 # D5
device_class: power
internal: false
# Note power off/on resets the mute state
on_state:
- switch.template.publish:
id: nad_mute
state: OFF
# Direct from NAD IR-Output
remote_receiver:
pin:
number: GPIO12 # D6
inverted: False
dump: all
tolerance: 50%
filter: 50us
idle: 10ms
# Publish all/most NEC parsed commands to MQTT
on_nec:
then:
- if:
condition:
lambda: |-
if(x.address != 0x1000) return true;
switch(x.command) {
// Volume down
case 0xbf40:
id(nad_volumedown).press();
return false;
// Volume up
case 0xe01f:
id(nad_volumeup).press();
return false;
// Volume mute
case 0xe718:
id(nad_mute).toggle();
return false;
// Power toggle
case 0xff00:
id(nad_power).toggle();
return true;
// Channel up
case 0xef10: {
auto call = id(nad_input_source).make_call();
call.select_next(true);
call.perform();
return false;
}
// Channel down
case 0xf00f: {
auto call = id(nad_input_source).make_call();
call.select_previous(true);
call.perform();
return false;
}
// Pass rest on
default:
break;
}
return true;
then:
- mqtt.publish_json:
topic: !lambda |-
static char topic[16];
sprintf(topic, "ir/nec/0x%x", x.address);
return topic;
payload: !lambda |-
root["address"] = x.address;
root["command"] = x.command;
# Direct to NAD IR-Input
remote_transmitter:
carrier_duty_percent: 50%
pin:
number: GPIO13 # D7
switch:
- platform: template
name: "NAD Amplifier Power"
id: nad_power
icon: "mdi:audio-video"
lambda: |-
return id(nad_power_state).state;
turn_on_action:
- button.press: nad_cmd_poweron
turn_off_action:
- button.press: nad_cmd_poweroff
optimistic: false
assumed_state: false
- platform: template
name: "NAD Volume Mute"
id: nad_mute
icon: "mdi:audio-video"
turn_on_action:
- button.press: nad_cmd_mute
turn_off_action:
- button.press: nad_cmd_mute
optimistic: true
assumed_state: false
# XXX: Tape monitor (1) is not a toggle as rest...
# it will co-exist with others
select:
- platform: template
name: "NAD Input Source"
id: nad_input_source
icon: "mdi:audio-video"
options:
#- tape1
- tape2
- tuner
- aux
- video
- cd
- disc
set_action:
- lambda: |-
static const std::map<std::string, template_::TemplateButton *> input = {
{"tape1", nad_cmd_tape1},
{"tape2", nad_cmd_tape2},
{"tuner", nad_cmd_tuner},
{"aux", nad_cmd_aux},
{"video", nad_cmd_video},
{"cd", nad_cmd_cd},
{"disc", nad_cmd_disc},
};
// Delay needed to separate from earler (power on) commands
delayMicroseconds(100000);
if(auto it{ input.find(x) }; it != std::end(input)) {
const auto&[key, value] {*it};
value->press();
} else {
ESP_LOGD("main", "unknown source for amplifier input select [%s]", x.c_str());
}
restore_value: true
optimistic: true
button:
#
# Address: 0x7C87 is 0111 1100 1000 0111
# Reversed: 1110 0001 0011 1110 -> 0xE13E which is the address Lirc uses for SR5
#
# Command: 0x6B94 is 0100 1011 1001 0100 -> is inverted cmd + cmd
# 6B => 0110 1011
# 94 => 1001 0100
# Reversed: 0010 1001 1101 0110 -> 0x29D6 which is the command Lirc uses for SR5
#
#
# mosquitto_pub -h stuepi -u sysop -P xs2mf4ao -t esp/button/nad_mute/command -m 'PRESS'
#
# Commands: Tuner, Aux, Video, CD, Disc (DD/9B/C2/85/89)
# Tape Monitor 1, Tape 2 (8D/91)
# Vol up/down, mute (88/8C/94)
# Power on/off (25/C8)
#
- platform: template
name: "NAD Select Tuner"
id: nad_cmd_tuner
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x22DD
internal: true
- platform: template
name: "NAD Select AUX"
id: nad_cmd_aux
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x649b
internal: true
- platform: template
name: "NAD Select VIDEO"
id: nad_cmd_video
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x3dc2
internal: true
- platform: template
name: "NAD Select CD"
id: nad_cmd_cd
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x7a85
internal: true
- platform: template
name: "NAD Select DISC"
id: nad_cmd_disc
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x7689
internal: true
- platform: template
name: "NAD Select Tape Monitor 1"
id: nad_cmd_tape1
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x728d
internal: true
- platform: template
name: "NAD Select Tape 2"
id: nad_cmd_tape2
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x6e91
internal: true
- platform: template
name: "NAD Volume up"
id: nad_volumeup
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x7788
- platform: template
name: "NAD Volume down"
id: nad_volumedown
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x738c
- platform: template
name: "NAD Mute"
id: nad_cmd_mute
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x6B94
internal: true
- platform: template
name: "NAD Power on"
id: nad_cmd_poweron
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0xda25
internal: true
- platform: template
name: "NAD Power off"
id: nad_cmd_poweroff
on_press:
- remote_transmitter.transmit_nec:
address: 0x7C87
command: 0x37c8
internal: true
Home assistant media player
media_player:
#
# Implementation of NAD amplifier
#
- platform: media_player_template
media_players:
nad_amplifier:
friendly_name: NAD Amplifier
device_class: receiver
# value_template: "{{ states('switch.nad_amplifier_power') }}" <-- XXX: test this
value_template: >
{% if is_state("switch.nad_amplifier_power", "on") -%}
on
{%- else -%}
off
{%- endif %}
#current_is_muted_template: "{{ states('switch.nad_volume_mute') }}" <-- XXX: test this
current_is_muted_template: >
{% if is_state("switch.nad_volume_mute", "on") -%}
True
{%- else -%}
False
{%- endif %}
current_source_template: "{{ states('select.nad_input_source') }}"
# Note tape1 is a "tape monitor" and coexists with the others
inputs:
tape2:
service: select.select_option
data:
option: tape2
target:
entity_id: select.nad_input_source
tuner:
service: select.select_option
data:
option: tuner
target:
entity_id: select.nad_input_source
aux:
service: select.select_option
data:
option: aux
target:
entity_id: select.nad_input_source
video:
service: select.select_option
data:
option: video
target:
entity_id: select.nad_input_source
cd:
service: select.select_option
data:
option: cd
target:
entity_id: select.nad_input_source
disc:
service: select.select_option
data:
option: disc
target:
entity_id: select.nad_input_source
turn_on:
service: switch.turn_on
target:
entity_id: switch.nad_amplifier_power
turn_off:
service: switch.turn_off
target:
entity_id: switch.nad_amplifier_power
mute:
service: switch.toggle
target:
entity_id: switch.nad_volume_mute
volume_up:
service: button.press
target:
entity_id: button.nad_volume_up
volume_down:
service: button.press
target:
entity_id: button.nad_volume_down