[APP][Pro] AWTRIX - Connected Smart Clock for your Smart Home

Maybe you could change the title of this forum and app logo to Awtrix 3 and NG. Awtrix light was the name of the software several generations ago.

I have fixed Awtrix3 issues with power of AI too… :smiley:

Regarding name, the name of app is awtrix light, i cant change it sadly, but in forum i can try to add some info to it…

In the app store I only see the name Awtrix with image Awtrix light. The forum title is too long now.

/critical mode off

[HOW-TO][Pro] Repairing Flows after migrating from AWTRIX Light to AWTRIX NG

Based on Martijn Poppen’s original Flow-repair script, I prepared a customized HomeyScript to make migration from AWTRIX Light to AWTRIX NG easier.

Martijn’s script is excellent when a device is removed and re-added while the replacement exposes the same Flow cards. In that situation, replacing the old device UUID with the new UUID is sufficient.

AWTRIX Light to AWTRIX NG is slightly different:

  1. The device UUID changes.
  2. The old AWTRIX Light Flow card IDs are not always available for AWTRIX NG.

For example, an old card may contain:

homey:device:<new-device-id>:notificationIcon

After migration, AWTRIX NG uses:

homey:device:<new-device-id>:notification

The message, icon, color, and duration arguments are compatible, but the Flow-card ID is different. Therefore, replacing only the device UUID still leaves the Flow card unavailable or displayed as card: null. This could be addressed in the app code, but changing or remapping existing Flow cards would require additional backwards-compatibility safeguards. It would also not automatically repair Flows that were already stored in the broken format. For users who have already migrated, the customized script performs both operations:

  • replaces old device UUID references in regular and Advanced Flows;
  • changes the legacy notificationIcon card to the NG-compatible notification card;
  • supports flows where the UUID was already replaced but the old card ID remains;
  • provides a soft-run mode so changes can be reviewed before applying them.

For an already completed UUID replacement, use the same UUID in both arrays and keep softRun = true first:

const oldIds = [
  'NEW_DEVICE_ID',
];

const newIds = [
  'NEW_DEVICE_ID',
];

const softRun = true;

The script does not send commands to the display. It only updates stored Flow definitions. Nevertheless, use it carefully:

  • verify the device IDs;
  • run a soft-run first;
  • review the output;
  • set softRun = false only after confirming the planned changes;
  • test one repaired Flow afterwards.

This is specific to the AWTRIX Light → AWTRIX NG migration. The UUID replacement logic is generic, but the notificationIcon → notification mapping should not be reused for unrelated apps.

An app-side compatibility change could theoretically keep the legacy card available for NG devices, but that would not automatically rewrite already stored Flow JSON. The one-time migration script is therefore useful for repairing existing Flows without recreating them manually.

The customized script is included below.

The original Flow-repair approach is documented by Martijn in the Homey Community thread.

// HomeyScript: AWTRIX 3 -> AWTRIX NG Flow migration



const oldIds = \[

  '37614016-25f4-48e6-95a7-d9221361d7fa',

\];



const newIds = \[

  '1cca7b6e-f9c9-493e-827e-879afd423db0',

\];



// true = allow oldId === newId for auditing already-replaced UUIDs

const allowSameId = true;



// true = preview only; false = write changes

const softRun = true;



const detailedLog = true;



// AWTRIX 3 legacy card -> shared AWTRIX/NG card

const ACTION_CARD_MAP = new Map(\[

  \['notificationIcon', 'notification'\],

\]);



if (oldIds.length !== newIds.length || oldIds.length === 0) {

  throw new Error('oldIds and newIds must have the same non-zero length.');

}



const migrations = oldIds.map((oldId, index) => ({

  oldId,

  newId: newIds\[index\],

}));



for (const { oldId, newId } of migrations) {

  if (

    !oldId

    || !newId

    || (oldId === newId && !allowSameId)

  ) {

    throw new Error(\`Invalid migration pair: ${oldId} -> ${newId}\`);

  }

}



function deepReplace(value, from, to) {

  if (typeof value === 'string') {

    const parts = value.split(from);



    return {

      value: parts.length > 1 ? parts.join(to) : value,

      count: parts.length - 1,

    };

  }



  if (Array.isArray(value)) {

    let count = 0;



    const result = value.map((item) => {

      const replaced = deepReplace(item, from, to);

      count += replaced.count;

      return replaced.value;

    });



    return { value: result, count };

  }



  if (value && typeof value === 'object') {

    let count = 0;

    const result = {};



    for (const \[key, item\] of Object.entries(value)) {

      const replaced = deepReplace(item, from, to);

      result\[key\] = replaced.value;

      count += replaced.count;

    }



    return { value: result, count };

  }



  return { value, count: 0 };

}



// Supports normal Advanced Flow cards and the diagnostic shape:

// { card: null, data: { ... } }

function storedCard(card) {

  return card?.data && typeof card.data === 'object'

    ? card.data

    : card;

}



function belongsToDevice(card, deviceId) {

  const data = storedCard(card);

  const deviceUri = \`homey:device:${deviceId}\`;



  return \[

    data?.ownerUri,

    data?.uri,

    data?.id,

  \].some((value) => (

    typeof value === 'string'

    && (

      value === deviceUri

      || value.startsWith(\`${deviceUri}:\`)

    )

  ));

}



function migrateActionCardId(card, newDeviceId) {

  const data = storedCard(card);



  if (

    !data

    || typeof data.id !== 'string'

    || !belongsToDevice(card, newDeviceId)

  ) {

    return null;

  }



  for (const \[oldCardId, newCardId\] of ACTION_CARD_MAP) {

    // Regular Flow card: id = notificationIcon

    if (data.id === oldCardId) {

      data.id = newCardId;

      return { oldCardId, newCardId };

    }



    // Advanced Flow card:

    // id = homey:device:<uuid>:notificationIcon

    const suffix = \`:${oldCardId}\`;



    if (data.id.endsWith(suffix)) {

      data.id = \`${data.id.slice(0, -suffix.length)}:${newCardId}\`;

      return { oldCardId, newCardId };

    }

  }



  return null;

}



function migrateCard(card, location, details) {

  let updated = card;

  let changed = false;

  let uuidReplacements = 0;



  for (const migration of migrations) {

    // Same-ID mode is an audit/card-repair mode.

    // Do not count same-ID strings as replacements.

    const replaced = migration.oldId === migration.newId

      ? { value: updated, count: 0 }

      : deepReplace(

        updated,

        migration.oldId,

        migration.newId,

      );



    updated = replaced.value;



    if (replaced.count > 0) {

      changed = true;

      uuidReplacements += replaced.count;



      if (detailedLog) {

        details.push(

          \`${location}: UUID ${migration.oldId} -> \`

          + \`${migration.newId} (${replaced.count})\`,

        );

      }

    }



    // This also runs when UUID replacement already happened earlier.

    const cardIdChange = migrateActionCardId(

      updated,

      migration.newId,

    );



    if (cardIdChange) {

      changed = true;



      if (detailedLog) {

        details.push(

          \`${location}: card ${cardIdChange.oldCardId} -> \`

          + \`${cardIdChange.newCardId}\`,

        );

      }

    }

  }



  return {

    value: updated,

    changed,

    uuidReplacements,

  };

}



function migrateRegularFlow(flow) {

  const updated = JSON.parse(JSON.stringify(flow));

  const details = \[\];

  let changed = false;

  let uuidReplacements = 0;



  if (updated.trigger) {

    const result = migrateCard(

      updated.trigger,

      'trigger',

      details,

    );



    updated.trigger = result.value;

    changed ||= result.changed;

    uuidReplacements += result.uuidReplacements;

  }



  for (const section of \['conditions', 'actions'\]) {

    if (!Array.isArray(updated\[section\])) continue;



    updated\[section\] = updated\[section\].map((card, index) => {

      const result = migrateCard(

        card,

        \`${section}\[${index}\]\`,

        details,

      );



      changed ||= result.changed;

      uuidReplacements += result.uuidReplacements;



      return result.value;

    });

  }



  return {

    updated,

    changed,

    uuidReplacements,

    details,

  };

}



function migrateAdvancedFlow(flow) {

  const updated = JSON.parse(JSON.stringify(flow));

  const details = \[\];

  let changed = false;

  let uuidReplacements = 0;



  for (const \[key, card\] of Object.entries(updated.cards || {})) {

    const result = migrateCard(

      card,

      \`cards.${key}\`,

      details,

    );



    updated.cards\[key\] = result.value;

    changed ||= result.changed;

    uuidReplacements += result.uuidReplacements;

  }



  return {

    updated,

    changed,

    uuidReplacements,

    details,

  };

}



async function main() {

  console.log('======================================');

  console.log('AWTRIX Flow migration');

  console.log(\`Soft run: ${softRun}\`);

  console.log(\`Allow same ID: ${allowSameId}\`);

  console.log('Card mapping: notificationIcon -> notification');

  console.log('======================================');



  let regularPlanned = 0;

  let advancedPlanned = 0;

  let successfulWrites = 0;

  let failedWrites = 0;



  // Regular Flows

  const flows = Object.values(

    await Homey.flow.getFlows(),

  );



  for (const flow of flows) {

    const result = migrateRegularFlow(flow);



    if (!result.changed) continue;



    regularPlanned += 1;



    console.log(

      \`\[regular\] ${flow.name || flow.id}: planned\`,

    );



    if (result.uuidReplacements > 0) {

      console.log(

        \`  UUID replacements: ${result.uuidReplacements}\`,

      );

    }



    if (detailedLog) {

      result.details.forEach((line) => {

        console.log(\`  ${line}\`);

      });

    }



    if (!softRun) {

      try {

        // Send only mutable regular-Flow sections.

        const partial = {

          id: result.updated.id,

          trigger: result.updated.trigger,

          conditions: result.updated.conditions,

          actions: result.updated.actions,

        };



        await Homey.flow.updateFlow({

          id: flow.id,

          flow: partial,

        });



        successfulWrites += 1;

        console.log('  Written successfully');

      } catch (err) {

        failedWrites += 1;



        console.error(

          \`\[regular\] FAILED ${flow.name || flow.id}:\`,

          err?.message || err,

        );

      }

    }

  }



  // Advanced Flows

  const advancedFlows = Object.values(

    await Homey.flow.getAdvancedFlows(),

  );



  for (const flow of advancedFlows) {

    const result = migrateAdvancedFlow(flow);



    if (!result.changed) continue;



    advancedPlanned += 1;



    console.log(

      \`\[advanced\] ${flow.name || flow.id}: planned\`,

    );



    if (result.uuidReplacements > 0) {

      console.log(

        \`  UUID replacements: ${result.uuidReplacements}\`,

      );

    }



    if (detailedLog) {

      result.details.forEach((line) => {

        console.log(\`  ${line}\`);

      });

    }



    if (!softRun) {

      try {

        // Preserve the complete Advanced Flow.

        await Homey.flow.updateAdvancedFlow({

          id: flow.id,

          advancedflow: result.updated,

        });



        successfulWrites += 1;

        console.log('  Written successfully');

      } catch (err) {

        failedWrites += 1;



        console.error(

          \`\[advanced\] FAILED ${flow.name || flow.id}:\`,

          err?.message || err,

        );

      }

    }

  }



  console.log('======================================');

  console.log('Summary');

  console.log(\`Regular flows planned: ${regularPlanned}\`);

  console.log(\`Advanced flows planned: ${advancedPlanned}\`);

  console.log(

    \`Successful writes: ${softRun ? 0 : successfulWrites}\`,

  );

  console.log(\`Failed writes: ${failedWrites}\`);

  console.log(\`Soft run: ${softRun}\`);

  console.log('======================================');

}



main().catch((err) => {

  console.error(

    'Migration failed:',

    err?.message || err,

  );

});

Output example :

[regular] Postbox movement closed doors: planned (4 UUID occurrence(s))

  actions[11]: UUID 37614016-25f4-48e6-95a7-d9221361d7fa -> 1cca7b6e-f9c9-493e-827e-879afd423db0 (2)

  actions[11]: card notificationIcon -> notification

  actions[12]: UUID 37614016-25f4-48e6-95a7-d9221361d7fa -> 1cca7b6e-f9c9-493e-827e-879afd423db0 (2)

  actions[12]: card notificationIcon -> notification

[regular] Pohyb v rozvodné skříni domu: planned (2 UUID occurrence(s))

  actions[5]: UUID 37614016-25f4-48e6-95a7-d9221361d7fa -> 1cca7b6e-f9c9-493e-827e-879afd423db0 (2)

  actions[5]: card notificationIcon -> notification

[advanced] Developer check - nr of install app - adv: planned (0 UUID occurrence(s))

  cards.fb2ebb67-c354-4a5e-9284-bf14163daab8: card notificationIcon -> notification

  cards.518ea370-18cf-41e5-8f29-612c330f52fb: card notificationIcon -> notification

[advanced] HaasSohn check prior to the non-working day adv: planned (0 UUID occurrence(s))

  cards.d888d61a-61ea-42ee-8604-861bc2ae8474: card notificationIcon -> notification

  cards.1f7cf32e-9e65-41c3-93a7-c47fc55ca96d: card notificationIcon -> notification

  cards.069f64fd-464c-4dbf-b625-f36ba74e34ea: card notificationIcon -> notification

  cards.edb4573a-9c4b-462c-a8eb-ef388fd4733f: card notificationIcon -> notification

[advanced] Weather alerts activated, storm - adv.flow : planned (0 UUID occurrence(s))

  cards.3c31bfff-8276-4f03-bdee-95cef174401b: card notificationIcon -> notification

[advanced] AWTrix light Simon room adv: planned (0 UUID occurrence(s))

  cards.64f371e9-af66-47ed-8f26-1322e323cca1: card notificationIcon -> notification

  cards.b8d3418d-9eba-43dd-aa1b-94eb95157488: card notificationIcon -> notification

  cards.952b3e49-0eda-4d48-9e83-11362eecb928: card notificationIcon -> notification

  cards.600ca871-7b3d-47ae-98b5-0abda2a46cb1: card notificationIcon -> notification

  cards.df780320-7d5a-4cdc-99ad-176d5e313a4e: card notificationIcon -> notification

  cards.04098c11-f475-4637-97c9-1697c37bc2a2: card notificationIcon -> notification

  cards.e939bcf3-9eab-4e0e-8483-8ba6f833a891: card notificationIcon -> notification

  cards.e03fb580-97cc-42c9-aacf-758b7fcce599: card notificationIcon -> notification

  cards.160626c8-b0f8-4616-851b-926595db8b15: card notificationIcon -> notification

  cards.3b7f2b5a-3387-4fed-9b01-4cd7f948b60c: card notificationIcon -> notification

  cards.0b574371-4df0-4f11-b3b8-7ebd99e33acc: card notificationIcon -> notification

  cards.3121aca1-e3e4-407c-a0ec-ada139d269a8: card notificationIcon -> notification

  cards.1e2f4abd-18a7-400b-b9b2-5493fb4e08f8: card notificationIcon -> notification

  cards.f20517c5-05ab-4446-ae8e-3ec8badf0090: card notificationIcon -> notification

  cards.901e08fa-81fd-46ab-957f-eb81e5e95667: card notificationIcon -> notification

  cards.59ee56e2-a6c9-48f1-8f26-2edaa7844013: card notificationIcon -> notification

  cards.ebb212e2-7a65-4d8a-8a32-b364b45540c8: card notificationIcon -> notification

  cards.cd74ef2f-66ae-4397-ad39-6acd1c15823f: card notificationIcon -> notification

  cards.a00b0b28-45db-4549-a81a-3728fe24c123: card notificationIcon -> notification

[advanced] AWTrix notifications: planned (0 UUID occurrence(s))

  cards.b6fc60f1-388c-4bcc-a09e-34f9487d7a8d: card notificationIcon -> notification

  cards.3df5f31a-7884-4a92-b570-577f1849f4e4: card notificationIcon -> notification

  cards.847e03b9-a1e5-43ad-a30b-92dfe184a643: card notificationIcon -> notification

  cards.0ad352f8-e537-407a-836d-e7028111d6c0: card notificationIcon -> notification

[advanced] Doorbell rang - adv (nonAthom): planned (0 UUID occurrence(s))

  cards.d1adc972-73cc-4ec5-8090-7cff77f56458: card notificationIcon -> notification

[advanced] MKubeska YouTube adv: planned (0 UUID occurrence(s))

  cards.9158f02e-183e-4ead-8dc3-81d39bdc0423: card notificationIcon -> notification

[advanced] HaasSohn checks adv (Advanced Scheduler): planned (0 UUID occurrence(s))

  cards.de70a46c-6ebd-4d66-a7e6-f4ca4e3e9b96: card notificationIcon -> notification

  cards.893a09c8-dea3-4271-b0a3-496f9dd1e836: card notificationIcon -> notification

  cards.6dd15f03-b5fd-492f-991c-30a28d29a9de: card notificationIcon -> notification

[advanced] Water pressure calculation adv: planned (0 UUID occurrence(s))

  cards.1f5a6548-53ec-49d9-85ab-c6c645bff37d: card notificationIcon -> notification

  cards.7fe20785-b3ed-4834-9d57-90d162e3caf4: card notificationIcon -> notification

  cards.d8855cd9-ce7c-4083-b05c-54a2a23af312: card notificationIcon -> notification

  cards.77de0cb7-969a-4cbf-ba24-dd3835c4fc23: card notificationIcon -> notification

  cards.c76e39b5-7e48-40b6-83bd-be9f14273242: card notificationIcon -> notification

  cards.a2603d68-a70e-4586-84d3-e65d9182513e: card notificationIcon -> notification

  cards.7e1fd0cf-72f7-4d09-981f-c0f9ecb90006: card notificationIcon -> notification

[advanced] Movement in front of house Eufy adv: planned (0 UUID occurrence(s))

  cards.e984d301-c74c-4dda-982c-06029e58ed8f: card notificationIcon -> notification

[advanced] Airplane tracker OpenSky, Flightradar adv: planned (0 UUID occurrence(s))

  cards.33120d80-8a4a-4201-826c-54f296d9fdff: card notificationIcon -> notification

[advanced] Pračka susička změna stavu adv: planned (0 UUID occurrence(s))

  cards.215f3404-e317-4543-8530-1466ed7c23eb: card notificationIcon -> notification

  cards.fc26b0f5-0323-47fe-915e-706ef9776efd: card notificationIcon -> notification

  cards.96b2d7aa-9989-4898-ac5e-d6f95dd8de67: card notificationIcon -> notification

  cards.db8440ed-3748-4f9c-8a97-a31d6f55b670: card notificationIcon -> notification

--- Summary ---

Regular flows planned: 2

Advanced flows planned: 12

Successfully written: 14

Failed writes: 0

Soft run: false

Thanks for creating this script, @Sharkys! If only I waited a little longer before migrating my AWTRIX 3 flows to NG manually… :blush:.

One small side-note: next to the flowcards also many of the JSON keys and values have changed. This means that I you are using flowcards in which the JSON field/parameter is populated, you’ll have to check and update those manually. On the positive side: this requires you to look at the new and very elaborate documentation of AWTRIX NG and learn/get an understanding of the new commands and syntax, which I personally found very valuable.

Right, you actually reminded me on JSONs indeed, which some are now build in cards for NG already but they will be not reported broken, simply they don’t work anymore :wink:


(at least in my case, didn’t use JSON too much - btw any interesting use case for using JSONs ? )

Yes, for some functionality there is a ready-made card available.

I mostly use the app and notification cards. If you want to control/manipulate the displayed text a little bit more then just the color and icon, then additional JSON-parameters are needed. Which is great because you can customize the text like you want.

For example I have a flow that creates/updates an app that shows the current weather alarm info (if any). It shows the weather alarm code (yellow/orange/red) in the color of the alarm code followed by the alarm’s info in white (so colored text fragments). Also the behavior of the icon and to show a longer message in full (instead of switching to the next app mid-way when the default app duration expires) requires additional JSON-parameters.

Similarly for e.g. displaying the album/artist/track info of the currently playing song in full (which is a longer text) requires additional JSON-parameters. I use an app for that as well as a notification that displays the new info when a track changes.

Note that this was also the case for the AWTRIX 3 flow cards. So I used the JSON parameters already for manipulating the text.

Yeah, i could make bc compatible layer :confused: sorry for that… my goal was to make new driver as much clean as possible. but we could try to put this migration to AI to write it even with json option changes, and yeah now i belive all json options are available :slight_smile:

but if you miss some functionallity, write me, i will add it :slight_smile:

Btw, if you want entire text no matter how long it is, give it json option: repeat: 1

It will display the entire message once, no matter of time set to display message

Yes, that is exactly what I am using :grinning_face:.

I am not missing any functionality (except the battery percentage we talked about in an earlier post).

By the way: I think your choice to offer basic flowcards for creating an app or notification and a more advanced version of each that allows you to add JSON-parameters is perfect. Keep it simple by default and offer the option to create fully bespoke apps/notifications for more advanced users :+1:.

FYI, I have submitted PR for that.

I know, merged, released to test branche… :slight_smile:

A small observation in the latest test version (2.2.2).

The WHEN section of the AWTRIX NG device’s flowcards contains 3 x the same set of sustained value cards for the ‘general alarm’ (General alarm activated/deactivated for [duration]):

i see it too, already have fix… battery_measure add some issues i did not expect… so i need to clean code a bit… Athom explicitly forbids to have battery_measure and lowBattery… Issue is, not all awtrix need to have battery, so im thinking how to do it without compromising anything…

battery_measure explicitly requires battery type in manifest, we can put here internal, but what if device have no battery? Low_battery only was easier :smiley: you could simply ignore it if battery was not present…

Fix released to test: 2.2.3