blob: 1c5ca71a4428f658598731631bafdcd82a12baa7 [file]
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BluetoothWidget.h"
#include "Button.h"
#include "CHIPDeviceManager.h"
#include "DataModelHandler.h"
#include "Display.h"
#include "EchoDeviceCallbacks.h"
#include "LEDWidget.h"
#include "ListScreen.h"
#include "QRCodeScreen.h"
#include "RendezvousSession.h"
#include "ScreenManager.h"
#include "WiFiWidget.h"
#include "esp_heap_caps_init.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_spi_flash.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include <cmath>
#include <cstdio>
#include <string>
#include <vector>
#include <crypto/CHIPCryptoPAL.h>
#include <platform/CHIPDeviceLayer.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <support/ErrorStr.h>
#include <transport/SecureSessionMgr.h>
using namespace ::chip;
using namespace ::chip::DeviceLayer;
extern void startServer();
#if CONFIG_USE_ECHO_CLIENT
extern void startClient(void);
#endif // CONFIG_USE_ECHO_CLIENT
#if CONFIG_DEVICE_TYPE_M5STACK
#define BUTTON_1_GPIO_NUM GPIO_NUM_39 // Left button on M5Stack
#define BUTTON_2_GPIO_NUM GPIO_NUM_38 // Middle button on M5Stack
#define BUTTON_3_GPIO_NUM GPIO_NUM_37 // Right button on M5Stack
#define STATUS_LED_GPIO_NUM GPIO_NUM_MAX // No status LED on M5Stack
#define LIGHT_CONTROLLER_OUTPUT_GPIO_NUM GPIO_NUM_2 // Use GPIO2 as the light controller output on M5Stack
#elif CONFIG_DEVICE_TYPE_ESP32_DEVKITC
#define BUTTON_1_GPIO_NUM GPIO_NUM_34 // Button 1 on DevKitC
#define BUTTON_2_GPIO_NUM GPIO_NUM_35 // Button 2 on DevKitC
#define BUTTON_3_GPIO_NUM GPIO_NUM_0 // Button 3 on DevKitC
#define STATUS_LED_GPIO_NUM GPIO_NUM_2 // Use LED1 (blue LED) as status LED on DevKitC
#define LIGHT_CONTROLLER_OUTPUT_GPIO_NUM GPIO_NUM_33 // Use GPIO33 as the light controller output on DevKitC
#else // !CONFIG_DEVICE_TYPE_ESP32_DEVKITC
#error "Unsupported device type selected"
#endif // !CONFIG_DEVICE_TYPE_ESP32_DEVKITC
// A temporary value assigned for this example's QRCode
// Spells CHIP on a dialer
#define EXAMPLE_VENDOR_ID 2447
// Spells ESP32 on a dialer
#define EXAMPLE_PRODUCT_ID 37732
// Used to indicate that an IP address has been added to the QRCode
#define EXAMPLE_VENDOR_TAG_IP 1
#if CONFIG_HAVE_DISPLAY
// Where to draw the connection status message
#define CONNECTION_MESSAGE 75
// Where to draw the IPv6 information
#define IPV6_INFO 85
#endif // CONFIG_HAVE_DISPLAY
LEDWidget statusLED1;
LEDWidget statusLED2;
BluetoothWidget bluetoothLED;
WiFiWidget wifiLED;
extern NodeId kLocalNodeId;
extern void PairingComplete(Optional<NodeId> peerNodeId, uint16_t peerKeyId, uint16_t localKeyId, SecurePairingSession * pairing);
const char * TAG = "wifi-echo-demo";
static EchoDeviceCallbacks EchoCallbacks;
RendezvousSession * rendezvousSession = nullptr;
namespace {
std::vector<Button> buttons = { Button(), Button(), Button() };
std::vector<gpio_num_t> button_gpios = { BUTTON_1_GPIO_NUM, BUTTON_2_GPIO_NUM, BUTTON_3_GPIO_NUM };
// Pretend these are devices with endpoints with clusters with attributes
typedef std::tuple<std::string, std::string> Attribute;
typedef std::vector<Attribute> Attributes;
typedef std::tuple<std::string, Attributes> Cluster;
typedef std::vector<Cluster> Clusters;
typedef std::tuple<std::string, Clusters> Endpoint;
typedef std::vector<Endpoint> Endpoints;
typedef std::tuple<std::string, Endpoints> Device;
typedef std::vector<Device> Devices;
Devices devices;
void AddAttribute(std::string name, std::string value)
{
Attribute attribute = std::make_tuple(std::move(name), std::move(value));
std::get<1>(std::get<1>(std::get<1>(devices.back()).back()).back()).emplace_back(std::move(attribute));
}
void AddCluster(std::string name)
{
Cluster cluster = std::make_tuple(std::move(name), std::move(Attributes()));
std::get<1>(std::get<1>(devices.back()).back()).emplace_back(std::move(cluster));
}
void AddEndpoint(std::string name)
{
Endpoint endpoint = std::make_tuple(std::move(name), std::move(Clusters()));
std::get<1>(devices.back()).emplace_back(std::move(endpoint));
}
void AddDevice(std::string name)
{
Device device = std::make_tuple(std::move(name), std::move(Endpoints()));
devices.emplace_back(std::move(device));
}
#if CONFIG_HAVE_DISPLAY
class EditAttributeListModel : public ListScreen::Model
{
int d;
int e;
int c;
int a;
public:
EditAttributeListModel(int d, int e, int c, int a) : d(d), e(e), c(c), a(a) {}
virtual std::string GetTitle()
{
auto & attribute = std::get<1>(std::get<1>(std::get<1>(devices[d])[e])[c])[a];
auto & name = std::get<0>(attribute);
auto & value = std::get<1>(attribute);
char buffer[64];
snprintf(buffer, sizeof(buffer), "%s : %s", name.c_str(), value.c_str());
return buffer;
}
virtual int GetItemCount() { return 2; }
virtual std::string GetItemText(int i) { return i == 0 ? "+" : "-"; }
virtual void ItemAction(int i)
{
auto & attribute = std::get<1>(std::get<1>(std::get<1>(devices[d])[e])[c])[a];
auto & value = std::get<1>(attribute);
int n;
if (sscanf(value.c_str(), "%d", &n) == 1)
{
ESP_LOGI(TAG, "editing attribute as integer: %d (%s)", n, i == 0 ? "+" : "-");
n += (i == 0) ? 1 : -1;
char buffer[32];
sprintf(buffer, "%d", n);
value = buffer;
}
else
{
ESP_LOGI(TAG, "editing attribute as string: '%s' (%s)", value.c_str(), i == 0 ? "+" : "-");
value = (value == "Closed") ? "Open" : "Closed";
}
}
};
class AttributeListModel : public ListScreen::Model
{
int d;
int e;
int c;
public:
AttributeListModel(int d, int e, int c) : d(d), e(e), c(c) {}
virtual std::string GetTitle() { return "Attributes"; }
virtual int GetItemCount() { return std::get<1>(std::get<1>(std::get<1>(devices[d])[e])[c]).size(); }
virtual std::string GetItemText(int i)
{
auto & attribute = std::get<1>(std::get<1>(std::get<1>(devices[d])[e])[c])[i];
auto & name = std::get<0>(attribute);
auto & value = std::get<1>(attribute);
char buffer[64];
snprintf(buffer, sizeof(buffer), "%s : %s", name.c_str(), value.c_str());
return buffer;
}
virtual void ItemAction(int i)
{
ESP_LOGI(TAG, "Opening attribute %d", i);
ScreenManager::PushScreen(new ListScreen(new EditAttributeListModel(d, e, c, i)));
}
};
class ClusterListModel : public ListScreen::Model
{
int d;
int e;
public:
ClusterListModel(int d, int e) : d(d), e(e) {}
virtual std::string GetTitle() { return "Clusters"; }
virtual int GetItemCount() { return std::get<1>(std::get<1>(devices[d])[e]).size(); }
virtual std::string GetItemText(int i) { return std::get<0>(std::get<1>(std::get<1>(devices[d])[e])[i]); }
virtual void ItemAction(int i)
{
ESP_LOGI(TAG, "Opening cluster %d", i);
ScreenManager::PushScreen(new ListScreen(new AttributeListModel(d, e, i)));
}
};
class EndpointListModel : public ListScreen::Model
{
int d;
public:
EndpointListModel(int d) : d(d) {}
virtual std::string GetTitle() { return "Endpoints"; }
virtual int GetItemCount() { return std::get<1>(devices[d]).size(); }
virtual std::string GetItemText(int i) { return std::get<0>(std::get<1>(devices[d])[i]); }
virtual void ItemAction(int i)
{
ESP_LOGI(TAG, "Opening endpoint %d", i);
ScreenManager::PushScreen(new ListScreen(new ClusterListModel(d, i)));
}
};
class DeviceListModel : public ListScreen::Model
{
public:
virtual std::string GetTitle() { return "Devices"; }
virtual int GetItemCount() { return devices.size(); }
virtual std::string GetItemText(int i) { return std::get<0>(devices[i]); }
virtual void ItemAction(int i)
{
ESP_LOGI(TAG, "Opening device %d", i);
ScreenManager::PushScreen(new ListScreen(new EndpointListModel(i)));
}
};
class SetupListModel : public ListScreen::Model
{
public:
SetupListModel()
{
std::string resetWiFi = "Reset WiFi";
options.emplace_back(resetWiFi);
}
virtual std::string GetTitle() { return "Setup"; }
virtual int GetItemCount() { return options.size(); }
virtual std::string GetItemText(int i) { return options.at(i); }
virtual void ItemAction(int i)
{
ESP_LOGI(TAG, "Opening options %d: %s", i, GetItemText(i).c_str());
if (i == 0)
{
ConnectivityMgr().ClearWiFiStationProvision();
}
}
private:
std::vector<std::string> options;
};
class CustomScreen : public Screen
{
public:
virtual void Display()
{
TFT_drawCircle(0.3 * DisplayWidth, 0.3 * DisplayHeight, 8, TFT_BLUE);
TFT_drawCircle(0.7 * DisplayWidth, 0.3 * DisplayHeight, 8, TFT_BLUE);
TFT_drawLine(0.2 * DisplayWidth, 0.6 * DisplayHeight, 0.3 * DisplayWidth, 0.7 * DisplayHeight, TFT_BLUE);
TFT_drawLine(0.3 * DisplayWidth, 0.7 * DisplayHeight, 0.7 * DisplayWidth, 0.7 * DisplayHeight, TFT_BLUE);
TFT_drawLine(0.7 * DisplayWidth, 0.7 * DisplayHeight, 0.8 * DisplayWidth, 0.6 * DisplayHeight, TFT_BLUE);
}
};
#endif // CONFIG_HAVE_DISPLAY
void SetupPretendDevices()
{
AddDevice("Watch");
AddEndpoint("Default");
AddCluster("Battery");
AddAttribute("Level", "89");
AddAttribute("Voltage", "490");
AddAttribute("Amperage", "501");
AddCluster("Heart Monitor");
AddAttribute("BPM", "72");
AddCluster("Step Counter");
AddAttribute("Steps", "9876");
AddDevice("Thermometer");
AddEndpoint("External");
AddCluster("Thermometer");
AddAttribute("Temperature", "21");
AddEndpoint("Internal");
AddCluster("Thermometer");
AddAttribute("Temperature", "42");
AddDevice("Garage 1");
AddEndpoint("Door 1");
AddCluster("Door");
AddAttribute("State", "Closed");
AddEndpoint("Door 2");
AddCluster("Door");
AddAttribute("State", "Closed");
AddEndpoint("Door 3");
AddCluster("Door");
AddAttribute("State", "Open");
AddDevice("Garage 2");
AddEndpoint("Door 1");
AddCluster("Door");
AddAttribute("State", "Open");
AddEndpoint("Door 2");
AddCluster("Door");
AddAttribute("State", "Closed");
}
void GetGatewayIP(char * ip_buf, size_t ip_len)
{
esp_netif_ip_info_t ipInfo;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ipInfo);
esp_ip4addr_ntoa(&ipInfo.ip, ip_buf, ip_len);
ESP_LOGI(TAG, "Got gateway ip %s", ip_buf);
}
bool isRendezvousBLE()
{
return static_cast<RendezvousInformationFlags>(CONFIG_RENDEZVOUS_MODE) == RendezvousInformationFlags::kBLE;
}
bool isRendezvousBypassed()
{
return static_cast<RendezvousInformationFlags>(CONFIG_RENDEZVOUS_MODE) == RendezvousInformationFlags::kNone;
}
std::string createSetupPayload()
{
CHIP_ERROR err = CHIP_NO_ERROR;
std::string result;
uint16_t discriminator;
err = ConfigurationMgr().GetSetupDiscriminator(discriminator);
if (err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "Couldn't get discriminator: %d", err);
return result;
}
uint32_t setupPINCode;
err = ConfigurationMgr().GetSetupPinCode(setupPINCode);
if (err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "Couldn't get setupPINCode: %d", err);
return result;
}
SetupPayload payload;
payload.version = 1;
payload.discriminator = discriminator;
payload.setUpPINCode = setupPINCode;
payload.rendezvousInformation = static_cast<RendezvousInformationFlags>(CONFIG_RENDEZVOUS_MODE);
payload.vendorID = EXAMPLE_VENDOR_ID;
payload.productID = EXAMPLE_PRODUCT_ID;
if (!isRendezvousBLE())
{
char gw_ip[INET6_ADDRSTRLEN];
GetGatewayIP(gw_ip, sizeof(gw_ip));
payload.addOptionalVendorData(EXAMPLE_VENDOR_TAG_IP, gw_ip);
QRCodeSetupPayloadGenerator generator(payload);
size_t tlvDataLen = sizeof(gw_ip);
uint8_t tlvDataStart[tlvDataLen];
err = generator.payloadBase41Representation(result, tlvDataStart, tlvDataLen);
}
else
{
QRCodeSetupPayloadGenerator generator(payload);
err = generator.payloadBase41Representation(result);
}
if (err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "Couldn't get payload string %d", err);
}
return result;
};
static SecurePairingUsingTestSecret gTestPairing;
} // namespace
extern "C" void app_main()
{
ESP_LOGI(TAG, "WiFi Echo Demo!");
/* Print chip information */
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
ESP_LOGI(TAG, "This is ESP32 chip with %d CPU cores, WiFi%s%s, ", chip_info.cores,
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
ESP_LOGI(TAG, "silicon revision %d, ", chip_info.revision);
ESP_LOGI(TAG, "%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
CHIP_ERROR err; // A quick note about errors: CHIP adopts the error type and numbering
// convention of the environment into which it is ported. Thus esp_err_t
// and CHIP_ERROR are in fact the same type, and both ESP-IDF errors
// and CHIO-specific errors can be stored in the same value without
// ambiguity. For convenience, ESP_OK and CHIP_NO_ERROR are mapped
// to the same value.
// Initialize the ESP NVS layer.
err = nvs_flash_init();
if (err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "nvs_flash_init() failed: %s", ErrorStr(err));
return;
}
CHIPDeviceManager & deviceMgr = CHIPDeviceManager::GetInstance();
err = deviceMgr.Init(&EchoCallbacks);
if (err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "device.Init() failed: %s", ErrorStr(err));
return;
}
SetupPretendDevices();
statusLED1.Init(STATUS_LED_GPIO_NUM);
// Our second LED doesn't map to any physical LEDs so far, just to virtual
// "LED"s on devices with screens.
statusLED2.Init(GPIO_NUM_MAX);
bluetoothLED.Init();
wifiLED.Init();
// Start the Echo Server
InitDataModelHandler();
startServer();
if (isRendezvousBLE())
{
uint32_t setupPINCode;
err = ConfigurationMgr().GetSetupPinCode(setupPINCode);
if (err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "GetSetupPinCode() failed: %s", ErrorStr(err));
return;
}
rendezvousSession = new RendezvousSession(&bluetoothLED, setupPINCode, kLocalNodeId);
}
else if (isRendezvousBypassed())
{
ChipLogProgress(Ble, "Rendezvous and Secure Pairing skipped. Using test secret.");
PairingComplete(Optional<NodeId>::Value(kUndefinedNodeId), 0, 0, &gTestPairing);
}
#if CONFIG_USE_ECHO_CLIENT
startClient();
#endif
std::string qrCodeText = createSetupPayload();
ESP_LOGI(TAG, "QR CODE: '%s'", qrCodeText.c_str());
#if CONFIG_HAVE_DISPLAY
// Initialize the buttons.
for (int i = 0; i < buttons.size(); ++i)
{
err = buttons[i].Init(button_gpios[i], 50);
if (err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "Button.Init() failed: %s", ErrorStr(err));
return;
}
}
// Initialize the display device.
err = InitDisplay();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "InitDisplay() failed: %s", ErrorStr(err));
return;
}
// Initialize the screen manager and push a rudimentary user interface.
ScreenManager::Init();
ScreenManager::PushScreen(new ListScreen((new SimpleListModel())
->Title("CHIP")
->Action([](int i) { ESP_LOGI(TAG, "action on item %d", i); })
->Item("Devices",
[]() {
ESP_LOGI(TAG, "Opening device list");
ScreenManager::PushScreen(new ListScreen(new DeviceListModel()));
})
->Item("Custom",
[]() {
ESP_LOGI(TAG, "Opening custom screen");
ScreenManager::PushScreen(new CustomScreen());
})
->Item("QR Code",
[=]() {
ESP_LOGI(TAG, "Opening QR code screen");
ScreenManager::PushScreen(new QRCodeScreen(qrCodeText));
})
->Item("Setup",
[=]() {
ESP_LOGI(TAG, "Opening Setup list");
ScreenManager::PushScreen(new ListScreen(new SetupListModel()));
})
->Item("More")
->Item("Items")
->Item("For")
->Item("Demo")));
// Connect the status LED to VLEDs.
{
int vled1 = ScreenManager::AddVLED(TFT_GREEN);
int vled2 = ScreenManager::AddVLED(TFT_RED);
statusLED1.SetVLED(vled1, vled2);
int vled3 = ScreenManager::AddVLED(TFT_CYAN);
int vled4 = ScreenManager::AddVLED(TFT_ORANGE);
statusLED2.SetVLED(vled3, vled4);
bluetoothLED.SetVLED(ScreenManager::AddVLED(TFT_BLUE));
wifiLED.SetVLED(ScreenManager::AddVLED(TFT_YELLOW));
}
#endif // CONFIG_HAVE_DISPLAY
// Run the UI Loop
while (true)
{
#if CONFIG_HAVE_DISPLAY
// TODO consider refactoring this example to use FreeRTOS tasks
bool woken = false;
// Poll buttons, possibly wake screen.
for (int i = 0; i < buttons.size(); ++i)
{
if (buttons[i].Poll())
{
if (!woken)
{
woken = WakeDisplay();
}
if (woken)
{
continue;
}
if (buttons[i].IsPressed())
{
ScreenManager::ButtonPressed(1 + i);
}
}
}
#endif // CONFIG_HAVE_DISPLAY
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}