#include "controller.h" #include "main.h" #include "spi_master.h" #include "config.h" #include "playlist.h" #include "http_server.h" #include "updater.h" #include Controller::Controller(Player* p, PlaylistManager* playlist_manager) { player = p; pm = playlist_manager; _rfid = new MFRC522(17, MFRC522::UNUSED_PIN); player->register_controller(this); BTN_NEXT_SETUP(); BTN_PREV_SETUP(); BTN_VOL_UP_SETUP(); BTN_VOL_DOWN_SETUP(); SPIMaster::select_rc522(); DEBUG("Initializing RC522...\n"); _rfid->PCD_Init(); #ifdef SHOW_DEBUG _rfid->PCD_DumpVersionToSerial(); #endif SPIMaster::select_rc522(false); INFO("RC522 initialized.\n"); for (uint8_t i=0; i 0) { process_message(_cmd_queue); _cmd_queue = ""; } #ifdef OTA_UPDATE_URL if (!player->is_playing() && _last_update_check_at < now && _last_update_check_at + OTA_CHECK_INTERVAL < now) { Updater::run(); } else { _last_update_check_at = now; } #endif if (!player->is_playing() && !WiFi.isConnected() && _last_wifi_try_at < now && _last_wifi_try_at + 5*60*1000 < now) { wifi_connect(); } else { _last_wifi_try_at = now; } } uint32_t Controller::_get_rfid_card_uid() { SPIMaster::select_rc522(); if (!_rfid->PICC_ReadCardSerial()) { if (!_rfid->PICC_IsNewCardPresent()) { return 0; } if (!_rfid->PICC_ReadCardSerial()) { return 0; } } SPIMaster::select_rc522(false); uint32_t uid = _rfid->uid.uidByte[0]<<24 | _rfid->uid.uidByte[1]<<16 | _rfid->uid.uidByte[2]<<8 | _rfid->uid.uidByte[3]; return uid; } void Controller::_check_rfid() { //TRACE("check_rfid running...\n"); MFRC522::StatusCode status; if (_rfid_present) { byte buffer[2]; byte buffer_size = 2; SPIMaster::select_rc522(); status = _rfid->PICC_WakeupA(buffer, &buffer_size); if (status == MFRC522::STATUS_OK) { // Card is still present. _rfid->PICC_HaltA(); SPIMaster::select_rc522(false); return; } SPIMaster::select_rc522(false); // Card is now gone _rfid_present = false; INFO("No more RFID card.\n"); if (_state != LOCKED) { player->stop(); } send_controller_status(); } else { uint32_t uid = _get_rfid_card_uid(); if (uid > 0) { String temp = String(uid, HEX); String s_uid = ""; for (int i=0; i<(8-temp.length()); i++) { s_uid.concat("0"); } s_uid.concat(temp); INFO("New RFID card uid: %s\n", s_uid.c_str()); _last_rfid_uid = s_uid; _rfid_present = true; String data = _read_rfid_data(); _last_rfid_data = data; Playlist* pl = pm->get_playlist_for_id(s_uid); if (data.indexOf("[lock]") != -1) { if (_state == LOCKED) { _state = NORMAL; DEBUG("ControllerState is now UNLOCKED\n"); } else { DEBUG("ControllerState is now LOCKING\n"); _state = LOCKING; } } if (pl==NULL) { INFO("Could not find album for id '%s'.\n", s_uid.c_str()); send_controller_status(); return; } int index; if (data.indexOf("[advent]") != -1 && pl->is_fresh()) { struct tm time; getLocalTime(&time); if (time.tm_mon == 11) { // tm_mon is "months since january", so 11 means december. pl->advent_shuffle(time.tm_mday); } else { DEBUG("Album is in advent mode, but it isn't december (yet). Not playing.\n"); return; } } else if (data.indexOf("[random]") != -1 && pl->is_fresh()) { pl->shuffle(); } else if ((index=data.indexOf("[random:")) != -1 && pl->is_fresh()) { String temp = data.substring(index + 8); index = temp.indexOf("]"); TRACE("temp: %s, temp.substring(0, %d): %s\n", temp.c_str(), index, temp.substring(0, index).c_str()); if (index>0) { uint8_t random_offset = temp.substring(0, index).toInt(); pl->shuffle(random_offset); } } if (_state == LOCKED) { DEBUG("ControllerState is LOCKED, ignoring card.\n"); return; } if (_state == LOCKING) { _state = LOCKED; DEBUG("ControllerState is now LOCKED.\n"); } player->play(pl); //send_playlist_manager_status(); send_controller_status(); } } } String Controller::_read_rfid_data() { TRACE("_read_rfid_data() running...\n"); static MFRC522::MIFARE_Key keys[8] = { {{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}}, // D3 F7 D3 F7 D3 F7 {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, // FF FF FF FF FF FF = factory default {{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}}, // A0 A1 A2 A3 A4 A5 {{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5}}, // B0 B1 B2 B3 B4 B5 {{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd}}, // 4D 3A 99 C3 51 DD {{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a}}, // 1A 98 2C 7E 45 9A {{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, // AA BB CC DD EE FF {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} // 00 00 00 00 00 00 }; SPIMaster::select_rc522(); DEBUG("Trying to read RFID data...\n"); String data = ""; MFRC522::PICC_Type type = _rfid->PICC_GetType(_rfid->uid.sak); uint8_t sectors = 0; switch(type) { case MFRC522::PICC_TYPE_MIFARE_MINI: sectors = 5; break; case MFRC522::PICC_TYPE_MIFARE_1K: sectors = 16; break; case MFRC522::PICC_TYPE_MIFARE_4K: sectors = 40; break; default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str()); } sectors = 2; // Pretend we have only two sectors, so we read only sector #1. int good_key_index = -1; for (uint8_t sector=1; sectorkeyByte[0], k->keyByte[1], k->keyByte[2], k->keyByte[3], k->keyByte[4], k->keyByte[5]); status = _rfid->PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block_offset, k, &_rfid->uid); if (status == MFRC522::STATUS_OK) { TRACE("Authentication succeeded with key #%d\n", i); good_key_index = i; break; } } if (good_key_index == -1) { TRACE("Could not find a valid MIFARE key.\n"); } else { for (uint8_t block=0; blockMIFARE_Read(block_offset + block, buffer, &byte_count); if (status != MFRC522::STATUS_OK) { DEBUG("MIFARE_Read() failed: %s\n", String(_rfid->GetStatusCodeName(status)).c_str()); continue; } for (int i=0; i<16; i++) { if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]); } } } } _rfid->PICC_HaltA(); _rfid->PCD_StopCrypto1(); DEBUG("Data from RFID: %s\n", data.c_str()); SPIMaster::select_rc522(false); return data; } void Controller::_check_serial() { if (Serial.available() > 0) { char c = Serial.read(); Serial.printf("%c", c); if (c==10 || c==13) { if (_serial_buffer.length()>0) { process_message(_serial_buffer); _serial_buffer = String(); } } else { _serial_buffer.concat(c); } } } bool Controller::process_message(String cmd) { DEBUG("Executing command: %s\n", cmd.c_str()); if (cmd.startsWith("play ")) { Playlist* p = pm->get_playlist_for_folder(cmd.substring(5)); player->play(p); } else if (cmd.equals("play")) { player->play(); } else if (cmd.equals("stop")) { player->stop(); } else if (cmd.equals("help")) { _execute_command_help(); } else if (cmd.equals("-")) { player->vol_down(); } else if (cmd.equals("+")) { player->vol_up(); } else if (cmd.startsWith("volume=")) { uint8_t vol = cmd.substring(7).toInt(); player->set_volume(vol); } else if (cmd.equals("track_prev")) { player->track_prev(); } else if (cmd.equals("track_next")) { player->track_next(); } else if (cmd.startsWith("track=")) { uint8_t track = cmd.substring(6).toInt(); player->set_track(track); } else if (cmd.equals("ids")) { pm->dump_ids(); } else if (cmd.equals("reset_vs1053")) { player->stop(); player->init(); } else if (cmd.equals("reboot")) { ESP.restart(); } else if (cmd.startsWith("add_mapping=")) { String rest = cmd.substring(12); uint8_t idx = rest.indexOf('='); String id = rest.substring(0, idx); String folder = rest.substring(idx + 1); pm->add_mapping(id, folder); send_playlist_manager_status(); #ifdef OTA_UPDATE_URL } else if (cmd.equals("update")) { Updater::run(); #endif } else if (cmd.startsWith("trace=")) { int val = cmd.substring(6).toInt(); if (val==0) { trace_enabled = false; prefs.putBool("trace_enabled", false); } else if (val==1) { trace_enabled = true; prefs.putBool("trace_enabled", true); } } else if (cmd.startsWith("debug=")) { int val = cmd.substring(6).toInt(); if (val==0) { debug_enabled = false; prefs.putBool("debug_enabled", false); } else if (val==1) { debug_enabled = true; prefs.putBool("debug_enabled", true); } } else { ERROR("Unknown command: %s\n", cmd.c_str()); return false; } return true; } void Controller::_execute_command_ls(String path) { INFO("Listing contents of %s:\n", path.c_str()); // TODO //std::list files = player->ls(path); //for(std::list::iterator it=files.begin(); it!=files.end(); ++it) { // INFO(" %s\n", (*it).c_str()); //} } void Controller::_execute_command_help() { INFO("Valid commands are:"); INFO(" help - Displays this help\n"); //INFO(" ls [dir] - Lists the contents of [dir] or, if not given, of /\n"); INFO(" ids - Lists all known ID-to-folder mappings\n"); INFO(" play [id] - Plays the album with the given id\n"); INFO(" stop - Stops playback\n"); INFO(" - / + - Decrease or increase the volume\n"); INFO(" p / n - Previous or next track\n"); } void Controller::_check_buttons() { if (BTN_PREV() && _debounce_button(0)) { if (_state == NORMAL) { player->track_prev(); } else { DEBUG("Ignoring btn_prev because state is LOCKED.\n"); } } else if (BTN_VOL_UP() && _debounce_button(1)) { player->vol_up(); } else if (BTN_VOL_DOWN() && _debounce_button(2)) { player->vol_down(); } else if (BTN_NEXT() && _debounce_button(3)) { if (_state == NORMAL) { player->track_next(); } else { DEBUG("Ignoring btn_next because state is LOCKED.\n"); } } } bool Controller::_debounce_button(uint8_t index) { bool ret = false; if (_button_last_pressed_at[index] + DEBOUNCE_MILLIS < millis()) { DEBUG("Button %d pressed.\n", index); ret = true; } _button_last_pressed_at[index] = millis(); return ret; } String Controller::json() { DynamicJsonDocument json(1024); json["_type"] = "controller"; switch(_state) { case LOCKED: json["state"] = "locked"; break; case LOCKING: json["state"] = "locking"; break; case NORMAL: json["state"] = "normal"; break; } json["is_rfid_present"] = _rfid_present; JsonObject rfid = json.createNestedObject("last_rfid"); rfid["uid"] = _last_rfid_uid; rfid["data"] = _last_rfid_data; json["uptime"] = millis() / 1000; json["free_heap"] = ESP.getFreeHeap(); JsonObject versions = json.createNestedObject("versions"); versions["ota"] = OTA_VERSION; #ifdef VERSION versions["release"] = VERSION; #else versions["release"] = "unknown"; #endif JsonObject wifi = json.createNestedObject("wifi"); if (WiFi.isConnected()) { wifi["connected"] = true; wifi["ssid"] = WiFi.SSID(); wifi["rssi"] = WiFi.RSSI(); } else { wifi["connected"] = false; } return json.as(); } void Controller::send_player_status() { TRACE("In send_player_status()...\n"); if (_http_server->ws->count() > 0) { _http_server->ws->textAll(player->json()); _http_server->ws->textAll(player->position_json()); } } void Controller::send_playlist_manager_status() { TRACE("In send_playlist_manager_status()...\n"); if (_http_server->ws->count() > 0) { _http_server->ws->textAll(pm->json()); } } void Controller::send_position() { TRACE("In send_position()...\n"); if (_http_server->ws->count() > 0) { _http_server->ws->textAll(player->position_json()); } _last_position_info_at = millis(); } void Controller::send_controller_status() { TRACE("In send_controller_status()...\n"); if (_http_server->ws->count() > 0) { _http_server->ws->textAll(json()); } } void Controller::inform_new_client(AsyncWebSocketClient* client) { String s; s += pm->json(); s += '\n'; s += player->json(); s += '\n'; s += player->position_json(); s += '\n'; s += json(); client->text(s); } void Controller::queue_command(String s) { DEBUG("Enqeueing command '%s'.\n", s.c_str()); _cmd_queue = s; } void Controller::update_playlist_manager() { pm->scan_files(); send_playlist_manager_status(); }