November 28, 2018

Automation Script to reset user's Start Centers

In a previous post I have explained how to force the reload of a the start center for a specific group of users.
I have now developed a useful automation script that can be invoked from the Security Group application to automatically perform this task with just one click.
Register the script as a Script with an Action Launch Point and link it to a sigoption and to a menuaction, toolbar icon or application button as described in this post or this one.


# Reset users Start Centers
# Object: MAXGROUP
# Resets the Start Centers for all users assigned to the selected Security Group
# Should be called when all users are logged off so the start center is reloaded after the login
# See: http://maximodev.blogspot.com/2012/11/how-to-reset-users-start-centers.html

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


def deleteSc(objectName, where):
 scSet = MXServer.getMXServer().getMboSet(objectName, mbo.getUserInfo())
 scSet.setWhere(where)
 #scSet.reset()
 logger.debug("Deleting rows from " + objectName)
 scSet.deleteAll()
 scSet.save()


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

logger.info("Entering ASTS_RESET_STARTCENTER")
grpname = mbo.getString("GROUPNAME")

where1 = "scconfigid IN (SELECT scconfigid FROM scconfig WHERE groupname='" + grpname + "')"
where2 = "layoutid IN (SELECT layoutid FROM layout WHERE " +where1+ ")"


deleteSc("RSCONFIG", where2)
deleteSc("FACONFIG", where2)
deleteSc("INBXCONFIG", where2)
deleteSc("KPILCONFIG", where2)
deleteSc("KPIGCONFIG", where2)
deleteSc("ACTIONSCFG", where2)
deleteSc("PORTLETDISPLAY", where2)
deleteSc("LAYOUT", where2)
deleteSc("SCCONFIG", where1)

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.