| /* |
| * Copyright (c) 2020 Project CHIP Authors |
| * All rights reserved. |
| * |
| * 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 <assert.h> |
| #include <errno.h> |
| #include <iostream> |
| #include <new> |
| #include <sstream> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <core/CHIPError.h> |
| #include <inet/InetLayer.h> |
| #include <inet/UDPEndPoint.h> |
| #include <support/CHIPLogging.h> |
| #include <support/CodeUtils.h> |
| #include <support/ErrorStr.h> |
| |
| #include <controller/CHIPDeviceController.h> |
| |
| extern "C" { |
| #include "chip-zcl/chip-zcl.h" |
| } // extern "C" |
| |
| #include "chip-zcl/chip-zcl-zpro-codec.h" |
| |
| // Delay, in seconds, between sends for the echo case. |
| #define SEND_DELAY 5 |
| |
| using namespace ::chip; |
| using namespace ::chip::Inet; |
| |
| // NOTE: Remote device ID is in sync with the echo server device id |
| // At some point, we may want to add an option to connect to a device without |
| // knowing its id, because the ID can be learned on the first response that is received. |
| constexpr NodeId kLocalDeviceId = 112233; |
| constexpr NodeId kRemoteDeviceId = 12344321; |
| |
| static const unsigned char local_private_key[] = { 0x00, 0xd1, 0x90, 0xd9, 0xb3, 0x95, 0x1c, 0x5f, 0xa4, 0xe7, 0x47, |
| 0x92, 0x5b, 0x0a, 0xa9, 0xa7, 0xc1, 0x1c, 0xe7, 0x06, 0x10, 0xe2, |
| 0xdd, 0x16, 0x41, 0x52, 0x55, 0xb7, 0xb8, 0x80, 0x8d, 0x87, 0xa1 }; |
| |
| static const unsigned char remote_public_key[] = { 0x04, 0xe2, 0x07, 0x64, 0xff, 0x6f, 0x6a, 0x91, 0xd9, 0xc2, 0xc3, 0x0a, 0xc4, |
| 0x3c, 0x56, 0x4b, 0x42, 0x8a, 0xf3, 0xb4, 0x49, 0x29, 0x39, 0x95, 0xa2, 0xf7, |
| 0x02, 0x8c, 0xa5, 0xce, 0xf3, 0xc9, 0xca, 0x24, 0xc5, 0xd4, 0x5c, 0x60, 0x79, |
| 0x48, 0x30, 0x3c, 0x53, 0x86, 0xd9, 0x23, 0xe6, 0x61, 0x1f, 0x5a, 0x3d, 0xdf, |
| 0x9f, 0xdc, 0x35, 0xea, 0xd0, 0xde, 0x16, 0x7e, 0x64, 0xde, 0x7f, 0x3c, 0xa6 }; |
| |
| static const char * PAYLOAD = "Message from Standalone CHIP echo client!"; |
| |
| static void EchoKeyExchange(chip::DeviceController::ChipDeviceController * controller, Transport::PeerConnectionState * state, |
| void * appReqState) |
| { |
| CHIP_ERROR err = controller->ManualKeyExchange(state, remote_public_key, sizeof(remote_public_key), local_private_key, |
| sizeof(local_private_key)); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| fprintf(stderr, "Failed to exchange keys\n"); |
| } |
| } |
| |
| // Device Manager Callbacks |
| static void EchoResponse(chip::DeviceController::ChipDeviceController * deviceController, void * appReqState, |
| System::PacketBuffer * buffer) |
| { |
| size_t data_len = buffer->DataLength(); |
| |
| printf("UDP packet received: %zu bytes\n", static_cast<size_t>(buffer->DataLength())); |
| |
| // attempt to print the incoming message |
| char msg_buffer[data_len]; |
| msg_buffer[data_len] = 0; // Null-terminate whatever we received and treat like a string... |
| memcpy(msg_buffer, buffer->Start(), data_len); |
| int compare = strncmp(msg_buffer, PAYLOAD, data_len); |
| if (compare == 0) |
| { |
| printf("Got expected Message...\n"); |
| } |
| else |
| { |
| printf("Didn't get the expected Echo. Compare: %d\n", compare); |
| printf("\nSend: %s \nRecv: %s\n", PAYLOAD, msg_buffer); |
| } |
| |
| System::PacketBuffer::Free(buffer); |
| } |
| |
| static void ReceiveError(chip::DeviceController::ChipDeviceController * deviceController, void * appReqState, CHIP_ERROR error, |
| const IPPacketInfo * pi) |
| { |
| printf("ERROR: %s\n Got UDP error\n", ErrorStr(error)); |
| } |
| |
| void ShowUsage(const char * executable) |
| { |
| fprintf(stderr, |
| "Usage: \n" |
| " %s device-ip-address device-port command [params]\n" |
| " Supported commands and their parameters:\n" |
| " echo\n" |
| " off endpoint-id\n" |
| " on endpoint-id\n" |
| " toggle endpoint-id\n", |
| executable); |
| } |
| |
| bool DetermineAddress(int argc, char * argv[], IPAddress * hostAddr, uint16_t * port) |
| { |
| if (argc < 3) |
| { |
| return false; |
| } |
| |
| if (!IPAddress::FromString(argv[1], *hostAddr)) |
| { |
| fputs("Error: Invalid device IP address", stderr); |
| return false; |
| } |
| |
| std::string port_str(argv[2]); |
| std::stringstream ss(port_str); |
| ss >> *port; |
| if (ss.fail() || !ss.eof()) |
| { |
| fputs("Error: Invalid device port", stderr); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| enum class Command |
| { |
| Off, |
| On, |
| Toggle, |
| Echo, |
| }; |
| |
| template <int N> |
| bool EqualsLiteral(const char * str, const char (&literal)[N]) |
| { |
| return strncmp(str, literal, N) == 0; |
| } |
| |
| bool DetermineCommand(int argc, char * argv[], Command * command) |
| { |
| if (argc < 4) |
| { |
| return false; |
| } |
| |
| if (EqualsLiteral(argv[3], "off")) |
| { |
| *command = Command::Off; |
| return true; |
| } |
| |
| if (EqualsLiteral(argv[3], "on")) |
| { |
| *command = Command::On; |
| return true; |
| } |
| |
| if (EqualsLiteral(argv[3], "toggle")) |
| { |
| *command = Command::Toggle; |
| return true; |
| } |
| |
| if (EqualsLiteral(argv[3], "echo")) |
| { |
| *command = Command::Echo; |
| return true; |
| } |
| |
| fprintf(stderr, "Unknown command: %s\n", argv[3]); |
| return false; |
| } |
| |
| union CommandArgs |
| { |
| uint8_t endpointId; |
| }; |
| |
| bool DetermineCommandArgs(int argc, char * argv[], Command command, CommandArgs * commandArgs) |
| { |
| if (command == Command::Echo) |
| { |
| // No args. |
| return true; |
| } |
| |
| if (command != Command::On && command != Command::Off && command != Command::Toggle) |
| { |
| fprintf(stderr, "Need to define arg handling for command '%d'\n", int(command)); |
| return false; |
| } |
| |
| if (argc < 5) |
| { |
| return false; |
| } |
| |
| std::string endpoint_str(argv[4]); |
| std::stringstream ss(endpoint_str); |
| // stringstream treats uint8_t as char, which is not what we want here. |
| uint16_t endpoint; |
| ss >> endpoint; |
| if (ss.fail() || !ss.eof() || endpoint < CHIP_ZCL_ENDPOINT_MIN || endpoint > CHIP_ZCL_ENDPOINT_MAX) |
| { |
| fprintf(stderr, "Error: Invalid endpoint id '%s'\n", argv[4]); |
| return false; |
| } |
| commandArgs->endpointId = endpoint; |
| |
| return true; |
| } |
| |
| // Handle the echo case, where we just send a string and expect to get it back. |
| void DoEcho(DeviceController::ChipDeviceController * controller, const IPAddress & host_addr, uint16_t port) |
| { |
| size_t payload_len = strlen(PAYLOAD); |
| |
| // Run the client |
| char host_ip_str[40]; |
| host_addr.ToString(host_ip_str, sizeof(host_ip_str)); |
| while (1) |
| { |
| // Reallocate buffer on each run, as the secure transport encrypts and |
| // overwrites the buffer from previous iteration. |
| auto * buffer = System::PacketBuffer::NewWithAvailableSize(payload_len); |
| memcpy(buffer->Start(), PAYLOAD, payload_len); |
| buffer->SetDataLength(payload_len); |
| |
| controller->SendMessage(NULL, buffer); |
| printf("Msg sent to server at %s:%d\n", host_ip_str, port); |
| |
| controller->ServiceEvents(); |
| |
| sleep(SEND_DELAY); |
| } |
| } |
| |
| // Handle the on/off/toggle case, where we are sending a ZCL command and not |
| // expecting a response at all. |
| void DoOnOff(DeviceController::ChipDeviceController * controller, Command command, uint8_t endpoint) |
| { |
| // Make sure our buffer is big enough, but this will need a better setup! |
| static const size_t bufferSize = 1024; |
| auto * buffer = System::PacketBuffer::NewWithAvailableSize(bufferSize); |
| |
| uint16_t dataLength = 0; |
| switch (command) |
| { |
| case Command::Off: |
| dataLength = encodeOffCommand(buffer->Start(), bufferSize, endpoint); |
| break; |
| case Command::On: |
| dataLength = encodeOnCommand(buffer->Start(), bufferSize, endpoint); |
| break; |
| case Command::Toggle: |
| dataLength = encodeToggleCommand(buffer->Start(), bufferSize, endpoint); |
| break; |
| default: |
| fprintf(stderr, "Unknown command: %d\n", command); |
| return; |
| } |
| buffer->SetDataLength(dataLength); |
| |
| #ifdef DEBUG |
| const size_t data_len = buffer->DataLength(); |
| |
| fprintf(stderr, "SENDING: %zu ", data_len); |
| for (size_t i = 0; i < data_len; ++i) |
| { |
| fprintf(stderr, "%d ", buffer->Start()[i]); |
| } |
| fprintf(stderr, "\n"); |
| #endif |
| |
| controller->SendMessage(NULL, buffer); |
| controller->ServiceEvents(); |
| } |
| |
| // ================================================================================ |
| // Main Code |
| // ================================================================================ |
| int main(int argc, char * argv[]) |
| { |
| IPAddress host_addr; |
| uint16_t port; |
| Command command; |
| CHIP_ERROR err; |
| CommandArgs commandArgs; |
| if (!DetermineAddress(argc, argv, &host_addr, &port) || !DetermineCommand(argc, argv, &command) || |
| !DetermineCommandArgs(argc, argv, command, &commandArgs)) |
| { |
| ShowUsage(argv[0]); |
| return -1; |
| } |
| |
| auto * controller = new DeviceController::ChipDeviceController(); |
| err = controller->Init(kLocalDeviceId); |
| VerifyOrExit(err == CHIP_NO_ERROR, fprintf(stderr, "Failed to initialize the device controller")); |
| |
| err = controller->ConnectDevice(kRemoteDeviceId, host_addr, NULL, EchoKeyExchange, EchoResponse, ReceiveError, port); |
| VerifyOrExit(err == CHIP_NO_ERROR, fprintf(stderr, "Failed to connect to the device")); |
| |
| if (command == Command::Echo) |
| { |
| DoEcho(controller, host_addr, port); |
| } |
| else |
| { |
| DoOnOff(controller, command, commandArgs.endpointId); |
| } |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| fprintf(stderr, "ERROR: %s\n", ErrorStr(err)); |
| } |
| controller->Shutdown(); |
| delete controller; |
| |
| return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |