September 28, 2018

MTTx Terminology

When it comes to asset reliability measurement all the different MTTx acronyms start to appear but sometimes is not clear to everybody the exact meanings of these terms.
In this post I want to give a very simple explanation of these reliability KPIs.


  • MTTD - Mean Time To Detect - How long it takes to discover a problem.
  • MTTK - Mean Time to Know - How long it takes to understand the problem.
  • MTTR - Mean Time To Repair - How long it takes to fix the problem.
  • MTTF - Mean Time To Failure - Amount of time the system is available and operating. Also known as "uptime".
  • MTBF - Mean Time Between Failures - Amount of time that elapses between one failure and the next.


MTBF is the sum of MTTF and MTTR, the total time required for a device to fail and that failure to be repaired.
MTBF = MTTR + MTTF

Here is a nice representation for these KPIs.




September 27, 2018

How to correctly handle synonym domains in automation script

We all know that Maximo allows the definition of new internal values for synonym domains. A common example is the LOCTYPE domain that defines the allowed values for the LOCATIONS.TYPE field.
If we want to implement a special business logic for the OPERATING type of locations we can write something like this in our automation script.

loctype = mbo.getString("TYPE")
if loctype=="OPERATING":
  # do something....

However, if we define a new alias for the OPERATING type this script will not work as expected because it will not manage automatically the different internal values of the LOCTYPE domain.


The best solution is to use the psdi.mbo.Translate.toInternalString method like this:

from psdi.server import MXServer

loctype = MXServer.getMXServer().getMaximoDD().getTranslator().toInternalString("LOCTYPE", mbo.getString("TYPE"), mbo)
if loctype=="OPERATING":
  # do something....

The toInternalString method will translate the internal value of the LOCATIONS.TYPE field to the corresponding domain external value.

There are two implementations of the toInternalString method that are documented in the Javadoc.

String toInternalString(String listName, String value)
Convert a specified value in the list to an internal MAXIMO value.
String returned is a single value.
Parameters:
- listName The valuelist name (valuelist.listname).  This is not case sensitive.
- value The external value (valuelist.value).  This is case sensitive.
Returns The internal value (valuelist.maxvalue)


String toInternalString(String listName, String value, MboRemote mbo)
Convert a specified value in the list to an internal MAXIMO value.
String returned is a single value.
Framework will use the siteid of the passed in MBO if it is site level, or the orgid of the MBO if it is org level to find out the actual values for the specified domain, and use those values for translation. If the siteid and orgid of the MBO is null, all the value records will be considered.
Parameters:
- listName The valuelist name (valuelist.listname).  This is not case sensitive.
- value The external value (valuelist.value).  This is case sensitive.
mbo the MBO that the translation values are used for or the MBO which the site and org is same with the MBO that the translation is used for.
Returns The internal value (valuelist.maxvalue)

August 30, 2018

Sending SMS from Maximo calling an external Web service

In my last project I had to implement a customization to send SMS messages using Skebby. The service APIs are obviously based on REST and JSON so I ended up writing an automation script to integrate Maximo with the Skebby service.
In the process of writing the code I have faced (and solved) some interesting issues so the developed script is quite complex and is an interesting demonstration of several useful techniques:
  1. External REST service invocation (POST and GET)
  2. JSON manipulation
  3. Retrieve Maximo system properties
  4. Usage of Java classes
  5. Appropriate logging using MXLoggerFactory
  6. Display error messages with parameters
The script is based on WORKORDER object and requires two custom system properties (sms.username, sms.password) and two messages (SmsSendError, SmsSendOk).
Here it is.
from java.lang import String
from java.io import BufferedReader, InputStreamReader
from java.net import URL, URLEncoder
from java.nio.charset import StandardCharsets
from com.ibm.json.java import JSONObject, JSONArray

from psdi.server import MXServer
from psdi.util.logging import MXLoggerFactory

logger = MXLoggerFactory.getLogger("maximo.script")
logger.debug("Entering SEND_SMS script")


BASEURL = "https://api.skebby.it/API/v1.0/REST/"
SMS_SENDER = "Maximo"
SMS_DEST = "+3933511111111"

sms_text = "Ticket " + mbo.getString("WONUM") + " - " + mbo.getString("DESCRIPTION")

properties = MXServer.getMXServer().getConfig()
username = properties.getProperty("sms.username")
password = properties.getProperty("sms.password")

urltxt = BASEURL + "login?username=" + URLEncoder.encode(username, "UTF-8") + "&password=" + URLEncoder.encode(password, "UTF-8")
logger.debug("Authenticating: " + urltxt)
url = URL(urltxt)
conn = url.openConnection()
conn.setRequestMethod("GET")

if conn.getResponseCode() != 200:
  conn.disconnect()
  msg = "HTTP Error: " + str(conn.getResponseCode()) + " - " + str(conn.getResponseMessage())
  logger.error(msg)
  service.error("asts", "SmsSendError", [str(conn.getResponseCode()), str(conn.getResponseMessage()), "Authentication failed"])

br = BufferedReader(InputStreamReader(conn.getInputStream()))
authResp = br.readLine()
conn.disconnect()

#logger.debug("Authentication output:" + authResp)
# auth tokens are returned separated by a ';' character
user_key,session_key = authResp.split(';')

logger.debug("Authentication tokens:" + user_key + "," + session_key)

logger.info("Sending SMS to " + SMS_DEST + " - " + sms_text)

url = URL(BASEURL + "sms")
conn = url.openConnection()
conn.setRequestMethod("POST")
conn.setRequestProperty("user_key", user_key)
conn.setRequestProperty("Session_key", session_key)
conn.setRequestProperty("content-type", "application/json")
conn.setDoOutput(True)

recipients = JSONArray()
recipients.add(SMS_DEST)

obj = JSONObject()
obj.put("message_type", "TI")
obj.put("returnCredits", False)
obj.put("sender", SMS_SENDER)
obj.put("recipient", recipients)
obj.put("message", sms_text)
jsonStr = obj.serialize(False)

logger.debug("SMS Json Request=" + jsonStr)

os = conn.getOutputStream()
postData = String.getBytes(jsonStr, StandardCharsets.UTF_8)
os.write(postData)
os.flush()

if conn.getResponseCode() != 201:
  br = BufferedReader(InputStreamReader(conn.getErrorStream()))
  output = br.readLine()
  logger.error("Error: " + output)
  conn.disconnect()
  msg = "HTTP Error: " + str(conn.getResponseCode()) + " - " + str(conn.getResponseMessage() + " - " + output)
  logger.error(msg)
  service.error("asts", "SmsSendError", [str(conn.getResponseCode()), str(conn.getResponseMessage()), output])

br = BufferedReader(InputStreamReader(conn.getInputStream()))
output = br.readLine()
logger.info("OK: " + output)
conn.disconnect()

service.setWarning("asts", "SmsSendOk", None)


The script has been developed to avoid external dependencies like this example.

August 23, 2018

Disable automatic asset/location population in Work Order and Ticket applications

Maximo has a feature that automatically populates the asset when a user enters a location and viceversa. This is a feature may be useful for most clients but some other clients don’t like this behavior. Unfortunately Maximo does not have a built in switch to disable this.

The best solution to solve the problem is to use an automation script with an attribute launch point.
This is documented in this post but i made few improvements to it.
My script resets the asset only when the location has been changed and ASSETNUM is changing from null to a new value. This is because there is already a warning dialog when the asset is changed from another value.

This is how I have defined my automation script. I have tested it for Service Requests application but it should work fine for Work Orders as well.

  • Script: SR_LOCATION
  • Language: python
  • Launch Point: SR_LOCATION_ACTION
  • Object: SR (this could be also be WORKORDER or TICKET)
  • Attribute: LOCATION
  • Event: Run Action (Validate should also work)
  • Variables: none

Script:
from psdi.mbo import MboConstants

if not mbo.isNull("LOCATION"):
  if mbo.getMboValue("ASSETNUM").getPreviousValue().asString()=="" and not mbo.isNull("ASSETNUM"):
    mbo.setValueNull("ASSETNUM", MboConstants.NOVALIDATION_AND_NOACTION)