1 #include <libcec/cec.h> 2 #include <iostream> 3 #include <thread> 4 #include <stdio.h> 5 #include <unistd.h> 6 #include <assert.h> 7 8 /* 9 * Constants 10 */ 11 const unsigned int receiver_steps = 36; 12 const unsigned int default_volume = 20; 13 const useconds_t post_send_delay = 200000; 14 const double step_size = ((double) (CEC::CEC_AUDIO_VOLUME_MAX - CEC::CEC_AUDIO_VOLUME_MIN)) / ((double) receiver_steps); 15 16 /* 17 * Global state 18 */ 19 double current_volume = 0; 20 bool current_mute = false; 21 CEC::ICECAdapter *adapter = 0; 22 23 /* 24 * Log object 25 */ 26 class logger { 27 public: 28 ~logger() { std::cerr << std::endl; } 29 template <class T> 30 logger &operator<<(T x) { 31 std::cerr << x; 32 return(*this); 33 } 34 }; 35 36 #define logstream(x...) { logger logstreaminstance; logstreaminstance x; } 37 38 /* 39 * CEC Helpers 40 */ 41 bool cec_execute_command(CEC::ICECAdapter *adapter, char *command) { 42 auto command_data = adapter->CommandFromString(command); 43 auto tx = adapter->Transmit(command_data); 44 if (!tx) { 45 std::cerr << "Failed to execute command: " << command << std::endl; 46 return(false); 47 } 48 49 return(true); 50 } 51 52 bool cec_enable_audio(CEC::ICECAdapter *adapter) { 53 return(cec_execute_command(adapter, (char *) "50:72:01")); 54 } 55 56 bool cec_set_volume(CEC::ICECAdapter *adapter, int volume, bool muted = false) { 57 char command[64]; 58 if (muted) { 59 volume |= CEC::CEC_AUDIO_MUTE_STATUS_MASK; 60 } 61 62 snprintf(command, sizeof(command), "50:7A:%02x", volume); 63 64 return(cec_execute_command(adapter, command)); 65 } 66 67 void cec_log_message(void *cb_data, const CEC::cec_log_message *message) { 68 // std::cerr << message->message << std::endl; 69 } 70 71 /* 72 * IR Helpers 73 */ 74 void ir_increase_volume() { 75 logstream(<< "Increasing volume (from " << current_volume << ")"); 76 77 system("ir-ctl -d /dev/lirc0 --send ~/cec-volume/mcm-vol-up.ir"); 78 usleep(post_send_delay); 79 return; 80 } 81 82 void ir_decrease_volume() { 83 logstream(<< "Decreasing volume (from " << current_volume << ")"); 84 85 system("ir-ctl -d /dev/lirc0 --send ~/cec-volume/mcm-vol-down.ir"); 86 usleep(post_send_delay); 87 return; 88 } 89 90 void ir_toggle_mute() { 91 logstream(<< "Toggling mute (from " << current_mute << ")"); 92 93 system("ir-ctl -d /dev/lirc0 --send ~/cec-volume/mcm-vol-mute.ir"); 94 usleep(post_send_delay); 95 return; 96 } 97 98 void ir_set_volume(unsigned int value) { 99 bool set_mute = current_mute; 100 101 logstream(<< "Setting volume to " << value << " with mute = " << set_mute); 102 103 if (current_mute) { 104 ir_toggle_mute(); 105 current_mute = !current_mute; 106 } 107 108 current_volume = CEC::CEC_AUDIO_VOLUME_MAX; 109 110 for (unsigned int i = 0; i < receiver_steps; i++) { 111 ir_decrease_volume(); 112 current_volume -= step_size; 113 } 114 115 assert(current_volume < (CEC::CEC_AUDIO_VOLUME_MIN + step_size + 0.1)); 116 117 auto steps_value = value / step_size; 118 for (unsigned int i = 0; i < steps_value; i++) { 119 ir_increase_volume(); 120 current_volume += step_size; 121 } 122 123 if (set_mute) { 124 ir_toggle_mute(); 125 current_mute = !current_mute; 126 } 127 128 logstream(<< "Done setting volume to " << value << "(current_volume =" << current_volume << ")"); 129 } 130 131 /* 132 * CEC Callback for a command from the bus to our process 133 */ 134 void cec_handle_command(void *cb_data, const CEC::cec_command *command) { 135 if (command->opcode == CEC::CEC_OPCODE_GIVE_AUDIO_STATUS) { 136 ((CEC::cec_command *) command)->Clear(); 137 logstream(<< "Got command CEC_OPCODE_GIVE_AUDIO_STATUS"); 138 cec_set_volume(adapter, (int) current_volume, current_mute); 139 } 140 } 141 142 /* 143 * CEC Callback for handling a key 144 */ 145 std::thread *change_thread = 0; 146 void cec_handle_key_thread(CEC::cec_user_control_code keycode) { 147 switch (keycode) { 148 case CEC::CEC_USER_CONTROL_CODE_VOLUME_DOWN: 149 if (current_mute) { 150 ir_toggle_mute(); 151 current_mute = !current_mute; 152 } 153 ir_decrease_volume(); 154 current_volume -= step_size; 155 break; 156 case CEC::CEC_USER_CONTROL_CODE_VOLUME_UP: 157 if (current_mute) { 158 ir_toggle_mute(); 159 current_mute = !current_mute; 160 } 161 ir_increase_volume(); 162 current_volume += step_size; 163 break; 164 case CEC::CEC_USER_CONTROL_CODE_MUTE: 165 ir_toggle_mute(); 166 current_mute = !current_mute; 167 break; 168 default: 169 /* Ignored */ 170 break; 171 } 172 173 if (current_volume < CEC::CEC_AUDIO_VOLUME_MIN) { 174 current_volume = CEC::CEC_AUDIO_VOLUME_MIN; 175 } 176 177 if (current_volume > CEC::CEC_AUDIO_VOLUME_MAX) { 178 current_volume = CEC::CEC_AUDIO_VOLUME_MAX; 179 } 180 181 if (adapter) { 182 auto tx = cec_set_volume(adapter, (int) current_volume, current_mute); 183 if (!tx) { 184 std::cerr << "Failed to set volume" << std::endl; 185 } 186 } 187 188 /* Really should be a mutex around this */ 189 change_thread = 0; 190 } 191 192 void cec_handle_key(void *cb_data, const CEC::cec_keypress *key_info) { 193 /* 194 * Key down events are sent with Duration=0, ignore them 195 */ 196 if (key_info->duration == 0) { 197 return; 198 } 199 200 /* 201 * If an event is already being processed, ignore this one 202 */ 203 if (change_thread) { 204 return; 205 } 206 207 logstream(<< "Key " << key_info->keycode << "; Duration=" << key_info->duration); 208 change_thread = new std::thread(cec_handle_key_thread, key_info->keycode); 209 } 210 211 int main(int argc, char *argv[]) { 212 CEC::libcec_configuration configuration; 213 CEC::ICECCallbacks callbacks; 214 215 configuration.Clear(); 216 configuration.deviceTypes.Add(CEC::CEC_DEVICE_TYPE_AUDIO_SYSTEM); 217 configuration.clientVersion = CEC::LIBCEC_VERSION_CURRENT; 218 configuration.bActivateSource = 0; 219 sprintf(configuration.strDeviceName, "SchiitModi3"); 220 221 adapter = CECInitialise(&configuration); 222 223 /* 224 * What if I wasn't using the RPI port ? 225 */ 226 adapter->InitVideoStandalone(); 227 228 callbacks.Clear(); 229 callbacks.logMessage = &cec_log_message; 230 callbacks.keyPress = &cec_handle_key; 231 callbacks.commandReceived = &cec_handle_command; 232 233 #if CEC_LIB_VERSION_MAJOR >= 5 234 auto set_callbacks_ret = adapter->SetCallbacks(&callbacks); 235 #else 236 auto set_callbacks_ret = adapter->EnableCallbacks(0, &callbacks); 237 #endif 238 239 if (!set_callbacks_ret) { 240 std::cerr << "Failed to set callbacks" << std::endl; 241 return(4); 242 } 243 244 auto open_ret = adapter->Open("RPI"); 245 if (!open_ret) { 246 std::cerr << "Failed to open port" << std::endl; 247 return(1); 248 } 249 250 auto audio_ret = cec_enable_audio(adapter); 251 if (!audio_ret) { 252 std::cerr << "Failed to enable audio" << std::endl; 253 return(2); 254 } 255 256 ir_set_volume(default_volume); 257 258 /* 259 * Another thread handles sending callbacks, so we just need to wait forever. 260 */ 261 while (true) { 262 sleep(300); 263 } 264 } |