diff --git a/samples/api-client/manager/manager.py b/samples/api-client/manager/manager.py index dbb81db1..0bcd63ab 100644 --- a/samples/api-client/manager/manager.py +++ b/samples/api-client/manager/manager.py @@ -490,6 +490,28 @@ def set_iam_permissions( # [END iot_set_iam_policy] +def send_command( + service_account_json, project_id, cloud_region, registry_id, device_id, + command): + """Send a command to a device.""" + # [START iot_send_command] + print('Sending command to device') + client = get_client(service_account_json) + device_path = 'projects/{}/locations/{}/registries/{}/devices/{}'.format( + project_id, cloud_region, registry_id, device_id) + + config_body = { + 'binaryData': base64.urlsafe_b64encode( + command.encode('utf-8')).decode('ascii') + } + + return client.projects( + ).locations().registries( + ).devices().sendCommandToDevice( + name=device_path, body=config_body).execute() + # [END iot_send_command] + + def parse_command_line_args(): """Parse command line arguments.""" default_registry = 'cloudiot_device_manager_example_registry_{}'.format( @@ -546,6 +568,10 @@ def parse_command_line_args(): '--role', default=None, help='Role used for IAM commands.') + parser.add_argument( + '--send_command', + default='1', + help='The command sent to the device') # Command subparser command = parser.add_subparsers(dest='command') @@ -566,6 +592,7 @@ def parse_command_line_args(): command.add_parser('list-registries', help=list_registries.__doc__) command.add_parser('patch-es256', help=patch_es256_auth.__doc__) command.add_parser('patch-rs256', help=patch_rsa256_auth.__doc__) + command.add_parser('send-command', help=send_command.__doc__) command.add_parser('set-config', help=patch_rsa256_auth.__doc__) command.add_parser('set-iam-permissions', help=set_iam_permissions.__doc__) @@ -679,6 +706,12 @@ def run_command(args): args.cloud_region, args.registry_id, args.device_id, args.rsa_certificate_file) + elif args.command == 'send-command': + send_command( + args.service_account_json, args.project_id, + args.cloud_region, args.registry_id, args.device_id, + args.send_command) + elif args.command == 'set-iam-permissions': if (args.member is None): sys.exit('Error: specify --member') @@ -699,10 +732,6 @@ def run_command(args): args.version, args.config) -def main(): +if __name__ == '__main__': args = parse_command_line_args() run_command(args) - - -if __name__ == '__main__': - main() diff --git a/samples/api-client/manager/manager_test.py b/samples/api-client/manager/manager_test.py index 142e60d1..fb115dec 100644 --- a/samples/api-client/manager/manager_test.py +++ b/samples/api-client/manager/manager_test.py @@ -13,17 +13,23 @@ # limitations under the License. import os +import sys import time +# Add command receiver for bootstrapping device registry / device for testing +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'mqtt_example')) # noqa from google.cloud import pubsub import pytest import manager +import cloudiot_mqtt_example cloud_region = 'us-central1' device_id_template = 'test-device-{}' +ca_cert_path = '../mqtt_example/resources/roots.pem' es_cert_path = 'resources/ec_public.pem' rsa_cert_path = 'resources/rsa_cert.pem' +rsa_private_path = 'resources/rsa_private.pem' # Must match rsa_cert topic_id = 'test-device-events-{}'.format(int(time.time())) project_id = os.environ['GCLOUD_PROJECT'] @@ -270,3 +276,46 @@ def test_add_patch_delete_es256(test_topic, capsys): manager.delete_registry( service_account_json, project_id, cloud_region, registry_id) + + +def test_send_command(test_topic, capsys): + device_id = device_id_template.format('RSA256') + manager.create_registry( + service_account_json, project_id, cloud_region, pubsub_topic, + registry_id) + manager.create_rs256_device( + service_account_json, project_id, cloud_region, registry_id, + device_id, rsa_cert_path) + + # Exercize the functionality + client = cloudiot_mqtt_example.get_client( + project_id, cloud_region, registry_id, device_id, + rsa_private_path, 'RS256', ca_cert_path, + 'mqtt.googleapis.com', 443) + client.loop_start() + out, _ = capsys.readouterr() + + # Pre-process commands + for i in range(1, 5): + client.loop() + time.sleep(1) + + manager.send_command( + service_account_json, project_id, cloud_region, registry_id, + device_id, 'me want cookies') + out, _ = capsys.readouterr() + + # Process commands + for i in range(1, 5): + client.loop() + time.sleep(1) + + # Clean up + manager.delete_device( + service_account_json, project_id, cloud_region, registry_id, + device_id) + manager.delete_registry( + service_account_json, project_id, cloud_region, registry_id) + + assert 'Sending command to device' in out + assert '400' not in out diff --git a/samples/api-client/manager/requirements.txt b/samples/api-client/manager/requirements.txt index 5a3fcc02..44105208 100644 --- a/samples/api-client/manager/requirements.txt +++ b/samples/api-client/manager/requirements.txt @@ -1,4 +1,8 @@ -google-api-python-client==1.7.4 +cryptography==2.4.2 +google-api-python-client==1.7.5 google-auth-httplib2==0.0.3 google-auth==1.6.1 -google-cloud-pubsub==0.38.0 +google-cloud-pubsub==0.39.0 +oauth2client==4.1.3 +paho-mqtt==1.4.0 +pyjwt==1.6.4 diff --git a/samples/api-client/manager/resources/rsa_cert.pem b/samples/api-client/manager/resources/rsa_cert.pem index f237f787..f0c79f9c 100644 --- a/samples/api-client/manager/resources/rsa_cert.pem +++ b/samples/api-client/manager/resources/rsa_cert.pem @@ -1,19 +1,18 @@ -----BEGIN CERTIFICATE----- -MIIDFzCCAf+gAwIBAgIJALsqlqk6FkVjMA0GCSqGSIb3DQEBBQUAMBExDzANBgNV -BAMTBnVudXNlZDAeFw0xNzEyMDYyMjQwNDRaFw0yNzEyMDQyMjQwNDRaMBExDzAN -BgNVBAMTBnVudXNlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZg -rgM97eLsTl3ul4m07pVHE/g9f0VP6/MDQcAjqj9kRmg5XMo/E6eRbNcIVwKEGHwC -SuSvGO/j7reYN4cbjvBqimc8asecg5rtXjaDBm66feB/ktOjASSfGfuN79JOJf+r -/BNcCrs8qWa1FSPEnQO7VTTUstkDKB8uvBFPOiPNVw438KM2lId2G/i63soMes0m -9RjPdXbRqJI39WdMVDYXIqLSVIX11xOXvroetwDPkHIi4Tjzus1T6KMjwKD3f3sm -DeUJeu4f1ZV4LPhhW62O9fkKeGy55LglfqOmWusySdOoNgEocN4V3iDQmJ7QOEXw -yPX+ZhhPHuptDwBTWJcCAwEAAaNyMHAwHQYDVR0OBBYEFD7D1XyWbYOiMWDgDyBs -po+JmlViMEEGA1UdIwQ6MDiAFD7D1XyWbYOiMWDgDyBspo+JmlVioRWkEzARMQ8w -DQYDVQQDEwZ1bnVzZWSCCQC7KpapOhZFYzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 -DQEBBQUAA4IBAQAlq4QHt5yqCBhhLsMNnTDmg6ev7lxXKnb+JRMm3Op0rgBDe4sQ -U3Hxrhma+55f2rx32kwpcMQr4WWp0tUKL0OpWrNqdJc4oGFftLDqzKxXyT1nN5PH -1p9HCkHHwxmGZ60fxKb64yu9PTgsZS64l21CWlNFEiC8IULa5HV/O0ZZdPuAZVv0 -hzmkalP/7uPqJ+z2tBEADQOjrU7SiB8uM+q1BLN9vnZrjo7CsXNwIiQ8N8eXpzsX -did5acwQdAt67uNx9e/YXLMvuAn+qVN+crS4IliBiikMZiUkug5yBDsLySFwXsZg -MGkUkV28QlgT4wzSQJ52yGtYCauqO2qU4zZg +MIIC+DCCAeCgAwIBAgIJALt7HnuYGgVcMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV +BAMMBnVudXNlZDAeFw0xODEyMDMwMDE2MjNaFw0yODExMzAwMDE2MjNaMBExDzAN +BgNVBAMMBnVudXNlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOrA +b+5XMIgwyl6+CkdJkKKp4f+2hHAOoSqQroj2MR/i41YysUbKCk8KdQSZqTBtIvrY +df7s6zDV3zmi3LBBHX6MGnz/1j5YIXlujBhsnQTRbTuUjjDx96ik2C2rR1w7okA7 +MmBsdVzRe8g5pfNQGdV0l15UaZK+qFlLg0xzasPPKmYFFUbKXeWPaKJmtw9d/l6a +8jb87fwI3LTWjZr6Bk7L4Zf1TDlTDroMvNlkW9Z9xSkcgC77EjMtC7RYxxbelaxd +qI9IdxIpmExg6pKMJEvJNA11GYhTlAxkJNh7gd0WlYvQlwDI7D6NJPbCmm4ac1P2 +AA1MFHVgxoKAFk/8V38CAwEAAaNTMFEwHQYDVR0OBBYEFC5Dlz2bTWzaJO6i1Qn8 +lTdDAigCMB8GA1UdIwQYMBaAFC5Dlz2bTWzaJO6i1Qn8lTdDAigCMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBANmnnBQaWPhJne5kOjMe+SEsdLbG +OD9L8RmokOsRPXJbj2KoM07UUXMgUe57daYm72wDfKZpvG5qwybkkym+NnahJY+C +u9FX1dTBjM/TqPWKI817mDp5W31a+q6DXdggG+Yf6pz0dMXGGzRtSTEpLKsKtXqe +Y9f7266JCx9V5QFK14SmpIdBF38G0bcNPEvRJ6uKaVKBnU4+7o4YlQLuDczT29Tp +CXL0egUViNT9kv03Pj9iSPR6EGcmOjnSZe1SFVg9OeCauF1wuuFCxUuCkWSEkEm7 +laNke9PHHTe9BoBxKMsTFEPivhVaAf9fUp+NQxZNmdgbux9AAlJgiyU0sFg= -----END CERTIFICATE----- diff --git a/samples/api-client/manager/resources/rsa_private.pem b/samples/api-client/manager/resources/rsa_private.pem new file mode 100644 index 00000000..5eaf0240 --- /dev/null +++ b/samples/api-client/manager/resources/rsa_private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqwG/uVzCIMMpe +vgpHSZCiqeH/toRwDqEqkK6I9jEf4uNWMrFGygpPCnUEmakwbSL62HX+7Osw1d85 +otywQR1+jBp8/9Y+WCF5bowYbJ0E0W07lI4w8feopNgtq0dcO6JAOzJgbHVc0XvI +OaXzUBnVdJdeVGmSvqhZS4NMc2rDzypmBRVGyl3lj2iiZrcPXf5emvI2/O38CNy0 +1o2a+gZOy+GX9Uw5Uw66DLzZZFvWfcUpHIAu+xIzLQu0WMcW3pWsXaiPSHcSKZhM +YOqSjCRLyTQNdRmIU5QMZCTYe4HdFpWL0JcAyOw+jST2wppuGnNT9gANTBR1YMaC +gBZP/Fd/AgMBAAECggEBAOevf4kColJ3nPM+qlRLJaV09yjyUOlrduLUon1oRXmL +6wUCyPXtp5j04CLnKRUzUUezZVlxKHotSr/OnfKSgXKJAgeGVEN5paf8U+YzJBFC +RIV+C4wA84WNFBKWrbo43Nx50DFcOcSet4UYaFGoJ6cFB/PAaeW7p9lhbreAXcnb +g2z4SBsGGO2ZKZkSOcXg39GKX5S0bsEhHzIOFvPBdDQpaVAx/Bq76iBzVz1bRwGp +A3SjJn0g4V+3TcTrpRzgtCRRNPM48JeLHw6/mdVSk6gIoEbg+WTB92Z7BlTbjqhI +LPoMx3wQkg7J2gRrT0rPJIMkgdK9Q8RyWaulsRAn+gECgYEA/jfisAhn7IAPBXKz +ED56NSr174qX97SO2JrB7PO614BV8lHSoV4QI+nQCBkNVhL/SuWvqB3naImo+UOX +Smo6fmh78X+yIXKKhj9qY01jxRGcrnhA25L9gk7TFSNm7XaV2HxHq+TW6tvkL0uO +jXSH3D+u8/f+5ZCt/egNq3eU4n8CgYEA7GWgBLq8jmiz/VVLYboJ0YcJVZ5XAjfJ +vgzxdzX9hIkq5Cpt1ZpO9zE3IXClVvfECXcLpEzoHbiJkEve4dDqbSe4T4JPmpRx +BKRPWFJvTieLA912UmHRBDTrQSA/nZ5zxHsXYmN52femqCwbPWYu5czoqud+GcNq +ghj4oF9UCwECgYEAzpaHx1ntakne6yR807SSB2b0GUfdm1TFyMxqz655pesK7TMF +IlGYeDbn8cy6A7rIcAsbplk21sMX6Ai/h5+wDU3He0e0cG3umI4sXKpla56WX0om +Gsnm7eA0tTbhzBPUTeshK1V6Ob2cP7r9C4MpbRjriiN8pv3eBzpu8WrqOO0CgYAB +8bgGMe75EN1iGQB8tkX8ZirqfFnk18ad/IdD3rrOCz7CD6NFnXZGzC3S5ZVGiNUg +6sy6tjM2g10GRcl4e/phmXEHnl+/OrdPPXa1mD/4GZUoG/ssJEfOzAyfRX+gcTws +goKnuX+4DjRdr7ctoxiBpVTIiwzbc2L93Oy2jPIpAQKBgGzaYhfOlB3SWo+iSKcb +Vx++0gXru1Sgeo42TIn+6adO6DYfwVtfaScVL+Jcg7MZYY+94gnPCp+/ohVTJTvQ +JZdI9Czem64VUtSvc0DCji5gPqvgsO5YgBQrGJJrbmVwu0A6RlNbDwxCdkqHuYk4 +7vsxlbXvNufb0LjnNN0lSUU2 +-----END PRIVATE KEY-----