CHAPTER 19


DIARY

This section describes the workings of the top level functions DIARY and ALARM. The ORGANISER II manual describes how to use the DIARY and ALARMS.


19.1 DIARY FORMAT (CM, XP, LA)

The information in the diary is stored in RAM in an allocated CELL, separate from the RAM device A:, so the diary can not be accessed as a file from OPL or by the top level functions FIND and SAVE. Entries are kept sorted by date and time, and apart from general data on A:, in order to allow alarm checking interrupts to scan the diary efficiently. This also enables the SAVE and RESTORE functions to save the whole diary as a block easily.

For information on the storage allocator, see section 6.3.2. The diary is held in the third cell, base pointer $2004. Below the diary are the DEVICE and MENU cells, so any operation which grows or shrinks these cells will cause the diary to move. This includes calls to DV$ and TL$ services, device booting at the top level and any changes in the top level menu. Within the diary cell the entries are stored as follows :

        BYTE    RANGE
        0       1 - 64          length of text 
        1       0 - 99          year 
        2       0 - 11          month 
        3       0 - 30          day 
        4       0 - 23          hour 
        5       0 or 30         minutes 
        6       0 - 60          if 0 
                                  no alarm set 
                                else 
                                  (number of minutes early+1)
        7                       text of diary entry 
        ...                     (1-64 chars) 
        ... 
        ...

Example :

12 JAN 1986 17:00 ABCDEF (alarm set 15 minutes early)

Bytes :

                $06     length of "ABCDEF" 
                $56     year 1986 
                $00     month JAN 
                $0B     day 12 
                $11     hour 17 
                $00     minutes 0 
                $10     15 minutes early 
                6 bytes "ABCDEF" 
                next entry or 0 terminator

The diary is terminated by a byte zero, not by the end of the cell and so at initialisation time the diary cell just contains this zero byte. It is illegal to shrink the cell to nothing using AL$ZERO or AL$SHNK. The following examples scan through each diary entry :

        LDX     $2004   ; X points to 1st byte 
        BRA     2$
1$:     ADD     B,#7    ; skip length byte date alarm flag 
        ABX             ; and skip over text 
2$:     LDA     B,0,X   ; get length byte 
        BNE     1$      ; until 0 terminator found
         ADDR% = PEEKW($2004)
         LEN%  = PEEKB(ADDR%)
         WHILE LEN% <> 0
           ADDR% = ADDR% + 7 + LEN%
           LEN%  = PEEKB(ADDR%)
         ENDWH

The diary is saved on the datapacks as a block file of type $82 (see section 12.1.3).

Bear in mind the following restrictions when manipulating the diary :

  1. Use SEI to prevent alarm checking interrupts while the diary is being modified or during loading from a datapack. Also see below, section 19.3.
  2. All entries must be inserted in chronological order
  3. No two entries must have the same time
  4. The time must be in the year range 1900-1999 with minutes 0 or 30
  5. Text must be less than 65 chars
  6. The AL$ system services must be used to allocate space in the diary

The following OPL program writes a formatted listing of the diary to either a file or the RS232. The main procedure is "DIARY:".

OUTLINE:(STRING$)
IF CHOICE%=1
 A.A$=STRING$
 APPEND
ELSE
 LPRINT STRING$
ENDIF


DIEND%:
RETURN (PEEKB(PTR%)=0)


INIT:
PTR%=PEEKW($2004)
PREVDAY$=""


DIG2$:(NUM%)
RETURN RIGHT$("0"+NUM$(NUM%,6),2)


OUTENTRY:
OUTLINE:("")
IF LEN(DAY$)
 OUTLINE:(DAY$)
ENDIF
OUTLINE:(TIME$)
OUTLINE:(DATA$)


GETENTRY:
LOCAL YEAR%,MONTH%,DAY%,HOUR%,MINUTE%,ALARM%,LEN%,D%,ENDP%
LOCAL AMPM$(2)
YEAR%=1900+PEEKB(PTR%+1)
MONTH%=PEEKB(PTR%+2)
DAY%=1+PEEKB(PTR%+3)
HOUR%=PEEKB(PTR%+4)
MINUTE%=PEEKB(PTR%+5)
ALARM%=PEEKB(PTR%+6)
DAY$=WKDAY$:(PTR%+1)+" "+NUM$(DAY%,-2)+" "+
                MID$("JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC",MONTH%*3+1,3)+
                " "+NUM$(YEAR%,4)
IF PREVDAY$=DAY$
 DAY$=""
ELSE
 PREVDAY$=DAY$
ENDIF
AMPM$="AM"
IF HOUR%>=12
 AMPM$="PM"
 IF HOUR%>12
  HOUR%=HOUR%-12
 ENDIF
ENDIF
TIME$="    "+DIG2$:(HOUR%)+"."+DIG2$:(MINUTE%)+" "+AMPM$
IF ALARM%
 TIME$=TIME$+"  ALARM SET "
 IF ALARM%>1
  TIME$=TIME$+DIG2$:(ALARM%-1)+" MINUTES EARLY."
 ENDIF
ENDIF
DATA$="    "
LEN%=PEEKB(PTR%)
PTR%=PTR%+7
ENDP%=PTR%+LEN%
D%=ADDR(DATA$)+LEN(DATA$)+1
DO
 POKEW D%,PEEKW(PTR%)
 D%=D%+2
 PTR%=PTR%+2
UNTIL PTR%>=ENDP%
POKEB ADDR(DATA$),LEN(DATA$)+LEN%
PTR%=ENDP%

ASKFILE:
LOCAL READY%,ERR%
LOCAL FILE$(10),K$(1)
AGAIN::
ONERR HANDLER::
DO
 DO
  CLS
  PRINT "FILE ";
  TRAP INPUT FILE$
  IF ERR
   RAISE ERR
  ENDIF
  FILE$=UPPER$(FILE$)
 UNTIL LEN(FILE$)
 READY%=0
 TRAP OPEN FILE$,A,A$
 IF ERR AND (ERR<>234)
  RAISE ERR
 ENDIF
 TRAP CLOSE
 IF EXIST(FILE$)
  CLS
  PRINT FILE$;" EXISTS"
  PRINT "DELETE [Y/N]?";
  DO
   K$=UPPER$(GET$)
   IF K$=CHR$(1)
    RAISE 206
   ENDIF
  UNTIL K$="Y" OR K$="N"
  IF K$="Y"
   DELETE FILE$
   READY%=1
  ENDIF
 ELSE
  READY%=1
 ENDIF
UNTIL READY%
CREATE FILE$,A,A$
RETURN

HANDLER::
ERR%=ERR
TRAP CLOSE
IF ERR%=206
 STOP
ELSEIF ERR%=236 OR ERR%=243
 CLS
 PRINT CHR$(16);ERR$(ERR%);
 IF GET=1
  STOP
 ENDIF
 GOTO AGAIN::
ENDIF
RAISE ERR%


WKDAY$:(ADDR%)
LOCAL ND%,YR%,M%
YR%=PEEKB(ADDR%)
M%=PEEKB(ADDR%+1)
ND%=YR%+YR%/4+PEEKB(ADDR%+2)+ASC(MID$("035136240250",M%+1,1))+ND%-(((YR% AND 3)=0) AND (M%<=1))
RETURN MID$("SUNMONTUEWEDTHUFRISAT",(ND%-ND%/7*7)*3+1,3)


DIARY:
GLOBAL CHOICE%,PTR%,DAY$(50),TIME$(50),DATA$(100),PREVDAY$(50)
CLS
CHOICE%=MENU("FILE,PRINTER")
IF CHOICE%
 INIT:
 IF DIEND%:
  PRINT "DIARY EMPTY";
  GET
 ELSE
  IF CHOICE%=1
   ASKFILE:
  ENDIF
  DO
   GETENTRY:
   OUTENTRY:
  UNTIL DIEND%:
  IF CHOICE%=1
   CLOSE
  ENDIF
 ENDIF
ENDIF


DIARY FORMAT (LZ)

The LZ diary is stored in the same cell and in almost exactly the same format as the CM/XP diary. The only change is that an extra byte is added at the end of each entry, to specify the duration of the entry (in quarter hours). The length byte also takes this duration byte into account, so it now contains the length of the entry plus one.

EXAMPLE

   Mon 8 May 1989 Wk19 
   6:00a
   5:15p ABCDEF 
   5:45p 

If an alarm is set for 15 minutes early, the bytes are:

   $07 length of "ABCDEF" plus one
   $59 year 1989 
   $04 month MAY
   $07 day 8
   $11 hour 17 
   $0F minutes 15
   $10 alarm 15 minutes early 
   6 bytes "ABCDEF" 
   $02 duration 30 minutes 
   and then, the next entry or 0 for end-of-diary. 

Notice that, as before, year month and day "start" from 0. An "alarm" byte of 0 means "no alarm"; otherwise it is the number of minutes to ring early, plus one. Because the length byte "includes" the extra duration byte, you can use the same code to scan through the diary as on the CM/XP, although your programs must then handle the duration byte specially. The length byte therefore can have values from 2 to 65.

Other Changes:

Times are displayed in 12 or 24 hour format, as specified by the user when setting the system time.

As well as chronological order being required, and no two entries having the same time, it is also essential that they do not overlap because of their durations. They may, however, meet exactly. No entry must pass midnight (p.m.) owing to its duration.

The time must be in the year range 1900-2155 with minutes 0, 15, 30 or 45. Note that all dates still "wrap around", from 2155 to 1900 and vice versa.

The maximum length of a "Find String" in the "Find" option is 30, not 32.

The diary is no longer saved (using the "Save" option) as a block file of type $82, but instead as an ordinary data file. Each entry is saved as a separate record - the previous example would be:

         1989050817150216
         ABCDEF 

The first field specifies the date of the entry (1989, 05, 08, for 8th May 1989), the time (17 15, for 5:15pm), the duration (02) and the alarm byte (16, for 15 minutes before - again, 00 would mean "no alarm"). The second field is the text of the entry. The order of things in the first field, and the use of zeroes where necessary to keep things the same length (e.g. "02" for a duration of 2) make the file easy to sort in OPL, or otherwise. Note, though, that the "Restore" option in the diary will restore entries in any order from a file. The "Xrestore" option can be used to load a CM/XP diary from a pak, in which case each entry is given a duration of 2 (=30 minutes). Note that because a saved record can be edited or even created by a user, the year, month and day sections start from 1, not 0 - e.g., the 1st of the month is saved as "01", not "00". You can not as yet interchange diaries with Sidekick on a PC, as versions 1.1 and below of Diary Link should not be used with the LZ/LZ64. The UDGs used by the diary are: high week view arrow: 0 low week view arrow: 1 bell symbol in day page: 3 four week view boxes: 4-7


19.2 ALARM TABLE FORMAT

The eight alarms are stored in a fixed length 48 byte area AMT_TAB ($22F9). Each entry contains a date-time in the usual format, with a flag indicating the type of alarm.

        BYTE    RANGE
        0       0 - 99   year 
        1       0 - 11   month 
        2       0 - 30   day 
        3       0 - 23   hour 
        4       0 - 59   minutes 
        5       0 - 4    0 means alarm cancelled 
                         1 non-repeating 
                         2 weekly, 3 daily, 4 hourly repeat 

The alarms on the LZ are very similar to those on the standard Organiser, with inforation stored in the same 48 byte table. The last byte of each alarm record now contains additional information on the sound set for the alarm and the new repeat types.

Bits 0-2 give the repeat setting associated with the alarm, as follows:

  Value      Repeat Setting
    0        Alarm not Set
    1        Hourly
    2        Daily
    3        Workdays
    4        Weekly
    5        Once

The top two bits give the associated tone to go with the alarm:

  Value      Tone
    0        Normal
    1        Siren
    2        Chimes

A system variable AMB_WRKD is provided on the LZ machines to indicated which days are workdays.

An alarm entry is cancelled by setting byte 5 to zero. Before setting or modifying any alarms, byte 5 should be cleared and then set last of all. This is to prevent interrupts from checking that entry.

Note that although there is no way of manually setting an alarm outside the current week, this limitation need not apply to user programs which manipulate AMT_TAB directly. You can set an alarm to ring at any time between 1900 and end of 1999 (LZ: 2155). The date-time of a repeating alarm is updated each time it rings; an alarm entry does not contain the original date-time.


19.3 ALARM CHECKING INTERRUPTS

Both the diary and the alarms are scanned approximately every minute by the 50ms maskable interrupts which scan the keyboard. Users wishing to alter the alarm or diary alarm action, see vector BTA_OCI, or the BZ$ALRM service. Every minute an NMI makes a request for alarm checking by setting the flag AMB_DOIT provided the following conditions are met :

        BTB_IGNM <> 0   (else NMI does nothing) 
        AMB_EI <> 0 
        TMB_SECS = 0    (we are on a minute boundary)

The flag AMB_EI is provided specifically so that user programs can disable alarm checking. If all these conditions are met, the alarm is not actually checked immediately : this is left to the next maskable interrupt which rings any pending alarms whenever AMB_DOIT is set. This means that alarms are checked as soon as possible after each minute boundary, but any time-critical activities such as writing to datapacks and other operations can delay alarms by using the SEI instruction.

Alarms will never occur while the interrupt mask is set. Also certain activities such as device booting (DV$ calls), storage management (AL$ calls), or modification of the diary or alarms can cause an ALARM to ring late. If interrupts are not required, then an SEI instruction is all that is required to disable alarm checking. If interrupts are required the following code must be used to maintain compatibility with all OS versions :

        LDA     A,AMB_EI 
        PSH     A
        TPA                     ; preserve interrupt mask 
        SEI 
        CLR     AMB_EI          ; prevent NMI setting AMB_DOIT 
        CLR     AMB_DOIT        ; in case AMB_DOIT already set 
        TAP                     ; restore interrupt mask
        ... ; user program alarm checking now off 
;       ...
; the next two lines are optional 
        INC     AMB_EI          ; set AMB_DOIT if check required ** 
        INC     AMB_DOIT        ; on next interrupt              **
        PUL     A 
        STA     A,AMB_EI        ; restart normal checking

The two lines ** will cause the next 50 ms interrupt to perform a check without waiting for the next minute boundary. This will minimise any late running of the alarms. The AMB_DOIT flag can also be set to request more frequent checking than once each minute. However, AMB_DOIT must at no time be set non-zero when AMB_EI is zero as this may cause problems with some early OS versions. Note also that AMB_EI should not be set to $FF.

The 50 ms interrupt first checks the DIARY then the eight alarms. All alarms are sounded even if they are overdue. The earliest DIARY alarm will sound, then the lowest numbered ALARM alarm. If more than one DIARY or ALARM alarm are due, they will ring in pairs (DIARY,ALARM) each minute.

Before a DIARY alarm sounds, the alarm flag (byte 6) in that entry is cleared. Before an ALARM alarm sounds, the repeat time is added on for repeating alarms, and byte 5 is cleared for non-repeating alarms.

The system service DP$SAVE is called to save the screen, and the time and diary text or "ALARM" is displayed for one minute or until ON/CLEAR is pressed. The screen is then restored with a call to DP$REST. The ON/CLEAR key is polled directly, so the keyboard buffer is not affected.

BZ$ALRM makes the ALARM sound, while the DIARY beep is a call to BZ$TONE with D = 200 (proportional to 1/frequency), X=50 (note length).


19.4 WAKING UP FOR AN ALARM

The Organiser II maintains the system time with a 12 bit external counter while switched off . The machine switches on when the counter overflows every 2048 seconds (34 minutes 8 seconds), updates the system time, and switches off again.

The system service BT$SWOF rings any alarms pending, then checks if an alarm is due in the next 34 mins 8 secs. If necessary, BT$SWOF sets the counter to a value greater than zero to switch the machine on early.

When the Organiser II switches on, it rings the alarm then remains on until normal switch-off. Users wishing to alter this behaviour see vector BTA_SOF and for vector BTA_WRM.