# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.

# !/usr/bin/env python
import greengrasssdk
import json
import logging
import os
import sys
import time
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import connector_lambda_python_commons.response as PublishResponse
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder

# NOTE: Must specify the serial port the GGC is connected to.
DEFAULT_SERIAL_PORT = '/dev/ttyUSB0'
METHOD = 'rtu'
DEFAULT_TIMEOUT_SECONDS = 1
DEFAULT_TOPIC_RESPONSE = 'modbus/adapter/response'
DEFAULT_BAUDRATE = 115200
UTF_8 = "utf-8"

#extra added ones
PARITY = 'N'
BYTESIZE = 8
STOPBITS = 1

FIELD_FUNCTION_CODE = "function_code"
FIELD_EXCEPTION_CODE = "exception_code"
FIELD_RESPONSE = "response"
FIELD_PAYLOAD = "payload"
FIELD_DEVICE = "device"
FIELD_OPERATION = "operation"
FIELD_ERROR = "error"
FIELD_VALUE = "value"
FIELD_VALUES = "values"
FIELD_ADDRESS = "address"
FIELD_COUNT = "count"
FIELD_READ_ADDRESS = "read_address"
FIELD_READ_COUNT = "read_count"
FIELD_WRITE_ADDRESS = "write_address"
FIELD_WRITE_REGISTERS = "write_registers"
FIELD_AND_MASK = "and_mask"
FIELD_OR_MASK = "or_mask"
FIELD_BITS = "bits"
FIELD_REGISTERS = "registers"
FIELD_REQUEST_ID = "id"
FIELD_REQUEST = "request"
FIELD_NUMBER_OF_PHASES = "number_of_phases"
FIELD_CHANNEL_FOR_FIRST_CT = "channel_for_first_CT"
FIELD_CHANNEL_FOR_SECOND_CT = "channel_for_second_CT"
FIELD_CHANNEL_FOR_THIRD_CT = "channel_for_third_CT"
FIELD_VIRTUAL_CHANNEL = "virtual_channel"
NAME_CURRENT_A = "current_a"
NAME_CURRENT_B = "current_b"
NAME_CURRENT_C = "current_c"

VALUE_REQUEST_ID_DEFAULT = "Missing"
VALUE_OPERATION_DEFAULT = "Missing"
VALUE_DEVICE_DEFAULT = -1

STATUS_NO_RESPONSE = "No Response"
STATUS_EXCEPTION = "Exception"
STATUS_SUCCESS = "Success"

SERIAL_PORT_ENV_VAR = "ModbusLocalPort"

NAMES_SINGLE_PHASE = ['power_total','power_reac_sum','power_app_sum','energy_total_pos','energy_total_neg','energy_reac_sum_import','energy_reac_sum_export']
NAMES_MULTIPHASE_A = ['power_a','power_reac_a','power_app_a','energy_a_pos','energy_a_neg','energy_a_sum_import','energy_reac_a_export']
NAMES_MULTIPHASE_B = ['power_b','power_reac_b','power_app_b','energy_b_pos','energy_b_neg','energy_b_sum_import','energy_reac_b_export']
NAMES_MULTIPHASE_C = ['power_a','power_reac_a','power_app_c','energy_c_pos','energy_c_neg','energy_c_sum_import','energy_reac_c_export']


# Convert string to unicode. In Python 3.x, string is by default unicode.
def convert_unicode(string):
    if sys.version_info[0] < 3:
        return unicode(string, UTF_8)
    else:
        return string


OPERATION_READ_COILS = convert_unicode("ReadCoilsRequest")
OPERATION_READ_INPUT_REGISTERS = convert_unicode("ReadInputRegistersRequest")
OPERATION_READ_DISCRETE_INPUTS = convert_unicode("ReadDiscreteInputsRequest")
OPERATION_READ_HOLDING_REGISTERS = convert_unicode("ReadHoldingRegistersRequest")
OPERATION_READ_ALL_HOLDING_REGISTERS = convert_unicode("ReadAllHoldingRegistersRequest")
OPERATION_WRITE_SINGLE_COIL = convert_unicode("WriteSingleCoilRequest")
OPERATION_WRITE_SINGLE_REGISTER = convert_unicode("WriteSingleRegisterRequest")
OPERATION_WRITE_MULTIPLE_COILS = convert_unicode("WriteMultipleCoilsRequest")
OPERATION_WRITE_MULTIPLE_REGISTERS = convert_unicode("WriteMultipleRegistersRequest")
OPERATION_READ_WRITE_MULTIPLE_REGISTERS = convert_unicode("ReadWriteMultipleRegistersRequest")
OPERATION_MASK_WRITE_REGISTER = convert_unicode("MaskWriteRegisterRequest")


# Run for all read requests
# Read requests require an address and count
def read_request(client, request, op):
    modbus_response = {}

    # Ensure address and count exist, and are both of type int
    error = check_fields(request, [FIELD_ADDRESS, FIELD_COUNT], [int, int])
    if error:
        modbus_response[FIELD_ERROR] = error
        return modbus_response

    address = request[FIELD_ADDRESS]
    count = request[FIELD_COUNT]
    # The device field has been checked earlier
    unit = request[FIELD_DEVICE]

    # Count has to be greater than 1.
    if count < 1:
        modbus_response[FIELD_ERROR] = "Invalid count {}".format(count)
        return modbus_response

    # The current supported read requests.
    if op == OPERATION_READ_COILS:
        modbus_response = client.read_coils(address, count, unit=unit)
    elif op == OPERATION_READ_DISCRETE_INPUTS:
        modbus_response = client.read_discrete_inputs(address, count, unit=unit)
    elif op == OPERATION_READ_HOLDING_REGISTERS:
        modbus_response = client.read_holding_registers(address, count, unit=unit)
    elif op == OPERATION_READ_INPUT_REGISTERS:
        modbus_response = client.read_input_registers(address, count, unit=unit)
    else:
        # Unexpected request
        modbus_response[FIELD_ERROR] = "Unexpected request {}".format(op)

    # Add address to response.
    setattr(modbus_response, FIELD_ADDRESS, address)
    return modbus_response


# Run for read all holding registers request
# Read all holding registers requests require number_of_phases
def read_all_request(client, request, op):
    modbus_response = {}

    # Ensure number_of_phases exists, and is of type int
    error = check_fields(request, [FIELD_NUMBER_OF_PHASES], [int])
    if error:
        modbus_response[FIELD_ERROR] = error
        return modbus_response

    address = request[FIELD_ADDRESS]
    numberOfPhases = request[FIELD_NUMBER_OF_PHASES]
    # The device field has been checked earlier
    unit = request[FIELD_DEVICE]
    virtualChannel = request[FIELD_VIRTUAL_CHANNEL]

    # numberOfPhases has to be greater than 0.
    if numberOfPhases < 1:
        modbus_response[FIELD_ERROR] = "Invalid numberOfPhases {}".format(numberOfPhases)
        return modbus_response

    # numberOfPhases has to be greater than 0.
    if numberOfPhases == 1:
        offsetPhaseA = request[FIELD_CHANNEL_FOR_FIRST_CT]
        getCtInfo(client, unit, offsetPhaseA, modbus_response, NAMES_SINGLE_PHASE)
        getCurrentValues(client, unit, offsetPhaseA, modbus_response, NAME_CURRENT_A)
    elif numberOfPhases == 2:
        offsetPhaseA = request[FIELD_CHANNEL_FOR_FIRST_CT]
        offsetPhaseB = request[FIELD_CHANNEL_FOR_SECOND_CT]
        getVcInfo(client, unit, virtualChannel, modbus_response)
        getCtInfo(client, unit, offsetPhaseA, modbus_response, NAMES_MULTIPHASE_A)
        getCtInfo(client, unit, offsetPhaseB, modbus_response, NAMES_MULTIPHASE_B)
        getCurrentValues(client, unit, offsetPhaseA, modbus_response, NAME_CURRENT_A)
        getCurrentValues(client, unit, offsetPhaseB, modbus_response, NAME_CURRENT_B)
    elif numberOfPhases == 3:
        offsetPhaseA = request[FIELD_CHANNEL_FOR_FIRST_CT]
        offsetPhaseB = request[FIELD_CHANNEL_FOR_SECOND_CT]
        offsetPhaseC = request[FIELD_CHANNEL_FOR_THIRD_CT]
        getVcInfo(client, unit, virtualChannel, modbus_response)
        getCtInfo(client, unit, offsetPhaseA, modbus_response, NAMES_MULTIPHASE_A)
        getCtInfo(client, unit, offsetPhaseB, modbus_response, NAMES_MULTIPHASE_B)
        getCtInfo(client, unit, offsetPhaseC, modbus_response, NAMES_MULTIPHASE_C)
        getCurrentValues(client, unit, offsetPhaseA, modbus_response, NAME_CURRENT_A)
        getCurrentValues(client, unit, offsetPhaseB, modbus_response, NAME_CURRENT_B)
        getCurrentValues(client, unit, offsetPhaseC, modbus_response, NAME_CURRENT_C)
    else:
        # Unexpected request
        modbus_response[FIELD_ERROR] = "Invalid numberOfPhases {}".format(numberOfPhases)

    getVoltageValues(client, unit, modbus_response)
    getFrequencyValue(client, unit, modbus_response)
    return modbus_response


# Run for all write single requests
# Single write requests require an address and value
def write_single_request(client, request, op):
    modbus_response = {}

    error = check_fields(request, [FIELD_ADDRESS, FIELD_VALUE], [int, int])
    if error:
        modbus_response[FIELD_ERROR] = error
        return modbus_response

    address = request[FIELD_ADDRESS]
    value = request[FIELD_VALUE]
    unit = request[FIELD_DEVICE]

    # The current supported write single requests
    if op == OPERATION_WRITE_SINGLE_COIL:
        modbus_response = client.write_coil(address, value, unit=unit)
    elif op == OPERATION_WRITE_SINGLE_REGISTER:
        modbus_response = client.write_register(address, value, unit=unit)
    else:
        # Unexpected request
        modbus_response[FIELD_ERROR] = "Unexpected request {}".format(op)

    return modbus_response


# Run for all write multiple requests
# Multiple write requests require an address and values
def write_multiple_request(client, request, op):
    modbus_response = {}

    error = check_fields(request, [FIELD_ADDRESS, FIELD_VALUES], [int, list])
    if error:
        modbus_response[FIELD_ERROR] = error
        return modbus_response

    values = request[FIELD_VALUES]
    address = request[FIELD_ADDRESS]
    unit = request[FIELD_DEVICE]

    if not all(isinstance(x, int) for x in values):
        modbus_response[FIELD_ERROR] = "Values must be a non-empty list of integers"
        return modbus_response

    # The current supported write multiple requests
    if op == OPERATION_WRITE_MULTIPLE_COILS:
        modbus_response = client.write_coils(address, values, unit=unit)
    elif op == OPERATION_WRITE_MULTIPLE_REGISTERS:
        modbus_response = client.write_registers(address, values, unit=unit)
    else:
        # Unexpected request
        modbus_response[FIELD_ERROR] = "Unexpected request {}".format(op)

    return modbus_response


# Run for readwrite multiple registers
# ReadWrite registers requests require a read address and count, and a write address and registers
def readwrite_multiple_registers_request(client, request, op):
    modbus_response = {}

    error = check_fields(request, [FIELD_READ_ADDRESS, FIELD_READ_COUNT, FIELD_WRITE_ADDRESS, FIELD_WRITE_REGISTERS],
                         [int, int, int, list])
    if error:
        modbus_response[FIELD_ERROR] = error
        return modbus_response

    if not all(isinstance(x, int) for x in request[FIELD_WRITE_REGISTERS]):
        modbus_response[FIELD_ERROR] = "{} must be a non-empty list of integers".format('write_registers')
        return modbus_response
    # This is the way pymodbus takes in these arguments.
    return client.readwrite_registers(**request)


# Run for maskwrite registers
# Maskwrite registers requests require an address, andmask and an ormask
def mask_write_register_request(client, request, op):
    modbus_response = {}

    error = check_fields(request, [FIELD_ADDRESS, FIELD_AND_MASK, FIELD_OR_MASK], [int, int, int])
    if error:
        modbus_response[FIELD_ERROR] = error
        return modbus_response

    return client.mask_write_register(**request)


# Executes the request and returns a json response
def execute(client, request):
    json_response = init_default_json_response(request)

    # First check if the request has the correct basic fields
    error = validate_request(request[FIELD_REQUEST])
    if error:
        json_response[FIELD_RESPONSE][FIELD_PAYLOAD][FIELD_ERROR] = error
    else:
        # At this point, 'json_response' should always have a valid Operation and Device field
        operation = json_response[FIELD_RESPONSE][FIELD_OPERATION]
        json_response[FIELD_RESPONSE][FIELD_PAYLOAD] = function_dict[operation](client, request[FIELD_REQUEST],
                                                                                operation)
        serialize_response(json_response, response_fields_dict[operation])

    return update_response_status(json_response)


# All Responses will contain an operation and device default
def init_default_json_response(request):
    json_response = {
        FIELD_RESPONSE: {
            FIELD_PAYLOAD: {},
            FIELD_OPERATION: VALUE_OPERATION_DEFAULT,
            FIELD_DEVICE: VALUE_DEVICE_DEFAULT
        },
        FIELD_REQUEST_ID: VALUE_REQUEST_ID_DEFAULT
    }

    if type(request) is dict and request.get(FIELD_REQUEST):
        json_response[FIELD_REQUEST_ID] = request.get(FIELD_REQUEST_ID, VALUE_REQUEST_ID_DEFAULT)
        json_response[FIELD_RESPONSE][FIELD_OPERATION] = request[FIELD_REQUEST].get(FIELD_OPERATION,
                                                                                    VALUE_OPERATION_DEFAULT)
        json_response[FIELD_RESPONSE][FIELD_DEVICE] = request[FIELD_REQUEST].get(FIELD_DEVICE, VALUE_DEVICE_DEFAULT)

    return json_response


# Return meaningful error message for invalid/missing fields, or None if the request is valid.
def validate_request(request):
    fields = [FIELD_DEVICE, FIELD_OPERATION]
    if sys.version_info[0] < 3:
        types = [int, unicode]
    else:
        types = [int, str]

    error = check_fields(request, fields, types)
    # At this point we know that the device and operation are present and valid format
    if not error:
        # The Device address is expected to be an integer from 1-247
        # The Operation is expected to be supported and found in function_dict
        if request[FIELD_DEVICE] < 1 or request[FIELD_DEVICE] > 247:
            error = "Device address {} is out of range (0-247)".format(request[FIELD_DEVICE])
        elif request[FIELD_OPERATION] not in function_dict:
            error = "Operation {} is not supported".format(request[FIELD_OPERATION])

    return error


# Returns whether the specified fields are present in the request and have the expected types.
def check_fields(request, fields, expected_types):
    # Assumes that fields and expected_types are the same length when check_fields is called
    for field, expected_type in zip(fields, expected_types):
        if field not in request:
            return "Missing {} field".format(field)
        elif not isinstance(request[field], expected_type):
            return "Invalid {} field. Expected {}, got {}".format(field, expected_type, type(request[field]))
        elif expected_type == list and len(request[field]) < 1:
            return "Invalid {} field. List must be non-empty".format(field)
    return None


# Turns the response into a serializable JSON.
def serialize_response(json_response, fields):
    serial_response = {}
    response = json_response[FIELD_RESPONSE][FIELD_PAYLOAD]

    if hasattr(response, FIELD_FUNCTION_CODE):
        # If the response is an exception, we want to pass the function code and exception code.
        if response.function_code > 0x80:
            serial_response[FIELD_FUNCTION_CODE] = response.function_code
            serial_response[FIELD_EXCEPTION_CODE] = response.exception_code
        else:
            for field in fields:
                serial_response[field] = getattr(response, field)
    else:
        # This means that the response was a ModbusException error. All ModbusException objects have a 'string' field.
        serial_response[FIELD_ERROR] = getattr(response, 'string')

    json_response[FIELD_RESPONSE][FIELD_PAYLOAD] = serial_response
    return json_response


# Adds corresponding status message to the JSONResponse
def update_response_status(json_response):
    response = json_response[FIELD_RESPONSE][FIELD_PAYLOAD]
    # The best way to determine if the response was a success is by checking the function_code field.
    # Function_code fields above 0x80 indicate an exception.
    if FIELD_FUNCTION_CODE not in response:
        # No response was received, the error was caught in translator.py, and no request was sent
        publishResponse = PublishResponse.generate_error_response(json_response[FIELD_REQUEST_ID], STATUS_NO_RESPONSE,
                                                                  json_response[FIELD_RESPONSE][FIELD_PAYLOAD][
                                                                      FIELD_ERROR],
                                                                  **json_response[FIELD_RESPONSE])
    elif response[FIELD_FUNCTION_CODE] > 0x80:
        publishResponse = PublishResponse.generate_error_response(json_response[FIELD_REQUEST_ID], STATUS_EXCEPTION,
                                                                  "Internal Error",
                                                                  **json_response[FIELD_RESPONSE])
    else:
        publishResponse = PublishResponse.generate_success_response(json_response[FIELD_REQUEST_ID],
                                                                    **json_response[FIELD_RESPONSE])

    return publishResponse


# Modbus Response Fields
response_fields_dict = {
    OPERATION_READ_COILS: [FIELD_FUNCTION_CODE, FIELD_BITS, FIELD_ADDRESS],
    OPERATION_READ_INPUT_REGISTERS: [FIELD_FUNCTION_CODE, FIELD_REGISTERS, FIELD_ADDRESS],
    OPERATION_READ_DISCRETE_INPUTS: [FIELD_FUNCTION_CODE, FIELD_REGISTERS, FIELD_ADDRESS],
    OPERATION_READ_HOLDING_REGISTERS: [FIELD_FUNCTION_CODE, FIELD_REGISTERS, FIELD_ADDRESS],
    OPERATION_WRITE_SINGLE_COIL: [FIELD_FUNCTION_CODE, FIELD_ADDRESS, FIELD_VALUE],
    OPERATION_WRITE_SINGLE_REGISTER: [FIELD_FUNCTION_CODE, FIELD_ADDRESS, FIELD_VALUE],
    OPERATION_WRITE_MULTIPLE_COILS: [FIELD_FUNCTION_CODE, FIELD_ADDRESS, FIELD_COUNT],
    OPERATION_WRITE_MULTIPLE_REGISTERS: [FIELD_FUNCTION_CODE, FIELD_COUNT, FIELD_ADDRESS],
    OPERATION_READ_WRITE_MULTIPLE_REGISTERS: [FIELD_FUNCTION_CODE, FIELD_REGISTERS, FIELD_ADDRESS],
    OPERATION_MASK_WRITE_REGISTER: [FIELD_FUNCTION_CODE, FIELD_AND_MASK, FIELD_OR_MASK]
}

# Modbus Function Mapping
function_dict = {
    OPERATION_READ_COILS: read_request,
    OPERATION_READ_INPUT_REGISTERS: read_request,
    OPERATION_READ_DISCRETE_INPUTS: read_request,
    OPERATION_READ_HOLDING_REGISTERS: read_request,
    OPERATION_READ_ALL_HOLDING_REGISTERS: read_all_request,
    OPERATION_WRITE_SINGLE_COIL: write_single_request,
    OPERATION_WRITE_SINGLE_REGISTER: write_single_request,
    OPERATION_WRITE_MULTIPLE_COILS: write_multiple_request,
    OPERATION_WRITE_MULTIPLE_REGISTERS: write_multiple_request,
    OPERATION_READ_WRITE_MULTIPLE_REGISTERS: readwrite_multiple_registers_request,
    OPERATION_MASK_WRITE_REGISTER: mask_write_register_request
}

log = logging.getLogger()
log.setLevel(logging.DEBUG)

# Creating a greengrass core sdk client
client = greengrasssdk.client('iot-data')

# Initializing the Modbus Client
if SERIAL_PORT_ENV_VAR in os.environ:
    port = os.environ[SERIAL_PORT_ENV_VAR]
    log.info("port: {}".format(port))
else:
    port = DEFAULT_SERIAL_PORT
    log.info("default port")
modbusClient = ModbusClient(method=METHOD, port=port, timeout=DEFAULT_TIMEOUT_SECONDS, baudrate=DEFAULT_BAUDRATE,
                            stopbits=STOPBITS, bytesize=BYTESIZE, parity=PARITY)


def getVoltageValues(client, deviceid, response):
  registers = [0, 4, 8, 12, 16, 20]
  length = 2
  names = ["voltage_a","voltage_b","voltage_c","voltage_a_b","voltage_c_b", "voltage_a_c"]
  for index, reg in enumerate(registers):
    result = client.read_holding_registers(reg, length , unit=deviceid)
    # if not result.isError():
    decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Little)
    value = decoder.decode_32bit_float()
    response[names[index]] = value


def getCurrentValues(client, deviceid, offset, response, name):
  reg = 128
  length = 2
  skip = 2
  regToRead = reg + offset * (length + skip)
  result = client.read_holding_registers(regToRead, length , unit=deviceid)
  decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Little)
  currentValue = decoder.decode_32bit_float()
  response[name] = currentValue


def getFrequencyValue(client, deviceid, response):
  register = 1016
  length = 2
  name = "frequency"
  result = client.read_holding_registers(register, length , unit=deviceid)
  decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Little)
  value = decoder.decode_32bit_float()
  response[name] = value


# This is used for multiphase type only
def getVcInfo(client, deviceid, offset, response):
  global previousValue
  registers = [512, 514, 640, 642, 12544, 12546, 12548, 12550]
  lengths = [2,2,2,2,2,2,2,2]
  skip = [2,2,2,2,2,2,2,2]
  names = ["power_total","power_total_fundamental","power_reac_sum","power_reac_sum_fundamental","energy_total_pos","energy_total_neg","energy_reac_sum_import","energy_reac_sum_export"]
  types = ['FLOAT', 'FLOAT', 'FLOAT', 'FLOAT', 'INT_32', 'INT_32', 'INT_32', 'INT_32']

  for index, reg in enumerate(registers):
    regToRead = reg + offset * (lengths[index] + skip[index])
    result = client.read_holding_registers(regToRead, lengths[index], unit=deviceid)
    decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Little)
    if types[index] == 'FLOAT':
      currentValue = decoder.decode_32bit_float()
    elif types[index] == 'INT_16':
      currentValue = decoder.decode_16bit_uint()
    elif types[index] == 'INT_32':
      currentValue = decoder.decode_32bit_uint()
    response[names[index]] = currentValue


# call for both single phase and multiphase
def getCtInfo(client, deviceid, offset, response, names):
  registers = [256, 384, 768, 12288, 12290, 12416, 12418]
  lengths = [2, 2, 2, 2, 2, 2, 2]
  skip = [2, 2, 2, 2, 2, 4, 4]
  types = ['FLOAT', 'FLOAT', 'FLOAT', 'INT_32', 'INT_32', 'INT_32', 'INT_32']

  for index, reg in enumerate(registers):
    regToRead = reg + offset * (lengths[index] + skip[index])
    result = client.read_holding_registers(regToRead, lengths[index], unit=deviceid)
    decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Little)
    if types[index] == 'FLOAT':
      currentValue = decoder.decode_32bit_float()
    elif types[index] == 'INT_16':
      currentValue = decoder.decode_16bit_uint()
    elif types[index] == 'INT_32':
      currentValue = decoder.decode_32bit_uint()
    response[names[index]] = currentValue


def function_handler(event, context):
    log.info("Client connecting...")
    response = {}
    try:
        modbusClient.connect()
        log.info("Connected")
        response = execute(modbusClient, event)
        log.info(response)
        log.info("Closing connection")
        modbusClient.close()
        time.sleep(1)
    except Exception as e:
        json_response = init_default_json_response(event)
        json_response[FIELD_RESPONSE][FIELD_PAYLOAD][FIELD_ERROR] = str(e)
        response = PublishResponse.generate_error_response(json_response[FIELD_REQUEST_ID], STATUS_EXCEPTION,
                                                           str(e),
                                                           **json_response[FIELD_RESPONSE])

    client.publish(topic=DEFAULT_TOPIC_RESPONSE, payload=json.dumps(response))

    return
 

Python Online Compiler

Write, Run & Share Python code online using OneCompiler's Python online compiler for free. It's one of the robust, feature-rich online compilers for python language, supporting both the versions which are Python 3 and Python 2.7. Getting started with the OneCompiler's Python editor is easy and fast. The editor shows sample boilerplate code when you choose language as Python or Python2 and start coding.

Taking inputs (stdin)

OneCompiler's python online editor supports stdin and users can give inputs to programs using the STDIN textbox under the I/O tab. Following is a sample python program which takes name as input and print your name with hello.

import sys
name = sys.stdin.readline()
print("Hello "+ name)

About Python

Python is a very popular general-purpose programming language which was created by Guido van Rossum, and released in 1991. It is very popular for web development and you can build almost anything like mobile apps, web apps, tools, data analytics, machine learning etc. It is designed to be simple and easy like english language. It's is highly productive and efficient making it a very popular language.

Tutorial & Syntax help

Loops

1. If-Else:

When ever you want to perform a set of operations based on a condition IF-ELSE is used.

if conditional-expression
    #code
elif conditional-expression
    #code
else:
    #code

Note:

Indentation is very important in Python, make sure the indentation is followed correctly

2. For:

For loop is used to iterate over arrays(list, tuple, set, dictionary) or strings.

Example:

mylist=("Iphone","Pixel","Samsung")
for i in mylist:
    print(i)

3. While:

While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while condition  
    #code 

Collections

There are four types of collections in Python.

1. List:

List is a collection which is ordered and can be changed. Lists are specified in square brackets.

Example:

mylist=["iPhone","Pixel","Samsung"]
print(mylist)

2. Tuple:

Tuple is a collection which is ordered and can not be changed. Tuples are specified in round brackets.

Example:

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)

Below throws an error if you assign another value to tuple again.

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)
myTuple[1]="onePlus"
print(myTuple)

3. Set:

Set is a collection which is unordered and unindexed. Sets are specified in curly brackets.

Example:

myset = {"iPhone","Pixel","Samsung"}
print(myset)

4. Dictionary:

Dictionary is a collection of key value pairs which is unordered, can be changed, and indexed. They are written in curly brackets with key - value pairs.

Example:

mydict = {
    "brand" :"iPhone",
    "model": "iPhone 11"
}
print(mydict)

Supported Libraries

Following are the libraries supported by OneCompiler's Python compiler

NameDescription
NumPyNumPy python library helps users to work on arrays with ease
SciPySciPy is a scientific computation library which depends on NumPy for convenient and fast N-dimensional array manipulation
SKLearn/Scikit-learnScikit-learn or Scikit-learn is the most useful library for machine learning in Python
PandasPandas is the most efficient Python library for data manipulation and analysis
DOcplexDOcplex is IBM Decision Optimization CPLEX Modeling for Python, is a library composed of Mathematical Programming Modeling and Constraint Programming Modeling