Sending simple tweets from the Psion MC400

Using a 30 year old laptop in 2021:

As the MC400 has no native networking capability we can’t send tweets directly from the machine itself. But, if you attach to another computer that does have a twitter client via the serial link(s) then it becomes possible. I could have just used the terminal and tweeted directly from the Raspberry Pi command line but I wanted something that felt more like an actual twitter client running on the MC itself. This solution uses rainbowstream on the Pi to tweet so obviously requires it to be installed and configured as a prerequisite. The account that the tweets will come from is the one that is configured within rainbowstream.

I created an OPL program that asks the user for the tweet text. The progam writes the text to REM::C:\TWEET\INBOX\TWEET.TXT directly on the Pi’s filesystem. This is possible as I have the DOS “MCLINK.EXE” running on the Pi in the dosbos-x emulator, but this obviously relies on the Link app running on the MC400 too so the first thing the OPL does is check that it can see the Pi’s filesystem. The dosbox-x config uses the USB-serial adapter /dev/ttyUSB1 as COM1 and mounts /home/pi/mc400 as C:\

The OPL also implements “Bring” functionality – basically a one shot copy/paste: highlight some text in another application i.e. text processor switch back to TWITTER.OPO and “bring” the text. This is based on some old OPL code from 1995 that utilises system calls (credit: Colly Myers, David Wood and Tom Dolbilin).

tweeting…

I found a generic Twitter icon on the web, resized it to 32 x 32 and converted it to PCX format using the GIMP, then converted it to the Psion .PIC format using Psion’s own DOS tool WSPCX.EXE. Renaming it to .ICN allows the translated .OPO file to be installed in the MC400 system with this icon.

A script on the Pi watches /home/pi/mc400/TWEET/INBOX using the inotifywait command (part of the inotify suite) and if it detects the file TWEET.TXT has been created it then runs some further commands to pipe the text through the rainbowstream twitter cleint.

Finally, the TWEET.TXT file from the /home/pi/mc400/TWEET/INBOX directory is moved to /home/pi/mc400/TWEET/OUTBOX with the current date/time added to the filename.

The shell script to process the tweets is installed as services on the Pi, the service spec is included below too. Save this file as
/lib/systemd/system/mc400_tweet.service
Then issue the commands
sudo systemctl daemon-reload
sudo systemctl enable mc400_tweet.service
sudo systemctl start mc400_tweet.service

sudo systemctl status mc400_tweet.service

(all code also available on GitHub)

OPL code – TWITTER.OPL:

REM
REM Simple Tweet sending from the Psion MC400
REM
REM - needs appropriate handling of files written
REM   to an attached device to actually send tweets!
REM
REM v1.0.01 19/09/2021 @zedstarr
REM

PROC Tweeter:

  LOCAL ret%,fName$(128),txt$(255),address%,version$(6)
  LOCAL handle%,mode%,k%,len%

  version$="1.0.01"
  
  REM Check link connected
  ONERR borked::
  k%=EXIST("REM::C:\MCLINK.EXE")
  borked::
  ONERR OFF
  IF NOT k%
    PRINT 
    PRINT "  PSION MC400 Simple Twitter Client v"+version$+" (c) 2021 @zedstarr"
    PRINT 
    PRINT "  ERROR: Can't connect to PsiBridge"
    PRINT "         Check serial connection."
    GET
    
  ELSE
  
    fName$="REM::C:\TWEET\INBOX\TWEET.TXT"  
    
    SCREEN 80,12
    
    WHILE NOT ERR
      PRINT 
      PRINT "   PSION MC400 Simple Twitter Client v"+version$+" (c) 2021 @zedstarr"
      PRINT "     (on a blank line - <ESC> to quit, L<Enter> to Bring data)"
      PRINT
      PRINT " Tweet:>",
      TRAP EDIT txt$
    
      IF ERR=-114 
        RETURN
      ENDIF
    
      IF txt$="L"
    	  txt$=bring$:
    	  CLS
    	ELSE
    	  BREAK
      ENDIF
    ENDWH
      
    mode% = $0001 OR $0020 OR $0100
  
    ret%=IOOPEN(handle%,fName$,mode%)
    IF ret%
      Showerr:(ret%)
      RETURN
    ENDIF
    
    address%=ADDR(txt$)+1 : len%=len(txt$)
    ret%=IOWRITE(handle%,address%,len%)
    IF ret%
      Showerr:(ret%)
      RETURN
    ENDIF

    ret%=IOCLOSE(handle%)
    IF ret%
      Showerr:(ret%)
      RETURN
    ENDIF
    
  ENDIF
  
ENDP

PROC Showerr:(val%)
  PRINT "Error: ",val%,err$(val%)
  GET
ENDP


rem The following code is based on "LPC.OPL"
rem (Original code written by Colly Myers)
rem (Translated into OPL by Tom Dolbilin)
rem (adapted for the MC with help from David Wood at Psion)

PROC Bring$:

global srvPid%      rem PID of Link server
global buf$(255)    rem Buffer to receive data
global ioSem%       rem The I/O semaphore handle

local wsrvPid%      rem PID of Window server
local fmt&          rem Message buffer
local pfmt%         rem Pointer to message buffer

local name$(15),form%(3),n%,ret%,ret$(255)
local w%(2)

rem --- Get Shell's pid

rem Note: on the MC, SYS$SHLL.IMG holds Link server data
rem on the Series 3/3a SYS$WSRV.IMG holds Link Server data
rem change name$ to "SYS$WSRV.*" for Series 3/3a

name$="SYS$SHLL.*"
wsrvPid%=call($188,addr(name$)+1)

rem --- Get the handle of our I/O semaphore

call($78b,0,2,0,(call($88) and $fff)+33,addr(ioSem%))

rem --- Get the Link Server's pid

pfmt%=addr(fmt&)
srvPid%=call($683,wsrvPid%,4,0,addr(pfmt%)) rem MessSendReceiveWithWait

if srvPid%<0
	showerr:(srvPid%)
	return ""
elseif srvPid%=0
	at 2,11
	print "Nothing to bring"
	pause 40
	at 2,11
	print "                "
	return ""
endif

rem --- Get the name of the Link Server

call($a88,srvPid%,0,0,0,addr(name$)+1)
pokeb addr(name$),call($b9,0,0,0,0,addr(name$)+1)

w%(1)=$2

rem --- Request a rendering

ret%=talk%:(w%(1),0)
if ret%=0
	rem --- Server prepared to render data
	while 1
		ret%=talk%:(addr(buf$)+1,255) : rem Get the server data
		if ret%<0
			if ret%=-36     rem End of file
				ret%=0        rem Avoid an error print
			endif
			break
		endif
		rem --- Display the data received
		pokeb addr(buf$),ret%
		ret$=peek$(addr(buf$))
	endwh
endif

if ret%<0
	showerr:(ret%)
endif

return ret$

ENDP

PROC Talk%:(a%,b%)
local arg1%,arg2%	        rem Message parameters - keep in order
local stat%,count%,ret%
arg1%=a%
arg2%=b%
call($0583,srvPid%,$21,0,addr(arg1%),addr(stat%)) rem MessSendReceiveAsynchronous
while 1
	iowait
	if stat%<>-46 : rem The result has been returned
		ret%=stat%
		break
	else
		count%=count%+1 : rem Count up all other signals
	endif
endwh

rem --- Put back spare signals if any
while count%
	iosignal
	rem call($382,ioSem%,0,0,0,0) rem SemSignalOnce
	count%=count%-1
endwh
return ret%
ENDP

Shell script –/home/pi/mc400/scripts/mc400_watch_tweet.sh:

#!/bin/bash
/usr/bin/inotifywait -m /home/pi/mc400/TWEET/INBOX/ -e create  |
  while read dir action file; do
    if [[ "$file" == "TWEET.TXT" ]] ; then
      sleep 3
      content=`cat /home/pi/mc400/TWEET/INBOX/TWEET.TXT`
      /usr/bin/printf "\nt $content\nexit()" | /usr/local/bin/rainbowstream
      sleep 12
      mv /home/pi/mc400/TWEET/INBOX/TWEET.TXT /home/pi/mc400/TWEET/OUTBOX/TWEET`date +"%Y-%m-%d-%H%M%S"`.TXT
    fi
  done

mc400_tweet.service file:

[Unit]
Description=PSION MC400 tweet helper

[Service]
Restart=on-failure
RestartSec=10
ExecStart=/home/pi/mc400/scripts/mc400_watch_tweet.sh
WorkingDirectory=/home/pi/mc400
SyslogIdentifier=mc400
User=pi
Group=pi
StandardOutput=null

[Install]
WantedBy=multi-user.target
%d bloggers like this: